Skip to content

Commit b320645

Browse files
authored
[DT-1051] Add identity and reason to Terminate in the UI (#1399)
* Fix extra space if there is no user defined reason * Add Web UI reason to terminate and reset on workflow * Add identity to terminate workflow * Remove workflow and use workflows namespace instead * Refactor reason and placeholder into utils with tests * Fix batch cancel toast translation * Add identity to terminateWorkflows
1 parent 6a149d5 commit b320645

File tree

10 files changed

+207
-41
lines changed

10 files changed

+207
-41
lines changed

src/lib/components/workflow-actions.svelte

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
signalWorkflow,
66
terminateWorkflow,
77
} from '$lib/services/workflow-service';
8+
import { authUser } from '$lib/stores/auth-user';
89
10+
import { formatReason } from '$lib/utilities/workflow-actions';
11+
import { Action } from '$lib/models/workflow-actions';
912
import { writeActionsAreAllowed } from '$lib/utilities/write-actions-are-allowed';
1013
import { ResetReapplyType } from '$lib/models/workflow-actions';
1114
@@ -90,7 +93,12 @@
9093
terminateWorkflow({
9194
workflow,
9295
namespace,
93-
reason,
96+
reason: formatReason({
97+
action: Action.Terminate,
98+
reason,
99+
email: $authUser.email,
100+
}),
101+
identity: $authUser.email,
94102
})
95103
.then(handleSuccessfulTermination)
96104
.catch(handleTerminationError);
@@ -153,7 +161,11 @@
153161
workflowId: workflow.id,
154162
runId: workflow.runId,
155163
eventId: resetId,
156-
reason: resetReason,
164+
reason: formatReason({
165+
action: Action.Reset,
166+
reason: resetReason,
167+
email: $authUser.email,
168+
}),
157169
resetReapplyType,
158170
});
159171

