360 workflow implement workflow cron triggers frontend 2 (#10051)
as title, closes https://github.com/twentyhq/core-team-issues/issues/360 ## Cron Setting behavior https://github.com/user-attachments/assets/0de3a8b9-d899-4455-a945-20c7541c3053 ## Cron running behavior https://github.com/user-attachments/assets/4c33f167-857c-4fcb-9dbe-0f9b661c9e61
This commit is contained in:
@ -59,6 +59,9 @@ export class WorkflowBuilderWorkspaceService {
|
||||
objectMetadataRepository: this.objectMetadataRepository,
|
||||
});
|
||||
}
|
||||
case WorkflowTriggerType.CRON: {
|
||||
return {};
|
||||
}
|
||||
case WorkflowActionType.SEND_EMAIL: {
|
||||
return this.computeSendEmailActionOutputSchema();
|
||||
}
|
||||
|
||||
@ -38,9 +38,20 @@ export type WorkflowManualTrigger = BaseTrigger & {
|
||||
|
||||
export type WorkflowCronTrigger = BaseTrigger & {
|
||||
type: WorkflowTriggerType.CRON;
|
||||
settings: {
|
||||
pattern: string;
|
||||
};
|
||||
settings: (
|
||||
| {
|
||||
type: 'HOURS';
|
||||
schedule: { hour: number; minute: number };
|
||||
}
|
||||
| {
|
||||
type: 'MINUTES';
|
||||
schedule: { minute: number };
|
||||
}
|
||||
| {
|
||||
type: 'CUSTOM';
|
||||
pattern: string;
|
||||
}
|
||||
) & { outputSchema: object };
|
||||
};
|
||||
|
||||
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
import {
|
||||
WorkflowCronTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import { WorkflowTriggerException } from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
|
||||
|
||||
describe('computeCronPatternFromSchedule', () => {
|
||||
it('should return the pattern for CUSTOM type', () => {
|
||||
const trigger: WorkflowCronTrigger = {
|
||||
name: '',
|
||||
type: WorkflowTriggerType.CRON,
|
||||
settings: {
|
||||
type: 'CUSTOM',
|
||||
pattern: '12 * * * *',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(computeCronPatternFromSchedule(trigger)).toBe('12 * * * *');
|
||||
});
|
||||
|
||||
it('should throw an exception for unsupported pattern for CUSTOM type', () => {
|
||||
const trigger: WorkflowCronTrigger = {
|
||||
name: '',
|
||||
type: WorkflowTriggerType.CRON,
|
||||
settings: {
|
||||
type: 'CUSTOM',
|
||||
pattern: '0 12 * * * *',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => computeCronPatternFromSchedule(trigger)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => computeCronPatternFromSchedule(trigger)).toThrow(
|
||||
"Cron pattern '0 12 * * * *' is invalid",
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the correct cron pattern for HOURS type', () => {
|
||||
const trigger: WorkflowCronTrigger = {
|
||||
name: '',
|
||||
type: WorkflowTriggerType.CRON,
|
||||
settings: {
|
||||
type: 'HOURS',
|
||||
schedule: { hour: 10, minute: 30 },
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(computeCronPatternFromSchedule(trigger)).toBe('30 */10 * * *');
|
||||
});
|
||||
|
||||
it('should return the correct cron pattern for MINUTES type', () => {
|
||||
const trigger: WorkflowCronTrigger = {
|
||||
name: '',
|
||||
type: WorkflowTriggerType.CRON,
|
||||
settings: {
|
||||
type: 'MINUTES',
|
||||
schedule: { minute: 15 },
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(computeCronPatternFromSchedule(trigger)).toBe('*/15 * * * *');
|
||||
});
|
||||
|
||||
it('should throw an exception for unsupported schedule type', () => {
|
||||
const trigger: WorkflowCronTrigger = {
|
||||
name: '',
|
||||
type: WorkflowTriggerType.CRON,
|
||||
settings: {
|
||||
type: 'INVALID_TYPE' as any,
|
||||
pattern: '',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => computeCronPatternFromSchedule(trigger)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => computeCronPatternFromSchedule(trigger)).toThrow(
|
||||
'Unsupported cron schedule type',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -70,6 +70,9 @@ function assertTriggerSettingsAreValid(
|
||||
break;
|
||||
case WorkflowTriggerType.MANUAL:
|
||||
break;
|
||||
case WorkflowTriggerType.CRON:
|
||||
assertCronTriggerSettingsAreValid(settings);
|
||||
break;
|
||||
default:
|
||||
throw new WorkflowTriggerException(
|
||||
'Invalid trigger type for enabling workflow trigger',
|
||||
@ -78,6 +81,50 @@ function assertTriggerSettingsAreValid(
|
||||
}
|
||||
}
|
||||
|
||||
function assertCronTriggerSettingsAreValid(settings: any) {
|
||||
if (!settings?.type) {
|
||||
throw new WorkflowTriggerException(
|
||||
'No setting type provided in cron trigger',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
if (settings.type === 'CUSTOM' && !settings.pattern) {
|
||||
throw new WorkflowTriggerException(
|
||||
'No pattern provided in CUSTOM cron trigger',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
if (!settings.schedule) {
|
||||
throw new WorkflowTriggerException(
|
||||
'No schedule provided in cron trigger',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
if (settings.type === 'HOURS' && settings.schedule.hour <= 0) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Invalid hour value. Should be integer greater than 1',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
settings.type === 'HOURS' &&
|
||||
(settings.schedule.minute < 0 || settings.schedule.minute > 59)
|
||||
) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Invalid minute value. Should be integer between 0 and 59',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.type === 'MINUTES' && settings.schedule.minute <= 0) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Invalid minute value. Should be integer greater than 1',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertDatabaseEventTriggerSettingsAreValid(settings: any) {
|
||||
if (!settings?.eventName) {
|
||||
throw new WorkflowTriggerException(
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import cron from 'cron-validate';
|
||||
|
||||
import { WorkflowCronTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import {
|
||||
WorkflowTriggerException,
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
|
||||
const validatePattern = (pattern: string) => {
|
||||
const cronValidator = cron(pattern);
|
||||
|
||||
if (cronValidator.isError()) {
|
||||
throw new WorkflowTriggerException(
|
||||
`Cron pattern '${pattern}' is invalid`,
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const computeCronPatternFromSchedule = (
|
||||
trigger: WorkflowCronTrigger,
|
||||
) => {
|
||||
switch (trigger.settings.type) {
|
||||
case 'CUSTOM': {
|
||||
validatePattern(trigger.settings.pattern);
|
||||
|
||||
return trigger.settings.pattern;
|
||||
}
|
||||
case 'HOURS': {
|
||||
const pattern = `${trigger.settings.schedule.minute} */${trigger.settings.schedule.hour} * * *`;
|
||||
|
||||
validatePattern(pattern);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
case 'MINUTES': {
|
||||
const pattern = `*/${trigger.settings.schedule.minute} * * * *`;
|
||||
|
||||
validatePattern(pattern);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
default:
|
||||
throw new WorkflowTriggerException(
|
||||
'Unsupported cron schedule type',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -36,6 +36,7 @@ import {
|
||||
WorkflowTriggerJob,
|
||||
WorkflowTriggerJobData,
|
||||
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job';
|
||||
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowTriggerWorkspaceService {
|
||||
@ -339,7 +340,9 @@ export class WorkflowTriggerWorkspaceService {
|
||||
return;
|
||||
case WorkflowTriggerType.MANUAL:
|
||||
return;
|
||||
case WorkflowTriggerType.CRON:
|
||||
case WorkflowTriggerType.CRON: {
|
||||
const pattern = computeCronPatternFromSchedule(workflowVersion.trigger);
|
||||
|
||||
await this.messageQueueService.addCron<WorkflowTriggerJobData>({
|
||||
jobName: WorkflowTriggerJob.name,
|
||||
jobId: workflowVersion.workflowId,
|
||||
@ -350,12 +353,13 @@ export class WorkflowTriggerWorkspaceService {
|
||||
},
|
||||
options: {
|
||||
repeat: {
|
||||
pattern: workflowVersion.trigger.settings.pattern,
|
||||
pattern,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
assertNever(workflowVersion.trigger);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user