@orchestrator-ui/orchestrator-ui-components 7.2.1 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +3 -6
  3. package/.turbo/turbo-test.log +8 -8
  4. package/CHANGELOG.md +12 -0
  5. package/dist/index.d.ts +1542 -305
  6. package/dist/index.js +10313 -9607
  7. package/dist/index.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/components/WfoButtonWithConfirm/WfoButtonWithConfirm.tsx +35 -0
  10. package/src/components/WfoButtonWithConfirm/index.ts +1 -0
  11. package/src/components/WfoPageTemplate/WfoPageTemplate/WfoPageTemplate.tsx +3 -5
  12. package/src/components/WfoPageTemplate/paths.ts +2 -0
  13. package/src/components/WfoPopover/WfoPopover.tsx +43 -0
  14. package/src/components/WfoPopover/index.ts +1 -0
  15. package/src/components/WfoPydanticForm/Footer.tsx +3 -3
  16. package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +10 -285
  17. package/src/components/WfoPydanticForm/fields/WfoTimestampField.tsx +67 -0
  18. package/src/components/WfoPydanticForm/fields/index.ts +1 -0
  19. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +10 -27
  20. package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +3 -0
  21. package/src/components/WfoWorkflowSteps/WfoStep/WfoStepForm.tsx +12 -21
  22. package/src/components/index.ts +2 -0
  23. package/src/configuration/constants.ts +1 -1
  24. package/src/configuration/version.ts +1 -1
  25. package/src/hooks/index.ts +1 -0
  26. package/src/hooks/useGetPydanticFormsConfig.tsx +324 -0
  27. package/src/messages/en-GB.json +27 -3
  28. package/src/messages/nl-NL.json +28 -4
  29. package/src/pages/metadata/WfoScheduleTaskFormPage.tsx +464 -0
  30. package/src/pages/metadata/WfoScheduledTasksPage.tsx +43 -3
  31. package/src/pages/metadata/WfoTasksPage.tsx +75 -1
  32. package/src/pages/metadata/index.ts +1 -0
  33. package/src/rtk/api.ts +1 -0
  34. package/src/rtk/endpoints/metadata/scheduledTasks.ts +131 -3
  35. package/src/rtk/endpoints/metadata/tasks.ts +2 -0
  36. package/src/types/types.ts +18 -0