src/lib/components/workflow/batch-operation-confirmation-modal.svelte

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
<script lang="ts" context="module">
2-
export enum Action {
3-
Terminate,
4-
Cancel,
5-
}
6-
</script>
7-
81
<script lang="ts">
92
import { createEventDispatcher } from 'svelte';
103
import Modal from '$lib/holocene/modal.svelte';
@@ -13,6 +6,8 @@
136
import { allSelected } from '$lib/pages/workflows-with-new-search.svelte';
147
import { translate } from '$lib/i18n/translate';
158
import Translate from '$lib/i18n/translate.svelte';
9+
import { formatReason, getPlacholder } from '$lib/utilities/workflow-actions';
10+
import { Action } from '$lib/models/workflow-actions';
1611
1712
export let action: Action;
1813
export let actionableWorkflowsLength: number;
@@ -32,31 +27,14 @@
3227
? translate('workflows', 'cancel')
3328
: translate('workflows', 'terminate');
3429
35-
$: pastTenseActionText =
36-
action === Action.Cancel
37-
? translate('workflows', 'canceled')
38-
: translate('workflows', 'terminated');
30+
$: placeholder = getPlacholder(action, $authUser.email);
3931
40-
let placeholder: string;
41-
$: {
42-
if ($authUser.email) {
43-
placeholder = translate(
44-
'workflows',
45-
'batch-operation-confirmation-placeholder-by-email',
46-
{ action: pastTenseActionText, email: $authUser.email },
47-
);
48-
} else {
49-
placeholder = translate(
50-
'workflows',
51-
'batch-operation-confirmation-placeholder',
52-
{ action: pastTenseActionText },
53-
);
54-
}
55-
}
5632
let reason: string = '';
5733
5834
const handleConfirmModal = () => {
59-
dispatch('confirm', { reason: [reason.trim(), placeholder].join(' ') });
35+
dispatch('confirm', {
36+
reason: formatReason({ action, reason, email: $authUser.email }),
37+
});
6038
};
6139
6240
const handleCancelModal = () => {

src/lib/i18n/locales/en/workflows.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export const Strings = {
1010
cancel: 'Cancel',
1111
reason: 'Reason',
1212
'batch-operation-modal-title': '{{action}} Workflows',
13-
'batch-operation-confirmation-placeholder': '{{action}} from the Web UI',
14-
'batch-operation-confirmation-placeholder-by-email':
13+
'workflow-action-reason-placeholder': '{{action}} from the Web UI',
14+
'workflow-action-reason-placeholder-with-email':
1515
'{{action}} from the Web UI by {{email}}',
1616
'batch-operation-confirmation-all':
1717
'Are you sure you want to {{action}} all workflows matching the following query? This action cannot be undone.',
@@ -32,7 +32,7 @@ export const Strings = {
3232
'batch-cancel-all-success':
3333
'The batch $t(cancel) request is processing in the background.',
3434
'batch-terminate-success': 'Successfully $t(terminated) {{count}} workflows.',
35-
'batch-cancel-success': 'Successfully $(canceled) {{count}} workflows.',
35+
'batch-cancel-success': 'Successfully $t(canceled) {{count}} workflows.',
3636
'configure-workflows': 'Configure Workflow List',
3737
'configure-workflows-description':
3838
'Add (<1></1>), re-arrange (<2></2>), and remove (<3></3>), Workflow Headings to personalize the Workflow List Table.',
@@ -46,6 +46,7 @@ export const Strings = {
4646
terminated: 'Terminated',
4747
canceled: 'Canceled',
4848
paused: 'Paused',
49+
reset: 'Reset',
4950
'n-selected': '{{count, number}} selected',
5051
'all-selected': 'All {{count, number}} selected.',
5152
'select-all': 'select all {{count, number}}',

src/lib/models/workflow-actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ export enum ResetReapplyType {
33
Signal = 1,
44
None = 2,
55
}
6+
7+
export enum Action {
8+
Cancel,
9+
Reset,
10+
Terminate,
11+
}

src/lib/pages/workflows-with-new-search.svelte

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@
7777
bulkTerminateByIDs,
7878
} from '$lib/services/batch-service';
7979
import { updateQueryParameters } from '$lib/utilities/update-query-parameters';
80-
import BatchOperationConfirmationModal, {
81-
Action,
82-
} from '$lib/components/workflow/batch-operation-confirmation-modal.svelte';
80+
import BatchOperationConfirmationModal from '$lib/components/workflow/batch-operation-confirmation-modal.svelte';
81+
import { Action } from '$lib/models/workflow-actions';
8382
import { supportsAdvancedVisibility } from '$lib/stores/advanced-visibility';
8483
import { toaster } from '$lib/stores/toaster';
8584
import WorkflowsSummaryConfigurableTable from '$lib/components/workflow/workflows-summary-configurable-table.svelte';

src/lib/services/batch-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { requestFromAPI } from '$lib/utilities/request-from-api';
55
import { routeForApi } from '$lib/utilities/route-for-api';
66
import { isVersionNewer } from '$lib/utilities/version-check';
77
import { temporalVersion } from '$lib/stores/versions';
8+
import { getAuthUser } from '$lib/stores/auth-user';
89

910
import type {
1011
StartBatchOperationRequest,
@@ -152,12 +153,13 @@ async function terminateWorkflows({
152153
}: CreateBatchOperationOptions): Promise<string> {
153154
const route = routeForApi('batch-operations', { namespace });
154155
const jobId = uuidv4();
156+
const identity = getAuthUser().email;
155157

156158
const body: StartBatchOperationRequest = {
157159
jobId,
158160
namespace,
159161
reason,
160-
terminationOperation: {},
162+
terminationOperation: { ...(identity && { identity }) },
161163
...(query && { visibilityQuery: query }),
162164
...(executions && { executions }),
163165
};

src/lib/services/workflow-service.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type TerminateWorkflowOptions = {
6060
workflow: WorkflowExecution;
6161
namespace: string;
6262
reason: string;
63+
identity?: string;
6364
};
6465

6566
export type ResetWorkflowOptions = {
@@ -244,14 +245,18 @@ export async function terminateWorkflow({
244245
workflow,
245246
namespace,
246247
reason,
248+
identity,
247249
}: TerminateWorkflowOptions): Promise<null> {
248250
const route = routeForApi('workflow.terminate', {
249251
namespace,
250252
workflowId: workflow.id,
251253
runId: workflow.runId,
252254
});
253255
return await requestFromAPI<null>(route, {
254-
options: { method: 'POST', body: stringifyWithBigInt({ reason }) },
256+
options: {
257+
method: 'POST',
258+
body: stringifyWithBigInt({ reason, ...(identity && { identity }) }),
259+
},
255260
notifyOnError: false,
256261
});
257262
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { Action } from '../models/workflow-actions';
3+
import { getPlacholder, formatReason } from './workflow-actions';
4+
5+
describe('getPlacholder', () => {
6+
describe('without an authorized user', () => {
7+
it('should return the correct placeholder', () => {
8+
expect(getPlacholder(Action.Cancel)).toEqual('Canceled from the Web UI');
9+
expect(getPlacholder(Action.Reset)).toEqual('Reset from the Web UI');
10+
expect(getPlacholder(Action.Terminate)).toEqual(
11+
'Terminated from the Web UI',
12+
);
13+
});
14+
});
15+
16+
describe('with authorized user', () => {
17+
it('should return the correct placeholder', () => {
18+
expect(getPlacholder(Action.Cancel, '[email protected]')).toEqual(
19+
'Canceled from the Web UI by [email protected]',
20+
);
21+
expect(getPlacholder(Action.Reset, '[email protected]')).toEqual(
22+
'Reset from the Web UI by [email protected]',
23+
);
24+
expect(getPlacholder(Action.Terminate, '[email protected]')).toEqual(
25+
'Terminated from the Web UI by [email protected]',
26+
);
27+
});
28+
});
29+
});
30+
31+
describe('formatReason', () => {
32+
describe('without an authorized user', () => {
33+
it('should return the reason with the placeholder', () => {
34+
expect(
35+
formatReason({ action: Action.Cancel, reason: 'Testing' }),
36+
).toEqual('Testing Canceled from the Web UI');
37+
expect(formatReason({ action: Action.Reset, reason: 'Testing' })).toEqual(
38+
'Testing Reset from the Web UI',
39+
);
40+
expect(
41+
formatReason({ action: Action.Terminate, reason: 'Testing' }),
42+
).toEqual('Testing Terminated from the Web UI');
43+
});
44+
});
45+
46+
it('should return the placeholder if there is no reason', () => {
47+
const reason = '';
48+
expect(formatReason({ action: Action.Cancel, reason })).toEqual(
49+
'Canceled from the Web UI',
50+
);
51+
expect(formatReason({ action: Action.Reset, reason })).toEqual(
52+
'Reset from the Web UI',
53+
);
54+
expect(formatReason({ action: Action.Terminate, reason })).toEqual(
55+
'Terminated from the Web UI',
56+
);
57+
});
58+
59+
describe('with an authorized user', () => {
60+
it('should return the reason with the placeholder', () => {
61+
const reason = 'Testing';
62+
63+
expect(
64+
formatReason({
65+
action: Action.Cancel,
66+
reason,
67+
68+
}),
69+
).toEqual('Testing Canceled from the Web UI by [email protected]');
70+
expect(
71+
formatReason({
72+
action: Action.Reset,
73+
reason,
74+
75+
}),
76+
).toEqual('Testing Reset from the Web UI by [email protected]');
77+
expect(
78+
formatReason({
79+
action: Action.Terminate,
80+
reason,
81+
82+
}),
83+
).toEqual('Testing Terminated from the Web UI by [email protected]');
84+
});
85+
86+
it('should return the placeholder if there is no reason', () => {
87+
const reason = '';
88+
89+
expect(
90+
formatReason({
91+
action: Action.Cancel,
92+
reason,
93+
94+
}),
95+
).toEqual('Canceled from the Web UI by [email protected]');
96+
expect(
97+
formatReason({
98+
action: Action.Reset,
99+
reason,
100+
101+
}),
102+
).toEqual('Reset from the Web UI by [email protected]');
103+
expect(
104+
formatReason({
105+
action: Action.Terminate,
106+
reason,
107+
108+
}),
109+
).toEqual('Terminated from the Web UI by [email protected]');
110+
});
111+
});
112+
});

src/lib/utilities/workflow-actions.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { translate } from '$lib/i18n/translate';
2+
import { Action } from '$lib/models/workflow-actions';
3+
4+
type PastActionText = 'terminated' | 'reset' | 'canceled';
5+
6+
function unhandledAction(action: never) {
7+
console.error('Unhandled action:', action);
8+
}
9+
10+
const getPastTenseActionText = (action: Action): PastActionText => {
11+
switch (action) {
12+
case Action.Cancel:
13+
return 'canceled';
14+
case Action.Reset:
15+
return 'reset';
16+
case Action.Terminate:
17+
return 'terminated';
18+
default:
19+
unhandledAction(action);
20+
}
21+
};
22+
23+
export const getPlacholder = (action: Action, email?: string): string => {
24+
const translatedAction = translate(
25+
'workflows',
26+
getPastTenseActionText(action),
27+
);
28+
29+
return email
30+
? translate('workflows', 'workflow-action-reason-placeholder-with-email', {
31+
action: translatedAction,
32+
email,
33+
})
34+
: translate('workflows', 'workflow-action-reason-placeholder', {
35+
action: translatedAction,
36+
});
37+
};
38+
39+
export const formatReason = ({
40+
action,
41+
reason,
42+
email,
43+
}: {
44+
action: Action;
45+
reason: string;
46+
email?: string;
47+
}) => {
48+
const placeholder = getPlacholder(action, email);
49+
return reason ? [reason.trim(), placeholder].join(' ') : placeholder;
50+
};

static/i18n/locales/en/workflows.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
"cancel": "Cancel",
88
"reason": "Reason",
99
"batch-operation-modal-title": "{{action}} Workflows",
10-
"batch-operation-confirmation-placeholder": "{{action}} from the Web UI",
11-
"batch-operation-confirmation-placeholder-by-email": "{{action}} from the Web UI by {{email}}",
10+
"workflow-action-reason-placeholder": "{{action}} from the Web UI",
11+
"workflow-action-reason-placeholder-with-email": "{{action}} from the Web UI by {{email}}",
1212
"batch-operation-confirmation-all": "Are you sure you want to {{action}} all workflows matching the following query? This action cannot be undone.",
1313
"batch-operation-count-disclaimer": "Note: The actual count of workflows that will be affected is the total number of running workflows matching this query at the time of clicking \"{{action}}\".",
1414
"batch-cancel-confirmation_one": "Are you sure you want to cancel {{count, number}} running workflow?",
@@ -19,7 +19,7 @@
1919
"batch-terminate-all-success": "The batch $t(terminate) request is processing in the background.",
2020
"batch-cancel-all-success": "The batch $t(cancel) request is processing in the background.",
2121
"batch-terminate-success": "Successfully $t(terminated) {{count}} workflows.",
22-
"batch-cancel-success": "Successfully $(canceled) {{count}} workflows.",
22+
"batch-cancel-success": "Successfully $t(canceled) {{count}} workflows.",
2323
"configure-workflows": "Configure Workflow List",
2424
"configure-workflows-description": "Add (<1></1>), re-arrange (<2></2>), and remove (<3></3>), Workflow Headings to personalize the Workflow List Table.",
2525
"all-statuses": "All Statuses",
@@ -32,6 +32,7 @@
3232
"terminated": "Terminated",
3333
"canceled": "Canceled",
3434
"paused": "Paused",
35+
"reset": "Reset",
3536
"n-selected": "{{count, number}} selected",
3637
"all-selected": "All {{count, number}} selected.",
3738
"select-all": "select all {{count, number}}",

0 commit comments

Comments
 (0)