- remove AbortController in PendingApprovalEntry (#902)

- fix find execution process check
- force stop process with non-killed status
This commit is contained in:
Gabriel Gordon-Hall
2025-10-01 16:51:23 +01:00
committed by GitHub
parent c78f48ae02
commit 0ace01b55f
13 changed files with 101 additions and 181 deletions

View File

@@ -15,7 +15,6 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { CircularProgress } from '@/components/ui/circular-progress';
import { approvalsApi } from '@/lib/api';
import { Check, X } from 'lucide-react';
import { Textarea } from '@/components/ui/textarea';
@@ -33,21 +32,6 @@ interface PendingApprovalEntryProps {
children: ReactNode;
}
// ---------- Utils ----------
function formatSeconds(s: number) {
if (s <= 0) return '0s';
const m = Math.floor(s / 60);
const rem = s % 60;
return m > 0 ? `${m}m ${rem}s` : `${rem}s`;
}
// ---------- Hooks ----------
function useAbortController() {
const ref = useRef<AbortController | null>(null);
useEffect(() => () => ref.current?.abort(), []);
return ref;
}
function useApprovalCountdown(
requestedAt: string | number | Date,
timeoutAt: string | number | Date,
@@ -86,31 +70,6 @@ function useApprovalCountdown(
return { timeLeft, percent };
}
// ---------- Subcomponents ----------
function ProgressWithTooltip({
visible,
timeLeft,
percent,
}: {
visible: boolean;
timeLeft: number;
percent: number;
}) {
if (!visible) return null;
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center pr-8">
<CircularProgress percent={percent} />
</div>
</TooltipTrigger>
<TooltipContent>
<p>{formatSeconds(timeLeft)} remaining</p>
</TooltipContent>
</Tooltip>
);
}
function ActionButtons({
disabled,
isResponding,
@@ -165,8 +124,6 @@ function ActionButtons({
function DenyReasonForm({
isResponding,
timeLeft,
percent,
value,
onChange,
onCancel,
@@ -174,8 +131,6 @@ function DenyReasonForm({
inputRef,
}: {
isResponding: boolean;
timeLeft: number;
percent: number;
value: string;
onChange: (v: string) => void;
onCancel: () => void;
@@ -193,11 +148,6 @@ function DenyReasonForm({
className="text-sm"
/>
<div className="mt-3 flex flex-wrap items-center justify-between gap-2">
<ProgressWithTooltip
visible={timeLeft > 0}
timeLeft={timeLeft}
percent={percent}
/>
<div className="flex items-center gap-2 text-sm">
<Button
variant="ghost"
@@ -228,7 +178,6 @@ const PendingApprovalEntry = ({
const [isEnteringReason, setIsEnteringReason] = useState(false);
const [denyReason, setDenyReason] = useState('');
const abortRef = useAbortController();
const denyReasonRef = useRef<HTMLTextAreaElement | null>(null);
const { enableScope, disableScope, activeScopes } = useHotkeysContext();
@@ -243,7 +192,7 @@ const PendingApprovalEntry = ({
dialogScopeActiveRef.current = dialogScopeActive;
}, [dialogScopeActive]);
const { timeLeft, percent } = useApprovalCountdown(
const { timeLeft } = useApprovalCountdown(
pendingStatus.requested_at,
pendingStatus.timeout_at,
hasResponded
@@ -294,19 +243,16 @@ const PendingApprovalEntry = ({
setIsResponding(true);
setError(null);
const controller = new AbortController();
abortRef.current = controller;
const status: ApprovalStatus = approved
? { status: 'approved' }
: { status: 'denied', reason };
try {
await approvalsApi.respond(
pendingStatus.approval_id,
{ execution_process_id: executionProcessId, status },
controller.signal
);
await approvalsApi.respond(pendingStatus.approval_id, {
execution_process_id: executionProcessId,
status,
});
setHasResponded(true);
setIsEnteringReason(false);
setDenyReason('');
@@ -317,7 +263,7 @@ const PendingApprovalEntry = ({
setIsResponding(false);
}
},
[abortRef, disabled, executionProcessId, pendingStatus.approval_id]
[disabled, executionProcessId, pendingStatus.approval_id]
);
const handleApprove = useCallback(() => respond(true), [respond]);
@@ -368,23 +314,19 @@ const PendingApprovalEntry = ({
return (
<div className="relative mt-3">
<div className="absolute -top-3 left-4 rounded-full border bg-background px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide shadow-sm">
Awaiting approval
</div>
<div className="overflow-hidden border">
{children}
<div className="border-t bg-background px-2 py-1.5 text-xs sm:text-sm">
<TooltipProvider>
<div className="flex items-center justify-between gap-1.5 pl-4">
{!isEnteringReason && !hasResponded && (
<ProgressWithTooltip
visible={timeLeft > 0}
timeLeft={timeLeft}
percent={percent}
/>
)}
<div className="flex items-center gap-1.5">
{!isEnteringReason && (
<span className="text-muted-foreground">
Would you like to approve this?
</span>
)}
</div>
{!isEnteringReason && (
<ActionButtons
disabled={disabled}
@@ -408,8 +350,6 @@ const PendingApprovalEntry = ({
{isEnteringReason && !hasResponded && (
<DenyReasonForm
isResponding={isResponding}
timeLeft={timeLeft}
percent={percent}
value={denyReason}
onChange={setDenyReason}
onCancel={handleCancelDeny}

View File

@@ -1,50 +0,0 @@
import * as React from 'react';
interface CircularProgressProps {
percent: number;
}
export const CircularProgress: React.FC<CircularProgressProps> = ({
percent,
}) => {
const size = 24;
const strokeWidth = 2;
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = circumference - (percent / 100) * circumference;
return (
<div
className="relative inline-flex items-center justify-center"
style={{ width: size, height: size }}
>
<svg
width={size}
height={size}
className="absolute inset-0 transform -rotate-90"
>
<circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke="currentColor"
strokeWidth={strokeWidth}
fill="none"
className="text-muted-foreground/20"
/>
<circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke="currentColor"
strokeWidth={strokeWidth}
fill="none"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
className="text-muted-foreground transition-all duration-1000 ease-linear"
strokeLinecap="round"
/>
</svg>
</div>
);
};