@@ -0,0 +1,464 @@
1
+ import React from 'react';
2
+
3
+ import _ from 'lodash';
4
+ import { useTranslations } from 'next-intl';
5
+ import { useRouter } from 'next/router';
6
+ import {
7
+ PydanticForm,
8
+ PydanticFormApiResponseType,
9
+ PydanticFormFieldFormat,
10
+ PydanticFormFieldType,
11
+ } from 'pydantic-forms';
12
+ import type {
13
+ PydanticFormApiProvider,
14
+ PydanticFormDefinitionResponse,
15
+ PydanticFormSuccessResponse,
16
+ RawJsonProperties,
17
+ } from 'pydantic-forms';
18
+
19
+ import {
20
+ Footer,
21
+ PATH_METADATA_SCHEDULED_TASKS,
22
+ WfoContentHeader,
23
+ WfoLoading,
24
+ } from '@/components';
25
+ import { NUMBER_OF_ITEMS_REPRESENTING_ALL_ITEMS } from '@/configuration';
26
+ import { useGetPydanticFormsConfig, useShowToastMessage } from '@/hooks';
27
+ import type { CronKwargs, ScheduledTaskPostPayload } from '@/rtk';
28
+ import { useCreateScheduledTaskMutation, useGetTasksQuery } from '@/rtk';
29
+ import { Intervals, TaskDefinition, TaskType, ToastTypes } from '@/types';
30
+
31
+ type CreateScheduleFormStep1 = {
32
+ workflowId: TaskDefinition['workflowId'];
33
+ taskType: TaskType;
34
+ };
35
+
36
+ type CreateScheduleFormStep2Once = {
37
+ taskType: TaskType.DATE;
38
+ startDate: string;
39
+ };
40
+ type CreateScheduleFormStep2Cron = {
41
+ taskType: TaskType.CRON;
42
+ startDate: string;
43
+ cron: string;
44
+ };
45
+ type CreateScheduleFormStep2Interval = {
46
+ taskType: TaskType.INTERVAL;
47
+ startDate: string;
48
+ interval: Intervals;
49
+ };
50
+
51
+ type CreateScheduleFormStep2 =
52
+ | CreateScheduleFormStep2Once
53
+ | CreateScheduleFormStep2Cron
54
+ | CreateScheduleFormStep2Interval;
55
+
56
+ type CreateScheduleFormInput = [
57
+ CreateScheduleFormStep1,
58
+ CreateScheduleFormStep2,
59
+ ];
60
+
61
+ export const WfoScheduleTaskFormPage = () => {
62
+ const t = useTranslations('metadata.scheduleTaskForm');
63
+ const { showToastMessage } = useShowToastMessage();
64
+
65
+ const [createScheduledTask, mutationState] =
66
+ useCreateScheduledTaskMutation();
67
+ const { data, isLoading } = useGetTasksQuery({
68
+ first: NUMBER_OF_ITEMS_REPRESENTING_ALL_ITEMS,
69
+ after: 0,
70
+ });
71
+ const router = useRouter();
72
+ const { workflowId } = router.query;
73
+ const getFormStep2 = (
74
+ userInput: CreateScheduleFormInput,
75
+ ): PydanticFormDefinitionResponse => {
76
+ const getStep2Defs = () => ({
77
+ IntervalEnum: {
78
+ enum: [
79
+ Intervals.ONE_HOUR,
80
+ Intervals.TWO_HOURS,
81
+ Intervals.FOUR_HOURS,
82
+ Intervals.TWELVE_HOURS,
83
+ Intervals.TWENTY4_HOURS,
84
+ Intervals.ONE_WEEK,
85
+ Intervals.TWO_WEEKS,
86
+ Intervals.ONE_MONTH,
87
+ ],
88
+ options: {
89
+ [Intervals.ONE_HOUR]: t('1hour'),
90
+ [Intervals.TWO_HOURS]: t('2hours'),
91
+ [Intervals.FOUR_HOURS]: t('4hours'),
92
+ [Intervals.TWELVE_HOURS]: t('12hours'),
93
+ [Intervals.TWENTY4_HOURS]: t('24hours'),
94
+ [Intervals.ONE_WEEK]: t('1week'),
95
+ [Intervals.TWO_WEEKS]: t('2weeks'),
96
+ [Intervals.ONE_MONTH]: t('1month'),
97
+ },
98
+ title: t('selectTaskType'),
99
+ type: PydanticFormFieldType.STRING,
100
+ },
101
+ ButtonColor: {
102
+ enum: [
103
+ 'primary',
104
+ 'accent',
105
+ 'success',
106
+ 'warning',
107
+ 'danger',
108
+ 'ghost',
109
+ 'text',
110
+ ],
111
+ options: {},
112
+ title: 'ButtonColor',
113
+ type: PydanticFormFieldType.STRING,
114
+ },
115
+ ButtonConfig: {
116
+ additionalProperties: false,
117
+ properties: {
118
+ text: {
119
+ title: 'Text',
120
+ type: PydanticFormFieldType.STRING,
121
+ },
122
+ dialog: {
123
+ title: 'Dialog',
124
+ type: PydanticFormFieldType.STRING,
125
+ },
126
+ color: {
127
+ $ref: '#/$defs/ButtonColor',
128
+ },
129
+ },
130
+ enum: [],
131
+ title: 'ButtonConfig',
132
+ options: {},
133
+ type: PydanticFormFieldType.OBJECT,
134
+ },
135
+ Buttons: {
136
+ additionalProperties: false,
137
+ title: 'Buttons',
138
+ type: PydanticFormFieldType.OBJECT,
139
+ properties: {
140
+ previous: {
141
+ $ref: '#/$defs/ButtonConfig',
142
+ },
143
+ next: {
144
+ $ref: '#/$defs/ButtonConfig',
145
+ },
146
+ },
147
+ required: ['previous', 'next'],
148
+ enum: [],
149
+ options: {},
150
+ },
151
+ });
152
+
153
+ const getStep2Properties = (
154
+ userInput: CreateScheduleFormInput,
155
+ ): RawJsonProperties => {
156
+ const step1UserInput = userInput[0];
157
+
158
+ const step2Properties: RawJsonProperties = {
159
+ startDate: {
160
+ type: PydanticFormFieldType.NUMBER,
161
+ format: PydanticFormFieldFormat.DATETIME,
162
+ title: t('firstRunDate'),
163
+ $ref: '',
164
+ uniforms: {
165
+ showTimeSelect: true,
166
+ },
167
+ },
168
+ };
169
+
170
+ if (step1UserInput.taskType === TaskType.INTERVAL) {
171
+ step2Properties.interval = {
172
+ type: PydanticFormFieldType.STRING,
173
+ format: PydanticFormFieldFormat.DROPDOWN,
174
+ title: t('selectInterval'),
175
+ $ref: '#/$defs/IntervalEnum',
176
+ };
177
+ }
178
+ if (step1UserInput.taskType === TaskType.CRON) {
179
+ const cronRegex =
180
+ '^(?:(\\*|([0-5]?\\d))(?:\\/(\\d+))?\\s+){4}(?:(\\*|([0-5]?\\d))(?:\\/(\\d+))?\\s+)?(?:([0-9,/*\\-?LW#]+)(?:\\s+([0-9,/*\\-?LW#]+))?(?:\\s+([0-9,/*\\-?LW#]+))?)$';
181
+
182
+ step2Properties.cron = {
183
+ type: PydanticFormFieldType.STRING,
184
+ format: PydanticFormFieldFormat.DEFAULT,
185
+ pattern: cronRegex,
186
+ $ref: '',
187
+ };
188
+ }
189
+
190
+ return step2Properties;
191
+ };
192
+
193
+ const step2Properties = getStep2Properties(userInput);
194
+ const form2Defs = getStep2Defs();
195
+ const formStep2: PydanticFormDefinitionResponse = {
196
+ type: PydanticFormApiResponseType.FORM_DEFINITION,
197
+ form: {
198
+ type: PydanticFormFieldType.OBJECT,
199
+ properties: {
200
+ buttons: {
201
+ $ref: '#/$defs/Buttons',
202
+ default: {
203
+ previous: {},
204
+ next: { text: t('createScheduleButton') },
205
+ },
206
+ type: PydanticFormFieldType.OBJECT,
207
+ format: PydanticFormFieldFormat.HIDDEN,
208
+ },
209
+ ...step2Properties,
210
+ },
211
+ $defs: { ...form2Defs },
212
+ },
213
+ meta: {
214
+ hasNext: false,
215
+ },
216
+ status: 510,
217
+ };
218
+ return formStep2;
219
+ };
220
+
221
+ const onSuccess = () => {
222
+ router.replace(PATH_METADATA_SCHEDULED_TASKS);
223
+ };
224
+
225
+ const onCancel = () => {
226
+ router.replace(PATH_METADATA_SCHEDULED_TASKS);
227
+ };
228
+
229
+ const getTaskByWorkflowId = (workflowId: TaskDefinition['workflowId']) => {
230
+ return data?.tasks.find((task) => task.workflowId === workflowId);
231
+ };
232
+
233
+ const createTask = (
234
+ userInput: CreateScheduleFormInput,
235
+ ): PydanticFormSuccessResponse => {
236
+ const getIntervalArg = (interval: Intervals) => {
237
+ const intervalMap = new Map([
238
+ [Intervals.ONE_HOUR, { hours: 1 }],
239
+ [Intervals.TWO_HOURS, { hours: 2 }],
240
+ [Intervals.FOUR_HOURS, { hours: 4 }],
241
+ [Intervals.TWELVE_HOURS, { hours: 12 }],
242
+ [Intervals.TWENTY4_HOURS, { hours: 24 }],
243
+ [Intervals.ONE_WEEK, { weeks: 1 }],
244
+ [Intervals.TWO_WEEKS, { weeks: 2 }],
245
+ [Intervals.ONE_MONTH, { weeks: 4 }],
246
+ ]);
247
+ return intervalMap.has(interval)
248
+ ? intervalMap.get(interval)
249
+ : undefined;
250
+ };
251
+
252
+ const getCronKwargs = (cron: string, startDate: string): CronKwargs => {
253
+ const [minute, hour, day, month, day_of_week] = cron.split(' ');
254
+
255
+ return {
256
+ start_date: startDate,
257
+ minute: parseInt(minute, 10),
258
+ hour: parseInt(hour, 10),
259
+ day: parseInt(day, 10),
260
+ month: parseInt(month, 10),
261
+ day_of_week: parseInt(day_of_week, 10),
262
+ };
263
+ };
264
+
265
+ const getCreateTaskPayload = (
266
+ userInput: CreateScheduleFormInput,
267
+ ): ScheduledTaskPostPayload => {
268
+ const userInputStep1 = userInput[0];
269
+ const userInputStep2 = userInput[1];
270
+
271
+ if (!userInputStep1 || !userInputStep2) {
272
+ throw new Error('Unknown or missing form input');
273
+ }
274
+
275
+ const startTimestampMilliseconds = parseInt(
276
+ userInputStep2.startDate,
277
+ 10,
278
+ );
279
+ const startDate = new Date(
280
+ startTimestampMilliseconds * 1000,
281
+ ).toISOString();
282
+
283
+ const task = getTaskByWorkflowId(userInputStep1.workflowId);
284
+
285
+ if (!task) {
286
+ throw Error('No task found with id');
287
+ }
288
+
289
+ if (userInputStep1.taskType === TaskType.DATE) {
290
+ return {
291
+ type: userInputStep1.taskType,
292
+ workflowId: task.workflowId,
293
+ workflowDescription: task.description,
294
+ workflowName: task.name,
295
+ kwargs: {
296
+ run_date: startDate,
297
+ },
298
+ };
299
+ } else if (userInputStep1.taskType === TaskType.INTERVAL) {
300
+ const step2Input =
301
+ userInputStep2 as CreateScheduleFormStep2Interval;
302
+ const intervalArg = getIntervalArg(step2Input.interval);
303
+
304
+ if (!intervalArg) {
305
+ throw new Error('Unknown or missing task interval');
306
+ }
307
+
308
+ return {
309
+ type: userInputStep1.taskType,
310
+ workflowId: task.workflowId,
311
+ workflowDescription: task.description,
312
+ workflowName: task.name,
313
+ kwargs: {
314
+ start_date: startDate,
315
+ ...intervalArg,
316
+ },
317
+ };
318
+ } else if (userInputStep1.taskType === TaskType.CRON) {
319
+ const step2Input =
320
+ userInputStep2 as CreateScheduleFormStep2Cron;
321
+ // minute hour day month weekday
322
+ return {
323
+ type: userInputStep1.taskType,
324
+ workflowId: task.workflowId,
325
+ workflowDescription: task.description,
326
+ workflowName: task.name,
327
+ kwargs: getCronKwargs(step2Input.cron, startDate),
328
+ };
329
+ }
330
+ throw new Error('Unknown or missing task type');
331
+ };
332
+
333
+ const createSchedulePayload = getCreateTaskPayload(userInput);
334
+ createScheduledTask(createSchedulePayload);
335
+
336
+ return {
337
+ type: PydanticFormApiResponseType.SUCCESS,
338
+ data: userInput,
339
+ status: 201,
340
+ };
341
+ };
342
+
343
+ const validateForm = (formInput: unknown): boolean => {
344
+ if (
345
+ !_.isEmpty(formInput) &&
346
+ _.isArray(formInput) &&
347
+ formInput.length === 2
348
+ ) {
349
+ return true;
350
+ }
351
+ return false;
352
+ };
353
+
354
+ const validateStep1 = (formInput: unknown): boolean => {
355
+ return !_.isEmpty(formInput);
356
+ };
357
+
358
+ const taskOptions =
359
+ data?.tasks.reduce((options, taskOption) => {
360
+ if (taskOption.isTask) {
361
+ return {
362
+ [taskOption.workflowId]: taskOption.description,
363
+ ...options,
364
+ };
365
+ }
366
+ return options;
367
+ }, {}) || {};
368
+
369
+ const formStep1: PydanticFormDefinitionResponse = {
370
+ type: PydanticFormApiResponseType.FORM_DEFINITION,
371
+ form: {
372
+ $defs: {
373
+ TaskTypeChoice: {
374
+ enum: ['once', 'recurring'],
375
+ options: {
376
+ [TaskType.DATE]: t('taskTypeDate'),
377
+ [TaskType.INTERVAL]: t('taskTypeInterval'),
378
+ [TaskType.CRON]: t('taskTypeCron'),
379
+ },
380
+ title: t('selectTaskType'),
381
+ type: PydanticFormFieldType.STRING,
382
+ },
383
+ TasksEnum: {
384
+ enum: Object.keys(taskOptions),
385
+ options: taskOptions,
386
+ title: t('selectTask'),
387
+ type: PydanticFormFieldType.STRING,
388
+ },
389
+ },
390
+ type: PydanticFormFieldType.OBJECT,
391
+ properties: {
392
+ workflowId: {
393
+ type: PydanticFormFieldType.STRING,
394
+ format: PydanticFormFieldFormat.DROPDOWN,
395
+ $ref: '#/$defs/TasksEnum',
396
+ default: workflowId as string,
397
+ },
398
+ taskType: {
399
+ type: PydanticFormFieldType.STRING,
400
+ format: PydanticFormFieldFormat.RADIO,
401
+ $ref: '#/$defs/TaskTypeChoice',
402
+ },
403
+ },
404
+ required: ['task', 'taskOption'],
405
+ },
406
+ meta: {
407
+ hasNext: true,
408
+ },
409
+ status: 510,
410
+ };
411
+
412
+ const getApiProvider = (): PydanticFormApiProvider => {
413
+ return ({
414
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
415
+ formKey,
416
+ requestBody,
417
+ }: {
418
+ formKey: string;
419
+ requestBody: CreateScheduleFormInput;
420
+ }) => {
421
+ const userInput = requestBody;
422
+ return new Promise<Record<string, unknown>>((resolve) => {
423
+ if (validateForm(userInput)) {
424
+ const successResponse = createTask(userInput);
425
+ return resolve(successResponse);
426
+ } else if (validateStep1(userInput[0])) {
427
+ const formStep2 = getFormStep2(userInput);
428
+ resolve(formStep2);
429
+ }
430
+
431
+ resolve(formStep1);
432
+ }).then((formDefinition) => {
433
+ return formDefinition;
434
+ });
435
+ };
436
+ };
437
+
438
+ const config = useGetPydanticFormsConfig(getApiProvider, Footer);
439
+
440
+ if (mutationState.isError) {
441
+ showToastMessage(
442
+ ToastTypes.ERROR,
443
+ '',
444
+ 'Error while saving scheduled task',
445
+ );
446
+ console.error('Error saving scheduled task', mutationState);
447
+ return undefined;
448
+ }
449
+
450
+ return (
451
+ <>
452
+ <WfoContentHeader title={t('newSchedule')} />
453
+ {(isLoading && <WfoLoading />) || (
454
+ <PydanticForm
455
+ formKey="add-schedule-key"
456
+ formId="add-schedule-id"
457
+ onSuccess={onSuccess}
458
+ onCancel={onCancel}
459
+ config={config}
460
+ />
461
+ )}
462
+ </>
463
+ );
464
+ };
@@ -1,10 +1,12 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
 
