Skip to content

Commit bb0432b

Browse files
Merge pull request #12 from wttech/auto-refresh-execution
Auto refresh execution
2 parents 68baa32 + 199b24a commit bb0432b

11 files changed

+471
-412
lines changed

ui.frontend/src/components/ExecutionAbortButton.tsx

+53-63
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,70 @@
11
import React, { useState } from 'react';
2-
import {
3-
Button,
4-
ButtonGroup,
5-
Content,
6-
Dialog,
7-
DialogTrigger,
8-
Divider,
9-
Heading,
10-
Text
11-
} from '@adobe/react-spectrum';
12-
import { toastRequest } from '../utils/api';
2+
import { Button, Text } from '@adobe/react-spectrum';
133
import Cancel from '@spectrum-icons/workflow/Cancel';
14-
import Checkmark from '@spectrum-icons/workflow/Checkmark';
15-
import {QueueOutput} from "../utils/api.types.ts";
4+
import { ToastQueue } from '@react-spectrum/toast';
5+
import { apiRequest } from '../utils/api';
6+
import {Execution, ExecutionStatus, isExecutionPending, QueueOutput} from '../utils/api.types';
167

17-
type ExecutionAbortButtonProps = {
18-
selectedKeys: string[];
19-
onAbort?: () => void;
20-
};
21-
22-
const ExecutionAbortButton: React.FC<ExecutionAbortButtonProps> = ({ selectedKeys, onAbort }) => {
23-
const [abortDialogOpen, setAbortDialogOpen] = useState(false);
24-
const [isLoading, setIsLoading] = useState(false);
8+
const executionPollInterval = 1000;
9+
const toastTimeout = 3000;
2510

26-
const handleConfirm = async () => {
27-
setIsLoading(true);
28-
const ids = Array.from(selectedKeys);
11+
interface ExecutionAbortButtonProps {
12+
execution: Execution | null;
13+
onComplete: (execution: Execution | null) => void;
14+
}
2915

30-
const params = new URLSearchParams();
31-
ids.forEach((id) => params.append('jobId', id));
16+
const ExecutionAbortButton: React.FC<ExecutionAbortButtonProps> = ({ execution, onComplete }) => {
17+
const [isAborting, setIsAborting] = useState(false);
3218

19+
const onAbort = async () => {
20+
if (!execution?.id) {
21+
console.warn('Code execution cannot be aborted as it is not running!');
22+
return;
23+
}
24+
setIsAborting(true);
3325
try {
34-
await toastRequest<QueueOutput>({
35-
method: 'DELETE',
36-
url: `/apps/acm/api/queue-code.json?${params.toString()}`,
37-
operation: 'Abort executions',
26+
await apiRequest<QueueOutput>({
27+
operation: 'Code execution aborting',
28+
url: `/apps/acm/api/queue-code.json?jobId=${execution.id}`,
29+
method: 'delete',
3830
});
39-
if (onAbort) onAbort();
31+
32+
let queuedExecution: Execution | null = null;
33+
while (queuedExecution === null || isExecutionPending(queuedExecution.status)) {
34+
const response = await apiRequest<QueueOutput>({
35+
operation: 'Code execution state',
36+
url: `/apps/acm/api/queue-code.json?jobId=${execution.id}`,
37+
method: 'get',
38+
});
39+
queuedExecution = response.data.data.executions[0]!;
40+
onComplete(queuedExecution);
41+
await new Promise((resolve) => setTimeout(resolve, executionPollInterval));
42+
}
43+
if (queuedExecution.status === ExecutionStatus.ABORTED) {
44+
ToastQueue.positive('Code execution aborted successfully!', {
45+
timeout: toastTimeout,
46+
});
47+
} else {
48+
console.warn('Code execution aborting failed!');
49+
ToastQueue.negative('Code execution aborting failed!', {
50+
timeout: toastTimeout,
51+
});
52+
}
4053
} catch (error) {
41-
console.error('Abort executions error:', error);
54+
console.error('Code execution aborting error:', error);
55+
ToastQueue.negative('Code execution aborting failed!', {
56+
timeout: toastTimeout,
57+
});
4258
} finally {
43-
setIsLoading(false);
44-
setAbortDialogOpen(false);
59+
setIsAborting(false);
4560
}
4661
};
4762

48-
const renderAbortDialog = () => (
49-
<>
50-
<Heading>
51-
<Text>Confirmation</Text>
52-
</Heading>
53-
<Divider />
54-
<Content>
55-
<Text>Are you sure you want to abort the selected executions? This action cannot be undone.</Text>
56-
</Content>
57-
<ButtonGroup>
58-
<Button variant="secondary" onPress={() => setAbortDialogOpen(false)} isDisabled={isLoading}>
59-
<Cancel />
60-
<Text>Cancel</Text>
61-
</Button>
62-
<Button variant="negative" style="fill" onPress={handleConfirm} isPending={isLoading}>
63-
<Checkmark />
64-
<Text>Confirm</Text>
65-
</Button>
66-
</ButtonGroup>
67-
</>
68-
);
69-
7063
return (
71-
<DialogTrigger isOpen={abortDialogOpen} onOpenChange={setAbortDialogOpen}>
72-
<Button variant="negative" style="fill" isDisabled={selectedKeys.length === 0} onPress={() => setAbortDialogOpen(true)}>
73-
<Cancel />
74-
<Text>Abort</Text>
75-
</Button>
76-
<Dialog>{renderAbortDialog()}</Dialog>
77-
</DialogTrigger>
64+
<Button variant="negative" isDisabled={!execution || !isExecutionPending(execution.status) || isAborting} onPress={onAbort}>
65+
<Cancel />
66+
<Text>Abort</Text>
67+
</Button>
7868
);
7969
};
8070

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { Button, ButtonGroup, Text } from '@adobe/react-spectrum';
3+
import { ToastQueue } from '@react-spectrum/toast';
4+
import Copy from '@spectrum-icons/workflow/Copy';
5+
6+
const toastTimeout = 3000;
7+
8+
interface ExecutionCopyOutputButtonProps {
9+
output: string;
10+
}
11+
12+
const ExecutionCopyOutputButton: React.FC<ExecutionCopyOutputButtonProps> = ({ output }) => {
13+
const onCopyExecutionOutput = () => {
14+
if (output) {
15+
navigator.clipboard
16+
.writeText(output)
17+
.then(() => {
18+
ToastQueue.info('Execution output copied to clipboard!', {
19+
timeout: toastTimeout,
20+
});
21+
})
22+
.catch(() => {
23+
ToastQueue.negative('Failed to copy execution output!', {
24+
timeout: toastTimeout,
25+
});
26+
});
27+
} else {
28+
ToastQueue.negative('No execution output to copy!', {
29+
timeout: toastTimeout,
30+
});
31+
}
32+
};
33+
34+
return (
35+
<Button variant="secondary" isDisabled={!output} onPress={onCopyExecutionOutput}>
36+
<Copy />
37+
<Text>Copy</Text>
38+
</Button>
39+
);
40+
};
41+
42+
export default ExecutionCopyOutputButton;

ui.frontend/src/components/ExecutionProgressBar.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { Meter, ProgressBar } from '@adobe/react-spectrum';
22
import React from 'react';
33
import { Execution, isExecutionPending } from '../utils/api.types.ts';
44
import { Strings } from '../utils/strings.ts';
5+
import {useFormatter} from "../utils/hooks/formatter.ts";
56

67
interface ExecutionProgressBarProps {
78
execution: Execution | null;
8-
active: boolean;
9+
active?: boolean;
910
}
1011

1112
const ExecutionProgressBar: React.FC<ExecutionProgressBarProps> = ({ execution, active }) => {
@@ -23,13 +24,15 @@ const ExecutionProgressBar: React.FC<ExecutionProgressBarProps> = ({ execution,
2324
}
2425
};
2526

27+
const formatter = useFormatter();
28+
2629
return (
2730
<>
2831
{execution ? (
2932
active || isExecutionPending(execution.status) ? (
3033
<ProgressBar aria-label="Executing" showValueLabel={false} label="Executing…" isIndeterminate />
3134
) : (
32-
<Meter aria-label="Executed" variant={variant()} showValueLabel={false} value={100} label={`${Strings.capitalize(execution.status)} after ${execution.duration} ms`} />
35+
<Meter aria-label="Executed" variant={variant()} showValueLabel={false} value={100} label={`${Strings.capitalize(execution.status)} after ${formatter.durationShort(execution.duration)}`} />
3336
)
3437
) : (
3538
<Meter aria-label="Not executing" label="Not executing" showValueLabel={false} value={0} />

ui.frontend/src/components/ExecutionStatusBadge.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Badge, ProgressCircle, Text} from '@adobe/react-spectrum';
1+
import { Badge, ProgressCircle, SpectrumBadgeProps, Text } from '@adobe/react-spectrum';
22
import Alert from '@spectrum-icons/workflow/Alert';
33
import Cancel from '@spectrum-icons/workflow/Cancel';
44
import Checkmark from '@spectrum-icons/workflow/Checkmark';
@@ -7,9 +7,9 @@ import Pause from '@spectrum-icons/workflow/Pause';
77
import React from 'react';
88
import { ExecutionStatus } from '../utils/api.types';
99

10-
interface ExecutionStatusProps {
10+
type ExecutionStatusProps = {
1111
value: ExecutionStatus;
12-
}
12+
} & Partial<SpectrumBadgeProps>;
1313

1414
const getVariant = (status: ExecutionStatus): 'positive' | 'negative' | 'neutral' | 'info' | 'yellow' => {
1515
switch (status) {
@@ -52,12 +52,12 @@ const getIcon = (status: ExecutionStatus) => {
5252
}
5353
};
5454

55-
const ExecutionStatusBadge: React.FC<ExecutionStatusProps> = ({ value }) => {
55+
const ExecutionStatusBadge: React.FC<ExecutionStatusProps> = ({ value, ...props }) => {
5656
const variant = getVariant(value);
5757
const icon = getIcon(value);
5858

5959
return (
60-
<Badge variant={variant}>
60+
<Badge variant={variant} {...props}>
6161
<Text>{value.toLowerCase()}</Text>
6262
{icon}
6363
</Badge>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useState } from 'react';
2+
import {
3+
Button,
4+
ButtonGroup,
5+
Content,
6+
Dialog,
7+
DialogTrigger,
8+
Divider,
9+
Heading,
10+
Text
11+
} from '@adobe/react-spectrum';
12+
import { toastRequest } from '../utils/api';
13+
import Cancel from '@spectrum-icons/workflow/Cancel';
14+
import Checkmark from '@spectrum-icons/workflow/Checkmark';
15+
import {QueueOutput} from "../utils/api.types.ts";
16+
17+
type ExecutionsAbortButtonProps = {
18+
selectedKeys: string[];
19+
onAbort?: () => void;
20+
};
21+
22+
const ExecutionsAbortButton: React.FC<ExecutionsAbortButtonProps> = ({ selectedKeys, onAbort }) => {
23+
const [abortDialogOpen, setAbortDialogOpen] = useState(false);
24+
const [isLoading, setIsLoading] = useState(false);
25+
26+
const handleConfirm = async () => {
27+
setIsLoading(true);
28+
const ids = Array.from(selectedKeys);
29+
30+
const params = new URLSearchParams();
31+
ids.forEach((id) => params.append('jobId', id));
32+
33+
try {
34+
await toastRequest<QueueOutput>({
35+
method: 'DELETE',
36+
url: `/apps/acm/api/queue-code.json?${params.toString()}`,
37+
operation: 'Abort executions',
38+
});
39+
if (onAbort) onAbort();
40+
} catch (error) {
41+
console.error('Abort executions error:', error);
42+
} finally {
43+
setIsLoading(false);
44+
setAbortDialogOpen(false);
45+
}
46+
};
47+
48+
const renderAbortDialog = () => (
49+
<>
50+
<Heading>
51+
<Text>Confirmation</Text>
52+
</Heading>
53+
<Divider />
54+
<Content>
55+
<Text>Are you sure you want to abort the selected executions? This action cannot be undone.</Text>
56+
</Content>
57+
<ButtonGroup>
58+
<Button variant="secondary" onPress={() => setAbortDialogOpen(false)} isDisabled={isLoading}>
59+
<Cancel />
60+
<Text>Cancel</Text>
61+
</Button>
62+
<Button variant="negative" style="fill" onPress={handleConfirm} isPending={isLoading}>
63+
<Checkmark />
64+
<Text>Confirm</Text>
65+
</Button>
66+
</ButtonGroup>
67+
</>
68+
);
69+
70+
return (
71+
<DialogTrigger isOpen={abortDialogOpen} onOpenChange={setAbortDialogOpen}>
72+
<Button variant="negative" style="fill" isDisabled={selectedKeys.length === 0} onPress={() => setAbortDialogOpen(true)}>
73+
<Cancel />
74+
<Text>Abort</Text>
75+
</Button>
76+
<Dialog>{renderAbortDialog()}</Dialog>
77+
</DialogTrigger>
78+
);
79+
};
80+
81+
export default ExecutionsAbortButton;

ui.frontend/src/components/ScriptExecutor.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import Replay from "@spectrum-icons/workflow/Replay";
3838
import Checkmark from "@spectrum-icons/workflow/Checkmark";
3939
import Cancel from "@spectrum-icons/workflow/Cancel";
4040
import Settings from "@spectrum-icons/workflow/Settings";
41-
import ExecutionAbortButton from './ExecutionAbortButton';
41+
import ExecutionsAbortButton from './ExecutionsAbortButton.tsx';
4242
import Code from "@spectrum-icons/workflow/Code";
4343

4444
const ScriptExecutor = () => {
@@ -70,7 +70,7 @@ const ScriptExecutor = () => {
7070
<Flex direction="row" justifyContent="space-between" alignItems="center">
7171
<Flex flex="1" alignItems="center">
7272
<ButtonGroup>
73-
<ExecutionAbortButton selectedKeys={selectedIds(selectedKeys)}/>
73+
<ExecutionsAbortButton selectedKeys={selectedIds(selectedKeys)}/>
7474
<MenuTrigger>
7575
<Button variant="negative">
7676
<Settings />

0 commit comments

Comments
 (0)