From dc8f8b7874ec8394fed9442045e7d75fb6009d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 19 Aug 2022 12:45:04 +0200 Subject: [PATCH] [N8N-4339] Unify all Cron-specific code. Improve typing, and add tests. (#3887) --- .../cli/test/integration/shared/types.d.ts | 9 - packages/cli/test/integration/shared/utils.ts | 252 +----------- packages/core/src/ActiveWorkflows.ts | 57 +-- packages/core/src/Interfaces.ts | 9 - packages/nodes-base/nodes/Cron/Cron.node.ts | 263 +----------- packages/workflow/src/Cron.ts | 68 ++++ packages/workflow/src/NodeHelpers.ts | 374 +++++++++--------- packages/workflow/src/index.ts | 1 + packages/workflow/test/Cron.test.ts | 75 ++++ 9 files changed, 354 insertions(+), 754 deletions(-) create mode 100644 packages/workflow/src/Cron.ts create mode 100644 packages/workflow/test/Cron.test.ts diff --git a/packages/cli/test/integration/shared/types.d.ts b/packages/cli/test/integration/shared/types.d.ts index 9b45b7b93a..1e74ad548f 100644 --- a/packages/cli/test/integration/shared/types.d.ts +++ b/packages/cli/test/integration/shared/types.d.ts @@ -41,15 +41,6 @@ export type PostgresSchemaSection = { [K in 'host' | 'port' | 'schema' | 'user' | 'password']: { env: string }; }; -export interface TriggerTime { - mode: string; - hour: number; - minute: number; - dayOfMonth: number; - weekeday: number; - [key: string]: string | number; -} - export type InstalledPackagePayload = { packageName: string; installedVersion: string; diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 1e16f75f3e..eaff862f10 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -20,6 +20,9 @@ import { ITriggerFunctions, ITriggerResponse, LoggerProxy, + NodeHelpers, + TriggerTime, + toCronExpression, } from 'n8n-workflow'; import config from '../../../config'; @@ -56,7 +59,6 @@ import type { InstalledNodePayload, InstalledPackagePayload, PostgresSchemaSection, - TriggerTime, } from './types'; import type { N8nApp } from '../../../src/UserManagement/Interfaces'; import { workflowsController } from '../../../src/api/workflows.api'; @@ -281,192 +283,7 @@ export async function initNodeTypes() { default: {}, description: 'Triggers for the workflow', placeholder: 'Add Cron Time', - options: [ - { - name: 'item', - displayName: 'Item', - values: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - options: [ - { - name: 'Every Minute', - value: 'everyMinute', - }, - { - name: 'Every Hour', - value: 'everyHour', - }, - { - name: 'Every Day', - value: 'everyDay', - }, - { - name: 'Every Week', - value: 'everyWeek', - }, - { - name: 'Every Month', - value: 'everyMonth', - }, - { - name: 'Every X', - value: 'everyX', - }, - { - name: 'Custom', - value: 'custom', - }, - ], - default: 'everyDay', - description: 'How often to trigger.', - }, - { - displayName: 'Hour', - name: 'hour', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 23, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyHour', 'everyMinute', 'everyX'], - }, - }, - default: 14, - description: 'The hour of the day to trigger (24h format).', - }, - { - displayName: 'Minute', - name: 'minute', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 59, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyMinute', 'everyX'], - }, - }, - default: 0, - description: 'The minute of the day to trigger.', - }, - { - displayName: 'Day of Month', - name: 'dayOfMonth', - type: 'number', - displayOptions: { - show: { - mode: ['everyMonth'], - }, - }, - typeOptions: { - minValue: 1, - maxValue: 31, - }, - default: 1, - description: 'The day of the month to trigger.', - }, - { - displayName: 'Weekday', - name: 'weekday', - type: 'options', - displayOptions: { - show: { - mode: ['everyWeek'], - }, - }, - options: [ - { - name: 'Monday', - value: '1', - }, - { - name: 'Tuesday', - value: '2', - }, - { - name: 'Wednesday', - value: '3', - }, - { - name: 'Thursday', - value: '4', - }, - { - name: 'Friday', - value: '5', - }, - { - name: 'Saturday', - value: '6', - }, - { - name: 'Sunday', - value: '0', - }, - ], - default: '1', - description: 'The weekday to trigger.', - }, - { - displayName: 'Cron Expression', - name: 'cronExpression', - type: 'string', - displayOptions: { - show: { - mode: ['custom'], - }, - }, - default: '* * * * * *', - description: - 'Use custom cron expression. Values and ranges as follows:.', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 1000, - }, - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - default: 2, - description: 'All how many X minutes/hours it should trigger.', - }, - { - displayName: 'Unit', - name: 'unit', - type: 'options', - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - options: [ - { - name: 'Minutes', - value: 'minutes', - }, - { - name: 'Hours', - value: 'hours', - }, - ], - default: 'hours', - description: 'If it should trigger all X minutes or hours.', - }, - ], - }, - ], + options: NodeHelpers.cronNodeOptions, }, ], }, @@ -475,61 +292,8 @@ export async function initNodeTypes() { item: TriggerTime[]; }; - // Define the order the cron-time-parameter appear - const parameterOrder = [ - 'second', // 0 - 59 - 'minute', // 0 - 59 - 'hour', // 0 - 23 - 'dayOfMonth', // 1 - 31 - 'month', // 0 - 11(Jan - Dec) - 'weekday', // 0 - 6(Sun - Sat) - ]; - // Get all the trigger times - const cronTimes: string[] = []; - let cronTime: string[]; - let parameterName: string; - if (triggerTimes.item !== undefined) { - for (const item of triggerTimes.item) { - cronTime = []; - if (item.mode === 'custom') { - cronTimes.push(item.cronExpression as string); - continue; - } - if (item.mode === 'everyMinute') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`); - continue; - } - if (item.mode === 'everyX') { - if (item.unit === 'minutes') { - cronTimes.push( - `${Math.floor(Math.random() * 60).toString()} */${item.value} * * * *`, - ); - } else if (item.unit === 'hours') { - cronTimes.push( - `${Math.floor(Math.random() * 60).toString()} 0 */${item.value} * * *`, - ); - } - continue; - } - - for (parameterName of parameterOrder) { - if (item[parameterName] !== undefined) { - // Value is set so use it - cronTime.push(item[parameterName] as string); - } else if (parameterName === 'second') { - // For seconds we use by default a random one to make sure to - // balance the load a little bit over time - cronTime.push(Math.floor(Math.random() * 60).toString()); - } else { - // For all others set "any" - cronTime.push('*'); - } - } - - cronTimes.push(cronTime.join(' ')); - } - } + const cronTimes = (triggerTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered @@ -540,10 +304,7 @@ export async function initNodeTypes() { const timezone = this.getTimezone(); // Start the cron-jobs - const cronJobs: CronJob[] = []; - for (const cronTime of cronTimes) { - cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone)); - } + const cronJobs = cronTimes.map(cronTime => new CronJob(cronTime, executeTrigger, undefined, true, timezone)); // Stop the cron-jobs async function closeFunction() { @@ -929,4 +690,3 @@ export const emptyPackage = () => { return Promise.resolve(installedPackage); }; - diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index 4ea94b0a2e..01271de8fb 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -12,6 +12,8 @@ import { ITriggerResponse, IWorkflowExecuteAdditionalData, LoggerProxy as Logger, + TriggerTime, + toCronExpression, Workflow, WorkflowActivateMode, WorkflowActivationError, @@ -19,7 +21,7 @@ import { } from 'n8n-workflow'; // eslint-disable-next-line import/no-cycle -import { ITriggerTime, IWorkflowData } from '.'; +import type { IWorkflowData } from '.'; export class ActiveWorkflows { private workflowData: { @@ -156,60 +158,11 @@ export class ActiveWorkflows { const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation); const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as { - item: ITriggerTime[]; + item: TriggerTime[]; }; - // Define the order the cron-time-parameter appear - const parameterOrder = [ - 'second', // 0 - 59 - 'minute', // 0 - 59 - 'hour', // 0 - 23 - 'dayOfMonth', // 1 - 31 - 'month', // 0 - 11(Jan - Dec) - 'weekday', // 0 - 6(Sun - Sat) - ]; - // Get all the trigger times - const cronTimes: string[] = []; - let cronTime: string[]; - let parameterName: string; - if (pollTimes.item !== undefined) { - for (const item of pollTimes.item) { - cronTime = []; - if (item.mode === 'custom') { - cronTimes.push((item.cronExpression as string).trim()); - continue; - } - if (item.mode === 'everyMinute') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`); - continue; - } - if (item.mode === 'everyX') { - if (item.unit === 'minutes') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} */${item.value} * * * *`); - } else if (item.unit === 'hours') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} 0 */${item.value} * * *`); - } - continue; - } - - for (parameterName of parameterOrder) { - if (item[parameterName] !== undefined) { - // Value is set so use it - cronTime.push(item[parameterName] as string); - } else if (parameterName === 'second') { - // For seconds we use by default a random one to make sure to - // balance the load a little bit over time - cronTime.push(Math.floor(Math.random() * 60).toString()); - } else { - // For all others set "any" - cronTime.push('*'); - } - } - - cronTimes.push(cronTime.join(' ')); - } - } + const cronTimes = (pollTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached const executeTrigger = async () => { diff --git a/packages/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index 1e0ad62bbe..5efda21290 100644 --- a/packages/core/src/Interfaces.ts +++ b/packages/core/src/Interfaces.ts @@ -180,15 +180,6 @@ export interface ITriggerFunctions extends ITriggerFunctionsBase { }; } -export interface ITriggerTime { - mode: string; - hour: number; - minute: number; - dayOfMonth: number; - weekeday: number; - [key: string]: string | number; -} - export interface IUserSettings { encryptionKey?: string; tunnelSubdomain?: string; diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index 5d018a1269..718d15f8f0 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -1,17 +1,15 @@ import { ITriggerFunctions } from 'n8n-core'; -import { INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow'; +import { + INodeType, + INodeTypeDescription, + ITriggerResponse, + NodeHelpers, + toCronExpression, + TriggerTime, +} from 'n8n-workflow'; import { CronJob } from 'cron'; -interface TriggerTime { - mode: string; - hour: number; - minute: number; - dayOfMonth: number; - weekeday: number; - [key: string]: string | number; -} - export class Cron implements INodeType { description: INodeTypeDescription = { displayName: 'Cron', @@ -49,194 +47,7 @@ export class Cron implements INodeType { default: {}, description: 'Triggers for the workflow', placeholder: 'Add Cron Time', - options: [ - { - name: 'item', - displayName: 'Item', - values: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items - options: [ - { - name: 'Every Minute', - value: 'everyMinute', - }, - { - name: 'Every Hour', - value: 'everyHour', - }, - { - name: 'Every Day', - value: 'everyDay', - }, - { - name: 'Every Week', - value: 'everyWeek', - }, - { - name: 'Every Month', - value: 'everyMonth', - }, - { - name: 'Every X', - value: 'everyX', - }, - { - name: 'Custom', - value: 'custom', - }, - ], - default: 'everyDay', - description: 'How often to trigger', - }, - { - displayName: 'Hour', - name: 'hour', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 23, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyHour', 'everyMinute', 'everyX'], - }, - }, - default: 14, - description: 'The hour of the day to trigger (24h format)', - }, - { - displayName: 'Minute', - name: 'minute', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 59, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyMinute', 'everyX'], - }, - }, - default: 0, - description: 'The minute of the day to trigger', - }, - { - displayName: 'Day of Month', - name: 'dayOfMonth', - type: 'number', - displayOptions: { - show: { - mode: ['everyMonth'], - }, - }, - typeOptions: { - minValue: 1, - maxValue: 31, - }, - default: 1, - description: 'The day of the month to trigger', - }, - { - displayName: 'Weekday', - name: 'weekday', - type: 'options', - displayOptions: { - show: { - mode: ['everyWeek'], - }, - }, - // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items - options: [ - { - name: 'Monday', - value: '1', - }, - { - name: 'Tuesday', - value: '2', - }, - { - name: 'Wednesday', - value: '3', - }, - { - name: 'Thursday', - value: '4', - }, - { - name: 'Friday', - value: '5', - }, - { - name: 'Saturday', - value: '6', - }, - { - name: 'Sunday', - value: '0', - }, - ], - default: '1', - description: 'The weekday to trigger', - }, - { - displayName: 'Cron Expression', - name: 'cronExpression', - type: 'string', - displayOptions: { - show: { - mode: ['custom'], - }, - }, - default: '* * * * * *', - description: - 'Use custom cron expression. Values and ranges as follows:.', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 1000, - }, - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - default: 2, - description: 'All how many X minutes/hours it should trigger', - }, - { - displayName: 'Unit', - name: 'unit', - type: 'options', - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - options: [ - { - name: 'Minutes', - value: 'minutes', - }, - { - name: 'Hours', - value: 'hours', - }, - ], - default: 'hours', - description: 'If it should trigger all X minutes or hours', - }, - ], - }, - ], + options: NodeHelpers.cronNodeOptions, }, ], }; @@ -246,57 +57,8 @@ export class Cron implements INodeType { item: TriggerTime[]; }; - // Define the order the cron-time-parameter appear - const parameterOrder = [ - 'second', // 0 - 59 - 'minute', // 0 - 59 - 'hour', // 0 - 23 - 'dayOfMonth', // 1 - 31 - 'month', // 0 - 11(Jan - Dec) - 'weekday', // 0 - 6(Sun - Sat) - ]; - // Get all the trigger times - const cronTimes: string[] = []; - let cronTime: string[]; - let parameterName: string; - if (triggerTimes.item !== undefined) { - for (const item of triggerTimes.item) { - cronTime = []; - if (item.mode === 'custom') { - cronTimes.push(item.cronExpression as string); - continue; - } - if (item.mode === 'everyMinute') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`); - continue; - } - if (item.mode === 'everyX') { - if (item.unit === 'minutes') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} */${item.value} * * * *`); - } else if (item.unit === 'hours') { - cronTimes.push(`${Math.floor(Math.random() * 60).toString()} 0 */${item.value} * * *`); - } - continue; - } - - for (parameterName of parameterOrder) { - if (item[parameterName] !== undefined) { - // Value is set so use it - cronTime.push(item[parameterName] as string); - } else if (parameterName === 'second') { - // For seconds we use by default a random one to make sure to - // balance the load a little bit over time - cronTime.push(Math.floor(Math.random() * 60).toString()); - } else { - // For all others set "any" - cronTime.push('*'); - } - } - - cronTimes.push(cronTime.join(' ')); - } - } + const cronTimes = (triggerTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered @@ -307,10 +69,7 @@ export class Cron implements INodeType { const timezone = this.getTimezone(); // Start the cron-jobs - const cronJobs: CronJob[] = []; - for (const cronTime of cronTimes) { - cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone)); - } + const cronJobs = cronTimes.map(cronTime => new CronJob(cronTime, executeTrigger, undefined, true, timezone)); // Stop the cron-jobs async function closeFunction() { diff --git a/packages/workflow/src/Cron.ts b/packages/workflow/src/Cron.ts new file mode 100644 index 0000000000..fb1446fa04 --- /dev/null +++ b/packages/workflow/src/Cron.ts @@ -0,0 +1,68 @@ +interface BaseTriggerTime { + mode: T; +} + +type CronExpression = string; +interface CustomTrigger extends BaseTriggerTime<'custom'> { + cronExpression: CronExpression; +} + +interface EveryX extends BaseTriggerTime<'everyX'> { + unit: U; + value: number; +} + +type EveryMinute = BaseTriggerTime<'everyMinute'>; +type EveryXMinutes = EveryX<'minutes'>; + +interface EveryHour extends BaseTriggerTime<'everyHour'> { + minute: number; // 0 - 59 +} +type EveryXHours = EveryX<'hours'>; + +interface EveryDay extends BaseTriggerTime<'everyDay'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 +} + +interface EveryWeek extends BaseTriggerTime<'everyWeek'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 + weekday: number; // 0 - 6(Sun - Sat) +} + +interface EveryMonth extends BaseTriggerTime<'everyMonth'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 + dayOfMonth: number; // 1 - 31 +} + +export type TriggerTime = + | CustomTrigger + | EveryMinute + | EveryXMinutes + | EveryHour + | EveryXHours + | EveryDay + | EveryWeek + | EveryMonth; + +const randomSecond = () => Math.floor(Math.random() * 60).toString(); + +export const toCronExpression = (item: TriggerTime): CronExpression => { + if (item.mode === 'everyMinute') return `${randomSecond()} * * * * *`; + if (item.mode === 'everyHour') return `${randomSecond()} ${item.minute} * * * *`; + + if (item.mode === 'everyX') { + if (item.unit === 'minutes') return `${randomSecond()} */${item.value} * * * *`; + if (item.unit === 'hours') return `${randomSecond()} 0 */${item.value} * * *`; + } + if (item.mode === 'everyDay') return `${randomSecond()} ${item.minute} ${item.hour} * * *`; + if (item.mode === 'everyWeek') + return `${randomSecond()} ${item.minute} ${item.hour} * * ${item.weekday}`; + + if (item.mode === 'everyMonth') + return `${randomSecond()} ${item.minute} ${item.hour} ${item.dayOfMonth} * *`; + + return item.cronExpression.trim(); +}; diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index e55da58148..63d20bad05 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -38,6 +38,193 @@ import { import { Workflow } from './Workflow'; +export const cronNodeOptions: INodePropertyCollection[] = [ + { + name: 'item', + displayName: 'Item', + values: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + options: [ + { + name: 'Every Minute', + value: 'everyMinute', + }, + { + name: 'Every Hour', + value: 'everyHour', + }, + { + name: 'Every Day', + value: 'everyDay', + }, + { + name: 'Every Week', + value: 'everyWeek', + }, + { + name: 'Every Month', + value: 'everyMonth', + }, + { + name: 'Every X', + value: 'everyX', + }, + { + name: 'Custom', + value: 'custom', + }, + ], + default: 'everyDay', + description: 'How often to trigger.', + }, + { + displayName: 'Hour', + name: 'hour', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 23, + }, + displayOptions: { + hide: { + mode: ['custom', 'everyHour', 'everyMinute', 'everyX'], + }, + }, + default: 14, + description: 'The hour of the day to trigger (24h format)', + }, + { + displayName: 'Minute', + name: 'minute', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 59, + }, + displayOptions: { + hide: { + mode: ['custom', 'everyMinute', 'everyX'], + }, + }, + default: 0, + description: 'The minute of the day to trigger', + }, + { + displayName: 'Day of Month', + name: 'dayOfMonth', + type: 'number', + displayOptions: { + show: { + mode: ['everyMonth'], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 31, + }, + default: 1, + description: 'The day of the month to trigger', + }, + { + displayName: 'Weekday', + name: 'weekday', + type: 'options', + displayOptions: { + show: { + mode: ['everyWeek'], + }, + }, + options: [ + { + name: 'Monday', + value: '1', + }, + { + name: 'Tuesday', + value: '2', + }, + { + name: 'Wednesday', + value: '3', + }, + { + name: 'Thursday', + value: '4', + }, + { + name: 'Friday', + value: '5', + }, + { + name: 'Saturday', + value: '6', + }, + { + name: 'Sunday', + value: '0', + }, + ], + default: '1', + description: 'The weekday to trigger', + }, + { + displayName: 'Cron Expression', + name: 'cronExpression', + type: 'string', + displayOptions: { + show: { + mode: ['custom'], + }, + }, + default: '* * * * * *', + description: + 'Use custom cron expression. Values and ranges as follows:
  • Seconds: 0-59
  • Minutes: 0 - 59
  • Hours: 0 - 23
  • Day of Month: 1 - 31
  • Months: 0 - 11 (Jan - Dec)
  • Day of Week: 0 - 6 (Sun - Sat)
', + }, + { + displayName: 'Value', + name: 'value', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 1000, + }, + displayOptions: { + show: { + mode: ['everyX'], + }, + }, + default: 2, + description: 'All how many X minutes/hours it should trigger', + }, + { + displayName: 'Unit', + name: 'unit', + type: 'options', + displayOptions: { + show: { + mode: ['everyX'], + }, + }, + options: [ + { + name: 'Minutes', + value: 'minutes', + }, + { + name: 'Hours', + value: 'hours', + }, + ], + default: 'hours', + description: 'If it should trigger all X minutes or hours', + }, + ], + }, +]; + /** * Gets special parameters which should be added to nodeTypes depending * on their type or configuration @@ -60,192 +247,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[] default: { item: [{ mode: 'everyMinute' }] }, description: 'Time at which polling should occur', placeholder: 'Add Poll Time', - options: [ - { - name: 'item', - displayName: 'Item', - values: [ - { - displayName: 'Mode', - name: 'mode', - type: 'options', - options: [ - { - name: 'Every Minute', - value: 'everyMinute', - }, - { - name: 'Every Hour', - value: 'everyHour', - }, - { - name: 'Every Day', - value: 'everyDay', - }, - { - name: 'Every Week', - value: 'everyWeek', - }, - { - name: 'Every Month', - value: 'everyMonth', - }, - { - name: 'Every X', - value: 'everyX', - }, - { - name: 'Custom', - value: 'custom', - }, - ], - default: 'everyDay', - description: 'How often to trigger.', - }, - { - displayName: 'Hour', - name: 'hour', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 23, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyHour', 'everyMinute', 'everyX'], - }, - }, - default: 14, - description: 'The hour of the day to trigger (24h format)', - }, - { - displayName: 'Minute', - name: 'minute', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 59, - }, - displayOptions: { - hide: { - mode: ['custom', 'everyMinute', 'everyX'], - }, - }, - default: 0, - description: 'The minute of the day to trigger', - }, - { - displayName: 'Day of Month', - name: 'dayOfMonth', - type: 'number', - displayOptions: { - show: { - mode: ['everyMonth'], - }, - }, - typeOptions: { - minValue: 1, - maxValue: 31, - }, - default: 1, - description: 'The day of the month to trigger', - }, - { - displayName: 'Weekday', - name: 'weekday', - type: 'options', - displayOptions: { - show: { - mode: ['everyWeek'], - }, - }, - options: [ - { - name: 'Monday', - value: '1', - }, - { - name: 'Tuesday', - value: '2', - }, - { - name: 'Wednesday', - value: '3', - }, - { - name: 'Thursday', - value: '4', - }, - { - name: 'Friday', - value: '5', - }, - { - name: 'Saturday', - value: '6', - }, - { - name: 'Sunday', - value: '0', - }, - ], - default: '1', - description: 'The weekday to trigger', - }, - { - displayName: 'Cron Expression', - name: 'cronExpression', - type: 'string', - displayOptions: { - show: { - mode: ['custom'], - }, - }, - default: '* * * * * *', - description: - 'Use custom cron expression. Values and ranges as follows:
  • Seconds: 0-59
  • Minutes: 0 - 59
  • Hours: 0 - 23
  • Day of Month: 1 - 31
  • Months: 0 - 11 (Jan - Dec)
  • Day of Week: 0 - 6 (Sun - Sat)
', - }, - { - displayName: 'Value', - name: 'value', - type: 'number', - typeOptions: { - minValue: 0, - maxValue: 1000, - }, - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - default: 2, - description: 'All how many X minutes/hours it should trigger', - }, - { - displayName: 'Unit', - name: 'unit', - type: 'options', - displayOptions: { - show: { - mode: ['everyX'], - }, - }, - options: [ - { - name: 'Minutes', - value: 'minutes', - }, - { - name: 'Hours', - value: 'hours', - }, - ], - default: 'hours', - description: 'If it should trigger all X minutes or hours', - }, - ], - }, - ], + options: cronNodeOptions, }, ]; } diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 5ae90bce09..a006e9d9d6 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -3,6 +3,7 @@ import * as LoggerProxy from './LoggerProxy'; import * as NodeHelpers from './NodeHelpers'; import * as ObservableObject from './ObservableObject'; +export * from './Cron'; export * from './DeferredPromise'; export * from './Interfaces'; export * from './Expression'; diff --git a/packages/workflow/test/Cron.test.ts b/packages/workflow/test/Cron.test.ts new file mode 100644 index 0000000000..43c122ee96 --- /dev/null +++ b/packages/workflow/test/Cron.test.ts @@ -0,0 +1,75 @@ +import { toCronExpression } from "../src/Cron" + +describe('Cron', () => { + describe('toCronExpression', () => { + test('should generate a valid cron for `everyMinute` triggers', () => { + const expression = toCronExpression({ + mode: 'everyMinute', + }) + expect(expression).toMatch(/^[1-6]?[0-9] \* \* \* \* \*$/) + }) + + test('should generate a valid cron for `everyHour` triggers', () => { + const expression = toCronExpression({ + mode: 'everyHour', + minute: 11, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 11 \* \* \* \*$/) + }) + + test('should generate a valid cron for `everyX[minutes]` triggers', () => { + const expression = toCronExpression({ + mode: 'everyX', + unit: 'minutes', + value: 42, + }) + expect(expression).toMatch(/^[1-6]?[0-9] \*\/42 \* \* \* \*$/) + }) + + test('should generate a valid cron for `everyX[hours]` triggers', () => { + const expression = toCronExpression({ + mode: 'everyX', + unit: 'hours', + value: 3, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 0 \*\/3 \* \* \*$/) + }) + + test('should generate a valid cron for `everyDay` triggers', () => { + const expression = toCronExpression({ + mode: 'everyDay', + hour: 13, + minute: 17, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 17 13 \* \* \*$/) + }) + + test('should generate a valid cron for `everyWeek` triggers', () => { + const expression = toCronExpression({ + mode: 'everyWeek', + hour: 13, + minute: 17, + weekday: 4, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 17 13 \* \* 4$/) + }) + + test('should generate a valid cron for `everyMonth` triggers', () => { + const expression = toCronExpression({ + mode: 'everyMonth', + hour: 13, + minute: 17, + dayOfMonth: 12, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 17 13 12 \* \*$/) + }) + + test('should trim custom cron expressions', () => { + const expression = toCronExpression({ + mode: 'custom', + cronExpression: ' 0 9-17 * * * ', + }) + expect(expression).toEqual('0 9-17 * * *') + }) + }) +})