3
3
  import { useTranslations } from 'next-intl';
4
+ import { useRouter } from 'next/router';
4
5
 
5
- import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
6
+ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
6
7
 
7
8
  import {
9
+ WfoButtonWithConfirm,
8
10
  WfoDataSorting,
9
11
  WfoProductBlockBadge,
10
12
  WfoScheduledTasksBadges,
@@ -19,6 +21,7 @@ import {
19
21
  getDataSortHandler,
20
22
  getQueryStringHandler,
21
23
  } from '@/components';
24
+ import { PATH_METADATA_ADD_SCHEDULE_TASK_FORM } from '@/components';
22
25
  import { WfoAdvancedTable } from '@/components/WfoTable/WfoAdvancedTable';
23
26
  import { WfoAdvancedTableColumnConfig } from '@/components/WfoTable/WfoAdvancedTable/types';
24
27
  import { ColumnType, Pagination } from '@/components/WfoTable/WfoTable';
@@ -31,11 +34,12 @@ import {
31
34
  } from '@/hooks';
32
35
  import {
33
36
  ScheduledTasksResponse,
37
+ useDeleteScheduledTaskMutation,
34
38
  useGetScheduledTasksQuery,
35
39
  useLazyGetScheduledTasksQuery,
36
40
  } from '@/rtk';
37
41
  import { mapRtkErrorToWfoError } from '@/rtk/utils';
38
- import { BadgeType } from '@/types';
42
+ import { BadgeType, ToastTypes } from '@/types';
39
43
  import type { GraphqlQueryVariables, ScheduledTaskDefinition } from '@/types';
40
44
  import { SortOrder } from '@/types';
41
45
  import { formatDate } from '@/utils';
@@ -70,13 +74,24 @@ export const WfoScheduledTasksPage = () => {
70
74
  const t = useTranslations('metadata.scheduledTasks');
71
75
  const tError = useTranslations('errors');
72
76
  const { showToastMessage } = useShowToastMessage();
77
+ const router = useRouter();
73
78
  const [tableDefaults, setTableDefaults] =
74
79
  useState<StoredTableConfig<ScheduledTaskDefinition>>();
75
-
80
+ const [deleteScheduledTask, mutationState] =
81
+ useDeleteScheduledTaskMutation();
76
82
  const getStoredTableConfig = useStoredTableConfig<ScheduledTaskDefinition>(
77
83
  METADATA_SCHEDULES_LOCAL_STORAGE_KEY,
78
84
  );
79
85
 
86
+ if (mutationState.isError) {
87
+ showToastMessage(
88
+ ToastTypes.ERROR,
89
+ '',
90
+ tError('failedDeletingScheduledTask'),
91
+ );
92
+ console.error('Failed to delete scheduled task.', mutationState.error);
93
+ }
94
+
80
95
  useEffect(() => {
81
96
  const storedConfig = getStoredTableConfig();
82
97
 
@@ -142,6 +157,22 @@ export const WfoScheduledTasksPage = () => {
142
157
  </EuiFlexGroup>
143
158
  ),
144
159
  },
160
+ deleteSchedule: {
161
+ columnType: ColumnType.CONTROL,
162
+ width: '80px',
163
+ renderControl: (taskListItem) => (
164
+ <WfoButtonWithConfirm
165
+ question={t('deleteConfirmationQuestion')}
166
+ onConfirm={() => {
167
+ deleteScheduledTask({
168
+ workflowId: taskListItem.workflowId,
169
+ scheduleId: taskListItem.id,
170
+ });
171
+ }}
172
+ ariaLabel={t('ariaLabelDeleteButton')}
173
+ />
174
+ ),
175
+ },
145
176
  };
146
177
 
147
178
  const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams;
@@ -217,6 +248,15 @@ export const WfoScheduledTasksPage = () => {
217
248
  )}
218
249
  exportDataIsLoading={isFetchingCsv}
219
250
  disableSearch={true}
251
+ extraButtons={
252
+ <EuiButton
253
+ onClick={() => {
254
+ router.push(PATH_METADATA_ADD_SCHEDULE_TASK_FORM);
255
+ }}
256
+ >
257
+ {t('addSchedule')}
258
+ </EuiButton>
259
+ }
220
260
  />
221
261
  </WfoMetadataPageLayout>
222
262
  );
