From 1b0413bf8bdbe53765e5b90f580f5f74a6258268 Mon Sep 17 00:00:00 2001 From: martmull Date: Wed, 12 Mar 2025 16:25:07 +0100 Subject: [PATCH] Add days schedule trigger (#10800) image --- .../validation-schemas/workflowSchema.ts | 16 +- .../WorkflowEditTriggerCronForm.tsx | 156 ++++++++++++++++++ .../constants/CronTriggerIntervalOptions.ts | 8 +- .../utils/getCronTriggerDefaultSettings.ts | 10 ++ .../utils/getTriggerDefaultDefinition.ts | 4 +- .../types/workflow-trigger.type.ts | 4 + ...compute-cron-pattern-from-schedule.spec.ts | 14 ++ .../assert-version-can-be-activated.util.ts | 29 ++++ .../compute-cron-pattern-from-schedule.ts | 7 + .../display/icon/components/TablerIcons.ts | 1 + 10 files changed, 244 insertions(+), 5 deletions(-) diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts index 13eaa986b..4373e0aa3 100644 --- a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -150,14 +150,26 @@ export const workflowManualTriggerSchema = baseTriggerSchema.extend({ export const workflowCronTriggerSchema = baseTriggerSchema.extend({ type: z.literal('CRON'), settings: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('DAYS'), + schedule: z.object({ + day: z.number().min(1), + hour: z.number().min(0).max(23), + minute: z.number().min(0).max(59), + }), + outputSchema: z.object({}).passthrough(), + }), z.object({ type: z.literal('HOURS'), - schedule: z.object({ hour: z.number(), minute: z.number() }), + schedule: z.object({ + hour: z.number().min(1), + minute: z.number().min(0).max(59), + }), outputSchema: z.object({}).passthrough(), }), z.object({ type: z.literal('MINUTES'), - schedule: z.object({ minute: z.number() }), + schedule: z.object({ minute: z.number().min(1) }), outputSchema: z.object({}).passthrough(), }), z.object({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx index 9612ddd80..6200621d9 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx @@ -30,6 +30,9 @@ type WorkflowEditTriggerCronFormProps = { type FormErrorMessages = { CUSTOM?: string | undefined; + DAYS_day?: string | undefined; + DAYS_hour?: string | undefined; + DAYS_minute?: string | undefined; HOURS_hour?: string | undefined; HOURS_minute?: string | undefined; MINUTES?: string | undefined; @@ -146,6 +149,159 @@ export const WorkflowEditTriggerCronForm = ({ }} /> )} + {trigger.settings.type === 'DAYS' && ( + <> + { + if (triggerOptions.readonly === true) { + return; + } + + if (!isDefined(newDay)) { + return; + } + + if (!isNumber(newDay) || newDay <= 0) { + setErrorMessages((prev) => ({ + ...prev, + DAYS_day: `Invalid day value '${newDay}'. Should be integer greater than 1`, + })); + return; + } + + setErrorMessages((prev) => ({ + ...prev, + DAYS_day: undefined, + })); + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + type: 'DAYS', + schedule: { + day: newDay, + hour: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.hour + : 0, + minute: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.minute + : 0, + }, + }, + }); + }} + placeholder="Enter number greater than 1" + readonly={triggerOptions.readonly} + /> + { + if (triggerOptions.readonly === true) { + return; + } + + if (!isDefined(newHour)) { + return; + } + + if (!isNumber(newHour) || newHour < 0 || newHour > 23) { + setErrorMessages((prev) => ({ + ...prev, + DAYS_hour: `Invalid hour value '${newHour}'. Should be integer between 0 and 23`, + })); + return; + } + + setErrorMessages((prev) => ({ + ...prev, + DAYS_hour: undefined, + })); + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + type: 'DAYS', + schedule: { + day: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.day + : 1, + hour: newHour, + minute: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.minute + : 0, + }, + }, + }); + }} + placeholder="Enter number between 0 and 23" + readonly={triggerOptions.readonly} + /> + { + if (triggerOptions.readonly === true) { + return; + } + + if (!isDefined(newMinute)) { + return; + } + + if (!isNumber(newMinute) || newMinute < 0 || newMinute > 59) { + setErrorMessages((prev) => ({ + ...prev, + DAYS_minute: `Invalid minute value '${newMinute}'. Should be integer between 0 and 59`, + })); + return; + } + + setErrorMessages((prev) => ({ + ...prev, + DAYS_minute: undefined, + })); + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + type: 'DAYS', + schedule: { + day: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.day + : 1, + hour: + trigger.settings.type === 'DAYS' + ? trigger.settings.schedule.hour + : 0, + minute: newMinute, + }, + }, + }); + }} + placeholder="Enter number between 0 and 59" + readonly={triggerOptions.readonly} + /> + + )} {trigger.settings.type === 'HOURS' && ( <> = [ + { + label: 'Days', + value: 'DAYS', + Icon: IconBrandDaysCounter, + }, { label: 'Hours', value: 'HOURS', diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings.ts index cad9ecf59..b6ece559d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings.ts @@ -8,6 +8,16 @@ export const getCronTriggerDefaultSettings = ( cronTriggerInterval: CronTriggerInterval, ): WorkflowCronTrigger['settings'] => { switch (cronTriggerInterval) { + case 'DAYS': + return { + schedule: { + day: 1, + hour: 0, + minute: 0, + }, + type: cronTriggerInterval, + outputSchema: {}, + }; case 'HOURS': return { schedule: { diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerDefaultDefinition.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerDefaultDefinition.ts index ea40c660e..be2987fb7 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerDefaultDefinition.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerDefaultDefinition.ts @@ -52,8 +52,8 @@ export const getTriggerDefaultDefinition = ({ type, name: defaultLabel, settings: { - type: 'HOURS', - schedule: { hour: 1, minute: 0 }, + type: 'DAYS', + schedule: { day: 1, hour: 0, minute: 0 }, outputSchema: {}, }, }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts index 227cddea9..10764bdce 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/types/workflow-trigger.type.ts @@ -39,6 +39,10 @@ export type WorkflowManualTrigger = BaseTrigger & { export type WorkflowCronTrigger = BaseTrigger & { type: WorkflowTriggerType.CRON; settings: ( + | { + type: 'DAYS'; + schedule: { day: number; hour: number; minute: number }; + } | { type: 'HOURS'; schedule: { hour: number; minute: number }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/compute-cron-pattern-from-schedule.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/compute-cron-pattern-from-schedule.spec.ts index ff2aebf23..70659a98f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/compute-cron-pattern-from-schedule.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/compute-cron-pattern-from-schedule.spec.ts @@ -39,6 +39,20 @@ describe('computeCronPatternFromSchedule', () => { ); }); + it('should return the correct cron pattern for DAYS type', () => { + const trigger: WorkflowCronTrigger = { + name: '', + type: WorkflowTriggerType.CRON, + settings: { + type: 'DAYS', + schedule: { day: 31, hour: 10, minute: 30 }, + outputSchema: {}, + }, + }; + + expect(computeCronPatternFromSchedule(trigger)).toBe('30 10 */31 * *'); + }); + it('should return the correct cron pattern for HOURS type', () => { const trigger: WorkflowCronTrigger = { name: '', diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts index 12b421e24..1c51b7a4d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts @@ -100,6 +100,35 @@ function assertCronTriggerSettingsAreValid(settings: any) { return; } + case 'DAYS': { + if (!settings.schedule) { + throw new WorkflowTriggerException( + 'No schedule provided in cron trigger', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, + ); + } + if (settings.schedule.day <= 0) { + throw new WorkflowTriggerException( + 'Invalid day value. Should be integer greater than 1', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, + ); + } + if (settings.schedule.hour < 0 || settings.schedule.hour > 23) { + throw new WorkflowTriggerException( + 'Invalid hour value. Should be integer between 0 and 23', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, + ); + } + if (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, + ); + } + + return; + } + case 'HOURS': { if (!settings.schedule) { throw new WorkflowTriggerException( diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts index 376950f88..77c23d991 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule.ts @@ -26,6 +26,13 @@ export const computeCronPatternFromSchedule = ( return trigger.settings.pattern; } + case 'DAYS': { + const pattern = `${trigger.settings.schedule.minute} ${trigger.settings.schedule.hour} */${trigger.settings.schedule.day} * *`; + + validatePattern(pattern); + + return pattern; + } case 'HOURS': { const pattern = `${trigger.settings.schedule.minute} */${trigger.settings.schedule.hour} * * *`; diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 82b9fc64d..5f136bbe7 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -27,6 +27,7 @@ export { IconBrackets, IconBracketsAngle, IconBracketsContain, + IconBrandDaysCounter, IconBrandGithub, IconBrandGoogle, IconBrandGraphql,