From f893e59d7b64fb986a9d3cfe96f82f0528ce647b 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: Tue, 16 Aug 2022 13:49:30 +0200 Subject: [PATCH 1/3] [N8N-4339] Unify all Cron-specific code. Improve typing, and add tests. --- .../cli/test/integration/shared/types.d.ts | 9 - packages/cli/test/integration/shared/utils.ts | 252 +----------- packages/core/src/ActiveWorkflows.ts | 56 +-- packages/core/src/Cron.ts | 68 ++++ packages/core/src/Interfaces.ts | 9 - packages/core/src/index.ts | 1 + packages/core/test/Cron.test.ts | 75 ++++ packages/nodes-base/nodes/Cron/Cron.node.ts | 258 +----------- packages/workflow/src/NodeHelpers.ts | 374 +++++++++--------- 9 files changed, 346 insertions(+), 756 deletions(-) create mode 100644 packages/core/src/Cron.ts create mode 100644 packages/core/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 9b45b7b93a6af..1e74ad548fab8 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 1e16f75f3ef22..64cba61954c8b 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -8,7 +8,7 @@ import { URL } from 'url'; import bodyParser from 'body-parser'; import { set } from 'lodash'; import { CronJob } from 'cron'; -import { BinaryDataManager, UserSettings } from 'n8n-core'; +import { BinaryDataManager, TriggerTime, triggerToCronExpression, UserSettings } from 'n8n-core'; import { ICredentialType, IDataObject, @@ -20,6 +20,7 @@ import { ITriggerFunctions, ITriggerResponse, LoggerProxy, + NodeHelpers, } from 'n8n-workflow'; import config from '../../../config'; @@ -56,7 +57,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 +281,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 +290,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(triggerToCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered @@ -540,10 +302,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 +688,3 @@ export const emptyPackage = () => { return Promise.resolve(installedPackage); }; - diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index 4ea94b0a2efdb..ff591d41b790c 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -18,8 +18,9 @@ import { WorkflowExecuteMode, } from 'n8n-workflow'; +import { TriggerTime, triggerToCronExpression } from './Cron'; // eslint-disable-next-line import/no-cycle -import { ITriggerTime, IWorkflowData } from '.'; +import { IWorkflowData } from '.'; export class ActiveWorkflows { private workflowData: { @@ -156,60 +157,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(triggerToCronExpression); // The trigger function to execute when the cron-time got reached const executeTrigger = async () => { diff --git a/packages/core/src/Cron.ts b/packages/core/src/Cron.ts new file mode 100644 index 0000000000000..b3b1df8937dd8 --- /dev/null +++ b/packages/core/src/Cron.ts @@ -0,0 +1,68 @@ +interface BaseTriggerTime { + mode: T; +} + +type CronExpression = string; +interface CustomTrigger extends BaseTriggerTime<'custom'> { + cronExpression: CronExpression; +} + +interface EveryXTrigger extends BaseTriggerTime<'everyX'> { + unit: U; + value: number; +} + +type EveryMinuteTrigger = BaseTriggerTime<'everyMinute'>; +type EveryXMinutesTrigger = EveryXTrigger<'minutes'>; + +interface EveryHourTrigger extends BaseTriggerTime<'everyHour'> { + minute: number; // 0 - 59 +} +type EveryXHoursTrigger = EveryXTrigger<'hours'>; + +interface EveryDayTrigger extends BaseTriggerTime<'everyDay'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 +} + +interface EveryWeekTrigger extends BaseTriggerTime<'everyWeek'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 + weekday: number; // 0 - 6(Sun - Sat) +} + +interface EveryMonthTrigger extends BaseTriggerTime<'everyMonth'> { + hour: number; // 0 - 23 + minute: number; // 0 - 59 + dayOfMonth: number; // 1 - 31 +} + +export type TriggerTime = + | CustomTrigger + | EveryMinuteTrigger + | EveryXMinutesTrigger + | EveryHourTrigger + | EveryXHoursTrigger + | EveryDayTrigger + | EveryWeekTrigger + | EveryMonthTrigger; + +const randomSecond = () => Math.floor(Math.random() * 60).toString(); + +export const triggerToCronExpression = (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/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index 1e0ad62bbeb78..5efda21290b73 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/core/src/index.ts b/packages/core/src/index.ts index 11d130d02155d..33feaebc99d1f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -13,6 +13,7 @@ export * from './ActiveWebhooks'; export * from './BinaryDataManager'; export * from './Constants'; export * from './Credentials'; +export * from './Cron'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; export * from './NodeExecuteFunctions'; diff --git a/packages/core/test/Cron.test.ts b/packages/core/test/Cron.test.ts new file mode 100644 index 0000000000000..7409d963b37ff --- /dev/null +++ b/packages/core/test/Cron.test.ts @@ -0,0 +1,75 @@ +import { triggerToCronExpression } from "../src" + +describe('Cron', () => { + describe('triggerToCronExpression', () => { + test('should generate a valid cron for `everyMinute` triggers', () => { + const expression = triggerToCronExpression({ + mode: 'everyMinute', + }) + expect(expression).toMatch(/^[1-6]?[0-9] \* \* \* \* \*$/) + }) + + test('should generate a valid cron for `everyHour` triggers', () => { + const expression = triggerToCronExpression({ + mode: 'everyHour', + minute: 11, + }) + expect(expression).toMatch(/^[1-6]?[0-9] 11 \* \* \* \*$/) + }) + + test('should generate a valid cron for `everyX[minutes]` triggers', () => { + const expression = triggerToCronExpression({ + 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 = triggerToCronExpression({ + 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 = triggerToCronExpression({ + 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 = triggerToCronExpression({ + 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 = triggerToCronExpression({ + 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 = triggerToCronExpression({ + mode: 'custom', + cronExpression: ' 0 9-17 * * * ', + }) + expect(expression).toEqual('0 9-17 * * *') + }) + }) +}) diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index 5d018a126911c..ae48a90cf09fd 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -1,17 +1,8 @@ -import { ITriggerFunctions } from 'n8n-core'; -import { INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow'; +import { ITriggerFunctions, TriggerTime, triggerToCronExpression } from 'n8n-core'; +import { INodeType, INodeTypeDescription, ITriggerResponse, NodeHelpers } 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 +40,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:
  • 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: NodeHelpers.cronNodeOptions, }, ], }; @@ -246,57 +50,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(triggerToCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered @@ -307,10 +62,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/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index e55da58148e5e..63d20bad05cb6 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, }, ]; } From b70a06d957c27639179ad7d307bf8d6c132017eb 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: Tue, 16 Aug 2022 14:00:58 +0200 Subject: [PATCH 2/3] move all Cron code to packages/workflow --- packages/cli/test/integration/shared/utils.ts | 4 +++- packages/core/src/ActiveWorkflows.ts | 3 ++- packages/core/src/index.ts | 1 - packages/nodes-base/nodes/Cron/Cron.node.ts | 11 +++++++++-- packages/{core => workflow}/src/Cron.ts | 0 packages/workflow/src/index.ts | 1 + packages/{core => workflow}/test/Cron.test.ts | 2 +- 7 files changed, 16 insertions(+), 6 deletions(-) rename packages/{core => workflow}/src/Cron.ts (100%) rename packages/{core => workflow}/test/Cron.test.ts (97%) diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 64cba61954c8b..6a87cc6065454 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -8,7 +8,7 @@ import { URL } from 'url'; import bodyParser from 'body-parser'; import { set } from 'lodash'; import { CronJob } from 'cron'; -import { BinaryDataManager, TriggerTime, triggerToCronExpression, UserSettings } from 'n8n-core'; +import { BinaryDataManager, UserSettings } from 'n8n-core'; import { ICredentialType, IDataObject, @@ -21,6 +21,8 @@ import { ITriggerResponse, LoggerProxy, NodeHelpers, + TriggerTime, + triggerToCronExpression, } from 'n8n-workflow'; import config from '../../../config'; diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index ff591d41b790c..67dedd2c765f7 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -12,13 +12,14 @@ import { ITriggerResponse, IWorkflowExecuteAdditionalData, LoggerProxy as Logger, + TriggerTime, + triggerToCronExpression, Workflow, WorkflowActivateMode, WorkflowActivationError, WorkflowExecuteMode, } from 'n8n-workflow'; -import { TriggerTime, triggerToCronExpression } from './Cron'; // eslint-disable-next-line import/no-cycle import { IWorkflowData } from '.'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 33feaebc99d1f..11d130d02155d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -13,7 +13,6 @@ export * from './ActiveWebhooks'; export * from './BinaryDataManager'; export * from './Constants'; export * from './Credentials'; -export * from './Cron'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; export * from './NodeExecuteFunctions'; diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index ae48a90cf09fd..2cfeb4a167201 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -1,5 +1,12 @@ -import { ITriggerFunctions, TriggerTime, triggerToCronExpression } from 'n8n-core'; -import { INodeType, INodeTypeDescription, ITriggerResponse, NodeHelpers } from 'n8n-workflow'; +import { ITriggerFunctions } from 'n8n-core'; +import { + INodeType, + INodeTypeDescription, + ITriggerResponse, + NodeHelpers, + TriggerTime, + triggerToCronExpression, +} from 'n8n-workflow'; import { CronJob } from 'cron'; diff --git a/packages/core/src/Cron.ts b/packages/workflow/src/Cron.ts similarity index 100% rename from packages/core/src/Cron.ts rename to packages/workflow/src/Cron.ts diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 5ae90bce09678..a006e9d9d6268 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/core/test/Cron.test.ts b/packages/workflow/test/Cron.test.ts similarity index 97% rename from packages/core/test/Cron.test.ts rename to packages/workflow/test/Cron.test.ts index 7409d963b37ff..85c53d51b88f9 100644 --- a/packages/core/test/Cron.test.ts +++ b/packages/workflow/test/Cron.test.ts @@ -1,4 +1,4 @@ -import { triggerToCronExpression } from "../src" +import { triggerToCronExpression } from "../src/Cron" describe('Cron', () => { describe('triggerToCronExpression', () => { From 680ec84c33f7349ce5bb1025fbf0d1950897a73f 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: Thu, 18 Aug 2022 15:21:51 +0200 Subject: [PATCH 3/3] address PR comments --- packages/cli/test/integration/shared/utils.ts | 4 +-- packages/core/src/ActiveWorkflows.ts | 6 ++-- packages/nodes-base/nodes/Cron/Cron.node.ts | 4 +-- packages/workflow/src/Cron.ts | 32 +++++++++---------- packages/workflow/test/Cron.test.ts | 20 ++++++------ 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 6a87cc6065454..eaff862f106bb 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -22,7 +22,7 @@ import { LoggerProxy, NodeHelpers, TriggerTime, - triggerToCronExpression, + toCronExpression, } from 'n8n-workflow'; import config from '../../../config'; @@ -293,7 +293,7 @@ export async function initNodeTypes() { }; // Get all the trigger times - const cronTimes = (triggerTimes.item || []).map(triggerToCronExpression); + const cronTimes = (triggerTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index 67dedd2c765f7..01271de8fb8d6 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -13,7 +13,7 @@ import { IWorkflowExecuteAdditionalData, LoggerProxy as Logger, TriggerTime, - triggerToCronExpression, + toCronExpression, Workflow, WorkflowActivateMode, WorkflowActivationError, @@ -21,7 +21,7 @@ import { } from 'n8n-workflow'; // eslint-disable-next-line import/no-cycle -import { IWorkflowData } from '.'; +import type { IWorkflowData } from '.'; export class ActiveWorkflows { private workflowData: { @@ -162,7 +162,7 @@ export class ActiveWorkflows { }; // Get all the trigger times - const cronTimes = (pollTimes.item || []).map(triggerToCronExpression); + const cronTimes = (pollTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached const executeTrigger = async () => { diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index 2cfeb4a167201..718d15f8f0d02 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -4,8 +4,8 @@ import { INodeTypeDescription, ITriggerResponse, NodeHelpers, + toCronExpression, TriggerTime, - triggerToCronExpression, } from 'n8n-workflow'; import { CronJob } from 'cron'; @@ -58,7 +58,7 @@ export class Cron implements INodeType { }; // Get all the trigger times - const cronTimes = (triggerTimes.item || []).map(triggerToCronExpression); + const cronTimes = (triggerTimes.item || []).map(toCronExpression); // The trigger function to execute when the cron-time got reached // or when manually triggered diff --git a/packages/workflow/src/Cron.ts b/packages/workflow/src/Cron.ts index b3b1df8937dd8..fb1446fa04cd8 100644 --- a/packages/workflow/src/Cron.ts +++ b/packages/workflow/src/Cron.ts @@ -7,31 +7,31 @@ interface CustomTrigger extends BaseTriggerTime<'custom'> { cronExpression: CronExpression; } -interface EveryXTrigger extends BaseTriggerTime<'everyX'> { +interface EveryX extends BaseTriggerTime<'everyX'> { unit: U; value: number; } -type EveryMinuteTrigger = BaseTriggerTime<'everyMinute'>; -type EveryXMinutesTrigger = EveryXTrigger<'minutes'>; +type EveryMinute = BaseTriggerTime<'everyMinute'>; +type EveryXMinutes = EveryX<'minutes'>; -interface EveryHourTrigger extends BaseTriggerTime<'everyHour'> { +interface EveryHour extends BaseTriggerTime<'everyHour'> { minute: number; // 0 - 59 } -type EveryXHoursTrigger = EveryXTrigger<'hours'>; +type EveryXHours = EveryX<'hours'>; -interface EveryDayTrigger extends BaseTriggerTime<'everyDay'> { +interface EveryDay extends BaseTriggerTime<'everyDay'> { hour: number; // 0 - 23 minute: number; // 0 - 59 } -interface EveryWeekTrigger extends BaseTriggerTime<'everyWeek'> { +interface EveryWeek extends BaseTriggerTime<'everyWeek'> { hour: number; // 0 - 23 minute: number; // 0 - 59 weekday: number; // 0 - 6(Sun - Sat) } -interface EveryMonthTrigger extends BaseTriggerTime<'everyMonth'> { +interface EveryMonth extends BaseTriggerTime<'everyMonth'> { hour: number; // 0 - 23 minute: number; // 0 - 59 dayOfMonth: number; // 1 - 31 @@ -39,17 +39,17 @@ interface EveryMonthTrigger extends BaseTriggerTime<'everyMonth'> { export type TriggerTime = | CustomTrigger - | EveryMinuteTrigger - | EveryXMinutesTrigger - | EveryHourTrigger - | EveryXHoursTrigger - | EveryDayTrigger - | EveryWeekTrigger - | EveryMonthTrigger; + | EveryMinute + | EveryXMinutes + | EveryHour + | EveryXHours + | EveryDay + | EveryWeek + | EveryMonth; const randomSecond = () => Math.floor(Math.random() * 60).toString(); -export const triggerToCronExpression = (item: TriggerTime): CronExpression => { +export const toCronExpression = (item: TriggerTime): CronExpression => { if (item.mode === 'everyMinute') return `${randomSecond()} * * * * *`; if (item.mode === 'everyHour') return `${randomSecond()} ${item.minute} * * * *`; diff --git a/packages/workflow/test/Cron.test.ts b/packages/workflow/test/Cron.test.ts index 85c53d51b88f9..43c122ee960d4 100644 --- a/packages/workflow/test/Cron.test.ts +++ b/packages/workflow/test/Cron.test.ts @@ -1,16 +1,16 @@ -import { triggerToCronExpression } from "../src/Cron" +import { toCronExpression } from "../src/Cron" describe('Cron', () => { - describe('triggerToCronExpression', () => { + describe('toCronExpression', () => { test('should generate a valid cron for `everyMinute` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyMinute', }) expect(expression).toMatch(/^[1-6]?[0-9] \* \* \* \* \*$/) }) test('should generate a valid cron for `everyHour` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyHour', minute: 11, }) @@ -18,7 +18,7 @@ describe('Cron', () => { }) test('should generate a valid cron for `everyX[minutes]` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyX', unit: 'minutes', value: 42, @@ -27,7 +27,7 @@ describe('Cron', () => { }) test('should generate a valid cron for `everyX[hours]` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyX', unit: 'hours', value: 3, @@ -36,7 +36,7 @@ describe('Cron', () => { }) test('should generate a valid cron for `everyDay` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyDay', hour: 13, minute: 17, @@ -45,7 +45,7 @@ describe('Cron', () => { }) test('should generate a valid cron for `everyWeek` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyWeek', hour: 13, minute: 17, @@ -55,7 +55,7 @@ describe('Cron', () => { }) test('should generate a valid cron for `everyMonth` triggers', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'everyMonth', hour: 13, minute: 17, @@ -65,7 +65,7 @@ describe('Cron', () => { }) test('should trim custom cron expressions', () => { - const expression = triggerToCronExpression({ + const expression = toCronExpression({ mode: 'custom', cronExpression: ' 0 9-17 * * * ', })