@@ -1,12 +1,14 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
 
3
3
  import { useTranslations } from 'next-intl';
4
+ import { useRouter } from 'next/router';
4
5
 
5
- import { EuiBadgeGroup } from '@elastic/eui';
6
+ import { EuiBadgeGroup, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui';
6
7
 
7
8
  import {
8
9
  PATH_METADATA_PRODUCTS,
9
10
  WfoFirstPartUUID,
11
+ WfoPopover,
10
12
  WfoScheduledTasksBadgesContainer,
11
13
  WfoWorkflowTargetBadge,
12
14
  getPageIndexChangeHandler,
@@ -21,6 +23,7 @@ import {
21
23
  WfoProductBlockBadge,
22
24
  } from '@/components';
23
25
  import { getDataSortHandler, getQueryStringHandler } from '@/components';
26
+ import { PATH_METADATA_ADD_SCHEDULE_TASK_FORM } from '@/components';
24
27
  import { WfoDateTime } from '@/components/WfoDateTime/WfoDateTime';
25
28
  import { WfoMetadataDescriptionField } from '@/components/WfoMetadata/WfoMetadataDescriptionField';
26
29
  import { WfoAdvancedTable } from '@/components/WfoTable/WfoAdvancedTable';
@@ -35,6 +38,7 @@ import {
35
38
  useShowToastMessage,
36
39
  useStoredTableConfig,
37
40
  } from '@/hooks';
41
+ import { WfoDotsHorizontal } from '@/icons/WfoDotsHorizontal';
38
42
  import {
39
43
  TasksResponse,
40
44
  useGetTasksQuery,
@@ -78,10 +82,72 @@ export type TaskListExportItem = Omit<
78
82
  productTags: string;
79
83
  };
80
84
 
85
+ interface ScheduleTaskPopoverMenuProps {
86
+ workflowId: string;
87
+ }
88
+
89
+ const ScheduleTaskPopoverMenu = ({
90
+ workflowId,
91
+ }: ScheduleTaskPopoverMenuProps) => {
92
+ const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
93
+ const button = (
94
+ <EuiButtonIcon
95
+ iconType={() => <WfoDotsHorizontal />}
96
+ onClick={() => setIsPopoverOpen(!isPopoverOpen)}
97
+ aria-label="Schedule task popover menu"
98
+ isLoading={false}
99
+ />
100
+ );
101
+
102
+ return (
103
+ <WfoPopover
104
+ id="schedule-task-popover-menu"
105
+ isLoading={false}
106
+ PopoverContent={() => (
107
+ <SetScheduleButton
108
+ workflowId={workflowId}
109
+ closePopover={() => setIsPopoverOpen(false)}
110
+ />
111
+ )}
112
+ button={button}
113
+ isPopoverOpen={isPopoverOpen}
114
+ closePopover={() => setIsPopoverOpen(false)}
115
+ />
116
+ );
117
+ };
118
+
119
+ const SetScheduleButton = ({
120
+ workflowId,
121
+ closePopover,
122
+ }: {
123
+ workflowId: string;
124
+ closePopover: () => void;
125
+ }) => {
126
+ const t = useTranslations('metadata.tasks');
127
+ const router = useRouter();
128
+ return (
129
+ <EuiContextMenuItem
130
+ icon="gear"
131
+ css={{
132
+ whiteSpace: 'nowrap',
133
+ }}
134
+ onClick={() => {
135
+ closePopover();
136
+ router.push(
137
+ `${PATH_METADATA_ADD_SCHEDULE_TASK_FORM}/?workflowId=${workflowId}`,
138
+ );
139
+ }}
140
+ >
141
+ {t('addSchedule')}
142
+ </EuiContextMenuItem>
143
+ );
144
+ };
145
+
81
146
  export const WfoTasksPage = () => {
82
147
  const t = useTranslations('metadata.tasks');
83
148
  const tError = useTranslations('errors');
84
149
  const { showToastMessage } = useShowToastMessage();
150
+
85
151
  const [tableDefaults, setTableDefaults] =
86
152
  useState<StoredTableConfig<TaskListItem>>();
87
153
  const getStoredTableConfig = useStoredTableConfig<TaskListItem>(
@@ -209,6 +275,7 @@ export const WfoTasksPage = () => {
209
275
  />
210
276
  ),
211
277
  width: '80px',
278
+ isSortable: false,
212
279
  },
213
280
  createdAt: {
214
281
  columnType: ColumnType.DATA,
@@ -219,6 +286,13 @@ export const WfoTasksPage = () => {
219
286
  clipboardText: parseIsoString(parseDateToLocaleDateTimeString),
220
287
  renderTooltip: parseIsoString(parseDateToLocaleDateTimeString),
221
288
  },
289
+ addSchedule: {
290
+ columnType: ColumnType.CONTROL,
291
+ width: '80px',
292
+ renderControl: (taskListItem) => (
293
+ <ScheduleTaskPopoverMenu workflowId={taskListItem.workflowId} />
294
+ ),
295
+ },
222
296
  };
223
297
 
224
298
  const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams;
@@ -6,3 +6,4 @@ export * from './WfoTasksPage';
6
6
  export * from './WfoMetadataPageLayout';
7
7
  export * from './WfoScheduledTasksPage';
8
8
  export * from './workflowListObjectMapper';
9
+ export * from './WfoScheduleTaskFormPage';
package/src/rtk/api.ts CHANGED
@@ -182,6 +182,7 @@ export const orchestratorApi = createApi({
182
182
  CacheTagType.processes,
183
183
  CacheTagType.processStatusCounts,
184
184
  CacheTagType.subscriptions,
185
+ CacheTagType.scheduledTasks,
185
186
  ],
186
187
  keepUnusedDataFor:
187
188
  process.env.NEXT_PUBLIC_DISABLE_CACHE === 'true' ? 0 : 60 * 60 * 1000,