@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.
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +3 -6
- package/.turbo/turbo-test.log +8 -8
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1542 -305
- package/dist/index.js +10313 -9607
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/WfoButtonWithConfirm/WfoButtonWithConfirm.tsx +35 -0
- package/src/components/WfoButtonWithConfirm/index.ts +1 -0
- package/src/components/WfoPageTemplate/WfoPageTemplate/WfoPageTemplate.tsx +3 -5
- package/src/components/WfoPageTemplate/paths.ts +2 -0
- package/src/components/WfoPopover/WfoPopover.tsx +43 -0
- package/src/components/WfoPopover/index.ts +1 -0
- package/src/components/WfoPydanticForm/Footer.tsx +3 -3
- package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +10 -285
- package/src/components/WfoPydanticForm/fields/WfoTimestampField.tsx +67 -0
- package/src/components/WfoPydanticForm/fields/index.ts +1 -0
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +10 -27
- package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +3 -0
- package/src/components/WfoWorkflowSteps/WfoStep/WfoStepForm.tsx +12 -21
- package/src/components/index.ts +2 -0
- package/src/configuration/constants.ts +1 -1
- package/src/configuration/version.ts +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useGetPydanticFormsConfig.tsx +324 -0
- package/src/messages/en-GB.json +27 -3
- package/src/messages/nl-NL.json +28 -4
- package/src/pages/metadata/WfoScheduleTaskFormPage.tsx +464 -0
- package/src/pages/metadata/WfoScheduledTasksPage.tsx +43 -3
- package/src/pages/metadata/WfoTasksPage.tsx +75 -1
- package/src/pages/metadata/index.ts +1 -0
- package/src/rtk/api.ts +1 -0
- package/src/rtk/endpoints/metadata/scheduledTasks.ts +131 -3
- package/src/rtk/endpoints/metadata/tasks.ts +2 -0
- 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;
|
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,
|