From 8f5f07a20d03d22a681cbaa34a79c37874803182 Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 30 Aug 2022 12:42:23 +0200 Subject: [PATCH 01/14] =?UTF-8?q?=E2=9C=A8=20Create=20Schedule=20node=20wi?= =?UTF-8?q?th=20MVP=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.json | 18 + .../nodes/Schedule/ScheduleTrigger.node.ts | 308 ++++++++++++++++++ packages/nodes-base/package.json | 1 + 3 files changed, 327 insertions(+) create mode 100644 packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json create mode 100644 packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json new file mode 100644 index 0000000000000..73cb130c7d37c --- /dev/null +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json @@ -0,0 +1,18 @@ +{ + "node": "n8n-nodes-base.scheduleTrigger", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": ["Core Nodes"], + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.scheduleTrigger/" + } + ], + "generic": [] + }, + "alias": ["Time", "Scheduler", "Polling"], + "subcategories": { + "Core Nodes": ["Flow"] + } +} diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts new file mode 100644 index 0000000000000..69c0a48a2177b --- /dev/null +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -0,0 +1,308 @@ +import { ITriggerFunctions } from 'n8n-core'; +import { + INodeType, + INodeTypeDescription, + ITriggerResponse, + NodeOperationError, +} from 'n8n-workflow'; + +export class ScheduleTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Schedule Trigger', + name: 'scheduleTrigger', + icon: 'fa:hourglass', + group: ['trigger', 'schedule'], + version: 1, + description: 'Triggers the workflow in a given schedule', + eventTriggerDescription: '', + activationMessage: + 'Your schedule trigger will now trigger executions on the schedule you have defined.', + defaults: { + name: 'Schedule Trigger', + color: '#00FF00', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + outputs: ['main'], + properties: [ + { + displayName: 'Add rule', + name: 'rule', + type: 'fixedCollection', + default: 'hours', + options: [ + { + displayName: 'Seconds', + name: 'seconds', + values: [ + { + name: 'value', + displayName: 'Seconds between triggers', + type: 'number', + default: 30, + description: 'Interval value', + }, + ], + }, + { + displayName: 'Minutes', + name: 'minutes', + values: [ + { + name: 'value', + displayName: 'Minutes between triggers', + type: 'number', + default: 5, + description: 'Interval value', + }, + ], + }, + { + displayName: 'Hours', + name: 'hours', + values: [ + { + name: 'value', + displayName: 'Hours between triggers', + type: 'number', + default: 1, + description: 'Interval value', + }, + { + name: 'triggerAtMinute', + displayName: 'Trigger at Minute', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + maxValue: 59, + }, + description: 'The minute past the hour to trigger (0-59)', + }, + ], + }, + { + displayName: 'Days', + name: 'days', + default: 1, + values: [ + { + name: 'value', + displayName: 'Days between triggers', + type: 'number', + default: 1, + description: 'Interval value', + }, + { + name: 'Trigger at Hour', + displayName: 'Trigger at Hour', + type: 'options', + default: 0, + options: [ + { + name: 'midnight', + displayName: 'Midnight', + value: 0, + }, + { + name: '1am', + displayName: '1am', + value: 1, + }, + ], + description: 'The hour of the day to trigger', + }, + { + name: 'triggerAtMinute', + displayName: 'Trigger at Minute', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + maxValue: 59, + }, + description: 'The minute past the hour to trigger (0-59)', + }, + ], + }, + { + displayName: 'Weeks', + name: 'weeks', + type: 'number', + default: 0, + values: [ + { + name: 'value', + displayName: 'Weeks between triggers', + type: 'number', + default: 1, + description: 'Interval value', + }, + { + name: 'triggerAtDay', + displayName: 'Trigger on Weekdays', + type: 'multiOptions', + typeOptions: { + maxValue: 7, + }, + options: [ + { + name: 'Monday', + displayName: '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: 7, + }, + ], + default: [7], + }, + { + name: 'Trigger at Hour', + displayName: 'Trigger at Hour', + type: 'options', + default: 0, + options: [ + { + name: 'midnight', + displayName: 'Midnight', + value: 0, + }, + { + name: '1am', + displayName: '1am', + value: 1, + }, + ], + description: 'The hour of the day to trigger', + }, + { + name: 'triggerAtMinute', + displayName: 'Trigger at Minute', + type: 'number', + default: 0, + description: 'The minute past the hour to trigger (0-59)', + }, + ], + }, + { + displayName: 'Months', + name: 'months', + type: 'number', + default: 1, + values: [ + { + name: 'value', + displayName: 'Months between triggers', + type: 'number', + default: 1, + description: 'Would run every month unless specified otherwise', + }, + { + name: 'triggerAtDayOfMonth', + displayName: 'Trigger at Day of Month', + type: 'number', + typeOptions: { + minValue: 1, + maxValue: 31, + }, + default: 1, + description: 'The day of the month to trigger (1-31)', + hint: 'Will run on the last day of the month if there aren"t enough days in the month', + }, + { + name: 'Trigger at Hour', + displayName: 'Trigger at Hour', + type: 'options', + default: 0, + options: [ + { + name: 'midnight', + displayName: 'Midnight', + value: 0, + }, + { + name: '1am', + displayName: '1am', + value: 1, + }, + ], + description: 'The hour of the day to trigger', + }, + { + name: 'triggerAtMinute', + displayName: 'Trigger at Minute', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 59, + }, + default: 0, + description: 'The minute past the hour to trigger (0-59)', + }, + ], + }, + { + displayName: 'Cron Expression', + name: 'cronExpression', + type: 'string', + values: [ + { + name: 'value', + displayName: 'Expression', + type: 'string', + default: '', + description: 'You can find help generating your cron expression here', + hint: '[Second] [Minute] [Hour] [Day of Month] [Month] [Day of Week]', + }, + ], + }, + ], + }, + ], + }; + + async trigger(this: ITriggerFunctions): Promise { + const executeTrigger = () => { + this.emit([this.helpers.returnJsonArray([{}])]); + }; + + const intervalValue = 1000; + + const intervalObj = setInterval(executeTrigger, intervalValue); + + async function closeFunction() { + clearInterval(intervalObj); + } + + async function manualTriggerFunction() { + executeTrigger(); + } + + return { + closeFunction, + manualTriggerFunction, + }; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index e56ed216ee6a5..391a1e2aa83a1 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -610,6 +610,7 @@ "dist/nodes/S3/S3.node.js", "dist/nodes/Salesforce/Salesforce.node.js", "dist/nodes/Salesmate/Salesmate.node.js", + "dist/nodes/Schedule/ScheduleTrigger.node.js", "dist/nodes/SeaTable/SeaTable.node.js", "dist/nodes/SeaTable/SeaTableTrigger.node.js", "dist/nodes/SecurityScorecard/SecurityScorecard.node.js", From 5b8e93da00adb0ab639418103531686b131d7227 Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 30 Aug 2022 14:02:23 +0200 Subject: [PATCH 02/14] =?UTF-8?q?=E2=9C=A8=20Add=2024=20increments=20for?= =?UTF-8?q?=20hours=20picker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.ts | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 69c0a48a2177b..ec6b82ea07a2d 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -109,6 +109,116 @@ export class ScheduleTrigger implements INodeType { displayName: '1am', value: 1, }, + { + name: '2am', + displayName: '2am', + value: 2, + }, + { + name: '3am', + displayName: '3am', + value: 3, + }, + { + name: '4am', + displayName: '4am', + value: 4, + }, + { + name: '5am', + displayName: '5am', + value: 5, + }, + { + name: '6am', + displayName: '6am', + value: 6, + }, + { + name: '7am', + displayName: '7am', + value: 7, + }, + { + name: '8am', + displayName: '8am', + value: 8, + }, + { + name: '9am', + displayName: '9am', + value: 9, + }, + { + name: '10am', + displayName: '10am', + value: 10, + }, + { + name: '11am', + displayName: '11am', + value: 11, + }, + { + name: 'noon', + displayName: 'Noon', + value: 12, + }, + { + name: '1pm', + displayName: '1pm', + value: 13, + }, + { + name: '2pm', + displayName: '2pm', + value: 14, + }, + { + name: '3pm', + displayName: '3pm', + value: 15, + }, + { + name: '4pm', + displayName: '4pm', + value: 16, + }, + { + name: '5pm', + displayName: '5pm', + value: 17, + }, + { + name: '6pm', + displayName: '6pm', + value: 18, + }, + { + name: '7pm', + displayName: '7pm', + value: 19, + }, + { + name: '8pm', + displayName: '8pm', + value: 20, + }, + { + name: '9pm', + displayName: '9pm', + value: 21, + }, + { + name: '10pm', + displayName: '10pm', + value: 22, + }, + { + name: '11pm', + displayName: '11pm', + value: 23, + }, ], description: 'The hour of the day to trigger', }, @@ -194,6 +304,116 @@ export class ScheduleTrigger implements INodeType { displayName: '1am', value: 1, }, + { + name: '2am', + displayName: '2am', + value: 2, + }, + { + name: '3am', + displayName: '3am', + value: 3, + }, + { + name: '4am', + displayName: '4am', + value: 4, + }, + { + name: '5am', + displayName: '5am', + value: 5, + }, + { + name: '6am', + displayName: '6am', + value: 6, + }, + { + name: '7am', + displayName: '7am', + value: 7, + }, + { + name: '8am', + displayName: '8am', + value: 8, + }, + { + name: '9am', + displayName: '9am', + value: 9, + }, + { + name: '10am', + displayName: '10am', + value: 10, + }, + { + name: '11am', + displayName: '11am', + value: 11, + }, + { + name: 'noon', + displayName: 'Noon', + value: 12, + }, + { + name: '1pm', + displayName: '1pm', + value: 13, + }, + { + name: '2pm', + displayName: '2pm', + value: 14, + }, + { + name: '3pm', + displayName: '3pm', + value: 15, + }, + { + name: '4pm', + displayName: '4pm', + value: 16, + }, + { + name: '5pm', + displayName: '5pm', + value: 17, + }, + { + name: '6pm', + displayName: '6pm', + value: 18, + }, + { + name: '7pm', + displayName: '7pm', + value: 19, + }, + { + name: '8pm', + displayName: '8pm', + value: 20, + }, + { + name: '9pm', + displayName: '9pm', + value: 21, + }, + { + name: '10pm', + displayName: '10pm', + value: 22, + }, + { + name: '11pm', + displayName: '11pm', + value: 23, + }, ], description: 'The hour of the day to trigger', }, @@ -247,6 +467,116 @@ export class ScheduleTrigger implements INodeType { displayName: '1am', value: 1, }, + { + name: '2am', + displayName: '2am', + value: 2, + }, + { + name: '3am', + displayName: '3am', + value: 3, + }, + { + name: '4am', + displayName: '4am', + value: 4, + }, + { + name: '5am', + displayName: '5am', + value: 5, + }, + { + name: '6am', + displayName: '6am', + value: 6, + }, + { + name: '7am', + displayName: '7am', + value: 7, + }, + { + name: '8am', + displayName: '8am', + value: 8, + }, + { + name: '9am', + displayName: '9am', + value: 9, + }, + { + name: '10am', + displayName: '10am', + value: 10, + }, + { + name: '11am', + displayName: '11am', + value: 11, + }, + { + name: 'noon', + displayName: 'Noon', + value: 12, + }, + { + name: '1pm', + displayName: '1pm', + value: 13, + }, + { + name: '2pm', + displayName: '2pm', + value: 14, + }, + { + name: '3pm', + displayName: '3pm', + value: 15, + }, + { + name: '4pm', + displayName: '4pm', + value: 16, + }, + { + name: '5pm', + displayName: '5pm', + value: 17, + }, + { + name: '6pm', + displayName: '6pm', + value: 18, + }, + { + name: '7pm', + displayName: '7pm', + value: 19, + }, + { + name: '8pm', + displayName: '8pm', + value: 20, + }, + { + name: '9pm', + displayName: '9pm', + value: 21, + }, + { + name: '10pm', + displayName: '10pm', + value: 22, + }, + { + name: '11pm', + displayName: '11pm', + value: 23, + }, ], description: 'The hour of the day to trigger', }, From 6c0c31c535e2b4a3299f7bfdc319e9e4a09f664e Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 30 Aug 2022 14:04:57 +0200 Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=9A=A8=20=20Lintfix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index ec6b82ea07a2d..6c7f7258f71e0 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -26,10 +26,10 @@ export class ScheduleTrigger implements INodeType { outputs: ['main'], properties: [ { - displayName: 'Add rule', + displayName: 'Add Rule', name: 'rule', type: 'fixedCollection', - default: 'hours', + default: {}, options: [ { displayName: 'Seconds', @@ -37,7 +37,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Seconds between triggers', + displayName: 'Seconds Between Triggers', type: 'number', default: 30, description: 'Interval value', @@ -50,7 +50,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Minutes between triggers', + displayName: 'Minutes Between Triggers', type: 'number', default: 5, description: 'Interval value', @@ -63,7 +63,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Hours between triggers', + displayName: 'Hours Between Triggers', type: 'number', default: 1, description: 'Interval value', @@ -88,7 +88,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Days between triggers', + displayName: 'Days Between Triggers', type: 'number', default: 1, description: 'Interval value', @@ -100,7 +100,7 @@ export class ScheduleTrigger implements INodeType { default: 0, options: [ { - name: 'midnight', + name: 'Midnight', displayName: 'Midnight', value: 0, }, @@ -160,7 +160,7 @@ export class ScheduleTrigger implements INodeType { value: 11, }, { - name: 'noon', + name: 'Noon', displayName: 'Noon', value: 12, }, @@ -243,7 +243,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Weeks between triggers', + displayName: 'Weeks Between Triggers', type: 'number', default: 1, description: 'Interval value', @@ -256,34 +256,33 @@ export class ScheduleTrigger implements INodeType { maxValue: 7, }, options: [ + { + name: 'Friday', + value: 5, + }, { name: 'Monday', - displayName: 'Monday', value: 1, }, { - name: 'Tuesday', - value: 2, + name: 'Saturday', + value: 6, }, { - name: 'Wednesday', - value: 3, + name: 'Sunday', + value: 7, }, { name: 'Thursday', value: 4, }, { - name: 'Friday', - value: 5, - }, - { - name: 'Saturday', - value: 6, + name: 'Tuesday', + value: 2, }, { - name: 'Sunday', - value: 7, + name: 'Wednesday', + value: 3, }, ], default: [7], @@ -295,7 +294,7 @@ export class ScheduleTrigger implements INodeType { default: 0, options: [ { - name: 'midnight', + name: 'Midnight', displayName: 'Midnight', value: 0, }, @@ -355,7 +354,7 @@ export class ScheduleTrigger implements INodeType { value: 11, }, { - name: 'noon', + name: 'Noon', displayName: 'Noon', value: 12, }, @@ -434,7 +433,7 @@ export class ScheduleTrigger implements INodeType { values: [ { name: 'value', - displayName: 'Months between triggers', + displayName: 'Months Between Triggers', type: 'number', default: 1, description: 'Would run every month unless specified otherwise', @@ -458,7 +457,7 @@ export class ScheduleTrigger implements INodeType { default: 0, options: [ { - name: 'midnight', + name: 'Midnight', displayName: 'Midnight', value: 0, }, @@ -518,7 +517,7 @@ export class ScheduleTrigger implements INodeType { value: 11, }, { - name: 'noon', + name: 'Noon', displayName: 'Noon', value: 12, }, @@ -597,6 +596,7 @@ export class ScheduleTrigger implements INodeType { displayName: 'Cron Expression', name: 'cronExpression', type: 'string', + default: '', values: [ { name: 'value', From 62bd78dc219a65127af5cc318de27de6574a2040 Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 7 Sep 2022 09:54:18 +0200 Subject: [PATCH 04/14] Add timestamp, add hour minute and cron expression --- .../nodes/Schedule/CronInterface.ts | 9 ++ .../nodes/Schedule/ScheduleTrigger.node.ts | 121 ++++++++++++++++-- .../nodes-base/nodes/Schedule/schedule.svg | 1 + 3 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 packages/nodes-base/nodes/Schedule/CronInterface.ts create mode 100644 packages/nodes-base/nodes/Schedule/schedule.svg diff --git a/packages/nodes-base/nodes/Schedule/CronInterface.ts b/packages/nodes-base/nodes/Schedule/CronInterface.ts new file mode 100644 index 0000000000000..22b6473cd1b7b --- /dev/null +++ b/packages/nodes-base/nodes/Schedule/CronInterface.ts @@ -0,0 +1,9 @@ +import { IDataObject } from 'n8n-workflow'; + +export type ICronExpression = [ + string | Date, + string | Date, + string | Date, + string | Date, + string | Date, +]; diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 6c7f7258f71e0..83edd3948c0a7 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -1,16 +1,23 @@ import { ITriggerFunctions } from 'n8n-core'; import { + IDataObject, INodeType, INodeTypeDescription, ITriggerResponse, NodeOperationError, + toCronExpression, + TriggerTime, } from 'n8n-workflow'; +import { CronJob } from 'cron'; +import { ICronExpression } from './CronInterface'; +import moment from 'moment'; + export class ScheduleTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Schedule Trigger', name: 'scheduleTrigger', - icon: 'fa:hourglass', + icon: 'file:schedule.svg', group: ['trigger', 'schedule'], version: 1, description: 'Triggers the workflow in a given schedule', @@ -29,7 +36,7 @@ export class ScheduleTrigger implements INodeType { displayName: 'Add Rule', name: 'rule', type: 'fixedCollection', - default: {}, + default: 'seconds', options: [ { displayName: 'Seconds', @@ -94,7 +101,7 @@ export class ScheduleTrigger implements INodeType { description: 'Interval value', }, { - name: 'Trigger at Hour', + name: 'triggerAtHour', displayName: 'Trigger at Hour', type: 'options', default: 0, @@ -288,7 +295,7 @@ export class ScheduleTrigger implements INodeType { default: [7], }, { - name: 'Trigger at Hour', + name: 'triggerAtHour', displayName: 'Trigger at Hour', type: 'options', default: 0, @@ -451,7 +458,7 @@ export class ScheduleTrigger implements INodeType { hint: 'Will run on the last day of the month if there aren"t enough days in the month', }, { - name: 'Trigger at Hour', + name: 'triggerAtHour', displayName: 'Trigger at Hour', type: 'options', default: 0, @@ -603,7 +610,8 @@ export class ScheduleTrigger implements INodeType { displayName: 'Expression', type: 'string', default: '', - description: 'You can find help generating your cron expression here', + description: + 'You can find help generating your cron expression ', hint: '[Second] [Minute] [Hour] [Day of Month] [Month] [Day of Week]', }, ], @@ -614,15 +622,110 @@ export class ScheduleTrigger implements INodeType { }; async trigger(this: ITriggerFunctions): Promise { + const rule = this.getNodeParameter('rule', []) as IDataObject; + const timezone = this.getTimezone(); + const date = moment.tz(timezone).week(); + let cronJobs: CronJob[] = []; + let intervalObj: NodeJS.Timeout; const executeTrigger = () => { - this.emit([this.helpers.returnJsonArray([{}])]); + // @ts-ignore + if (!rule.weeks || (52 - date) % rule.weeks.value) { + const resultData = { + timestamp: moment.tz(timezone).toISOString(), + 'Readable date': moment.tz(timezone).format('MMMM Do YYYY, h:mm:ss a'), + 'Readable time': moment.tz(timezone).format('h:mm:ss a'), + 'Day of week': moment.tz(timezone).format('dddd'), + Year: moment.tz(timezone).format('YYYY'), + Month: moment.tz(timezone).format('MMMM'), + 'Day of month': moment.tz(timezone).format('DD'), + Hour: moment.tz(timezone).format('HH'), + Minute: moment.tz(timezone).format('mm'), + Second: moment.tz(timezone).format('ss'), + Timezone: moment.tz(timezone).format('z Z'), + }; + this.emit([this.helpers.returnJsonArray([resultData])]); + } }; + let intervalValue = 1000; + + intervalObj = setInterval(executeTrigger, intervalValue); - const intervalValue = 1000; + if (rule.cronExpression) { + const cronExpression = rule.cronExpression as IDataObject; + const cronArray: string[] = []; + cronArray.push(cronExpression.value as string); + cronJobs = cronArray.map( + (cronArray) => new CronJob(cronArray, executeTrigger, undefined, true, timezone), + ); + } + if (rule.seconds) { + const seconds = rule.seconds as IDataObject; + intervalValue *= seconds.value as number; + intervalObj = setInterval(executeTrigger, intervalValue); + } + if (rule.minutes) { + const minutes = rule.minutes as IDataObject; + // @ts-ignore + intervalValue *= (60 * minutes.value) as number; + intervalObj = setInterval(executeTrigger, intervalValue); + } + + if (rule.hours) { + const expression: TriggerTime[] = [ + { + mode: 'everyHour', + // @ts-ignore + hour: rule.hours.value, + // @ts-ignore + minute: rule.hours.triggerAtMinute, + }, + ]; + const cronTimes = (expression || []).map(toCronExpression); + cronJobs = cronTimes.map( + (crontime) => new CronJob(crontime, executeTrigger, undefined, true, timezone), + ); + } - const intervalObj = setInterval(executeTrigger, intervalValue); + if (rule.days) { + const daysObject = rule.days as IDataObject; + const day = daysObject.value?.toString() as string; + const hour = daysObject.triggerAtHour?.toString() as string; + const minute = daysObject.triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, `*/${day}`, '*', '*']; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } + + if (rule.weeks) { + const weeksObject = rule.weeks as IDataObject; + + const days = weeksObject.triggerAtDay as IDataObject[]; + const day = days.join(',') as string; + const hour = weeksObject.triggerAtHour?.toString() as string; + const minute = weeksObject.triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, '*', '*', day]; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } + + if (rule.months) { + const monthsObejct = rule.months as IDataObject; + const month = monthsObejct.value?.toString() as string; + const day = monthsObejct.triggerAtDayOfMonth?.toString() as string; + const hour = monthsObejct.triggerAtHour?.toString() as string; + const minute = monthsObejct.triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, day, `*/${month}`, '*']; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } async function closeFunction() { + for (const cronJob of cronJobs) { + cronJob.stop(); + } clearInterval(intervalObj); } diff --git a/packages/nodes-base/nodes/Schedule/schedule.svg b/packages/nodes-base/nodes/Schedule/schedule.svg new file mode 100644 index 0000000000000..b496e09d6b893 --- /dev/null +++ b/packages/nodes-base/nodes/Schedule/schedule.svg @@ -0,0 +1 @@ + \ No newline at end of file From f161eaf5e47a4e496571ab0a70309974799cae57 Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 7 Sep 2022 10:29:43 +0200 Subject: [PATCH 05/14] Fix bug where there was one extra interval object --- packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 83edd3948c0a7..24e38389f0a1e 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -647,9 +647,6 @@ export class ScheduleTrigger implements INodeType { } }; let intervalValue = 1000; - - intervalObj = setInterval(executeTrigger, intervalValue); - if (rule.cronExpression) { const cronExpression = rule.cronExpression as IDataObject; const cronArray: string[] = []; From c19e4336cc334488f58a7bd1df94e9bc904e20aa Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 7 Sep 2022 10:54:30 +0200 Subject: [PATCH 06/14] Fix default value from fixedCollection --- packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 24e38389f0a1e..d1cbfb8c103f8 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -36,7 +36,7 @@ export class ScheduleTrigger implements INodeType { displayName: 'Add Rule', name: 'rule', type: 'fixedCollection', - default: 'seconds', + default: {}, options: [ { displayName: 'Seconds', From 4d7eaf7586b39c32a68ad9bdeb664dfe12e32cc3 Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 13 Sep 2022 17:23:28 +0200 Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=90=9B=20UI=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.ts | 479 +++++------------- 1 file changed, 126 insertions(+), 353 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index d1cbfb8c103f8..c434707df0ba7 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -33,232 +33,160 @@ export class ScheduleTrigger implements INodeType { outputs: ['main'], properties: [ { - displayName: 'Add Rule', + displayName: + 'This workflow will run on the schedule you define here once you activate it.

For testing, you can also trigger it manually: by going back to the canvas and clicking ‘execute workflow’', + name: 'notice', + type: 'notice', + default: '', + }, + { + displayName: 'Trigger Rules', name: 'rule', + placeholder: 'Add Rule', type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, default: {}, options: [ { - displayName: 'Seconds', - name: 'seconds', + name: 'interval', + displayName: 'Trigger Interval', values: [ { - name: 'value', + displayName: 'Trigger Interval', + name: 'field', + type: 'options', + default: 'hours', + options: [ + { + name: 'Seconds', + value: 'seconds', + }, + { + name: 'Minutes', + value: 'minutes', + }, + { + name: 'Hours', + value: 'hours', + }, + { + name: 'Days', + value: 'days', + }, + { + name: 'Weeks', + value: 'weeks', + }, + { + name: 'Months', + value: 'months', + }, + { + name: 'Cron Expression', + value: 'cronExpression', + }, + ], + }, + { + name: 'secondsInterval', displayName: 'Seconds Between Triggers', type: 'number', default: 30, + displayOptions: { + show: { + field: ['seconds'], + }, + }, description: 'Interval value', }, - ], - }, - { - displayName: 'Minutes', - name: 'minutes', - values: [ { - name: 'value', + name: 'minutesInterval', displayName: 'Minutes Between Triggers', type: 'number', default: 5, + displayOptions: { + show: { + field: ['minutes'], + }, + }, description: 'Interval value', }, - ], - }, - { - displayName: 'Hours', - name: 'hours', - values: [ { - name: 'value', + name: 'hoursInterval', displayName: 'Hours Between Triggers', type: 'number', + displayOptions: { + show: { + field: ['hours'], + }, + }, default: 1, description: 'Interval value', }, { - name: 'triggerAtMinute', - displayName: 'Trigger at Minute', - type: 'number', - default: 0, - typeOptions: { - minValue: 0, - maxValue: 59, - }, - description: 'The minute past the hour to trigger (0-59)', - }, - ], - }, - { - displayName: 'Days', - name: 'days', - default: 1, - values: [ - { - name: 'value', + name: 'daysInterval', displayName: 'Days Between Triggers', type: 'number', + displayOptions: { + show: { + field: ['days'], + }, + }, default: 1, description: 'Interval value', }, { - name: 'triggerAtHour', - displayName: 'Trigger at Hour', - type: 'options', - default: 0, - options: [ - { - name: 'Midnight', - displayName: 'Midnight', - value: 0, - }, - { - name: '1am', - displayName: '1am', - value: 1, - }, - { - name: '2am', - displayName: '2am', - value: 2, - }, - { - name: '3am', - displayName: '3am', - value: 3, - }, - { - name: '4am', - displayName: '4am', - value: 4, - }, - { - name: '5am', - displayName: '5am', - value: 5, - }, - { - name: '6am', - displayName: '6am', - value: 6, - }, - { - name: '7am', - displayName: '7am', - value: 7, - }, - { - name: '8am', - displayName: '8am', - value: 8, - }, - { - name: '9am', - displayName: '9am', - value: 9, - }, - { - name: '10am', - displayName: '10am', - value: 10, - }, - { - name: '11am', - displayName: '11am', - value: 11, - }, - { - name: 'Noon', - displayName: 'Noon', - value: 12, - }, - { - name: '1pm', - displayName: '1pm', - value: 13, - }, - { - name: '2pm', - displayName: '2pm', - value: 14, - }, - { - name: '3pm', - displayName: '3pm', - value: 15, - }, - { - name: '4pm', - displayName: '4pm', - value: 16, - }, - { - name: '5pm', - displayName: '5pm', - value: 17, - }, - { - name: '6pm', - displayName: '6pm', - value: 18, - }, - { - name: '7pm', - displayName: '7pm', - value: 19, - }, - { - name: '8pm', - displayName: '8pm', - value: 20, - }, - { - name: '9pm', - displayName: '9pm', - value: 21, - }, - { - name: '10pm', - displayName: '10pm', - value: 22, - }, - { - name: '11pm', - displayName: '11pm', - value: 23, + name: 'weeksInterval', + displayName: 'Weeks Between Triggers', + type: 'number', + displayOptions: { + show: { + field: ['weeks'], }, - ], - description: 'The hour of the day to trigger', + }, + default: 1, + description: 'Would run every week unless specified otherwise', }, { - name: 'triggerAtMinute', - displayName: 'Trigger at Minute', + name: 'monthsInterval', + displayName: 'Months Between Triggers', type: 'number', - default: 0, - typeOptions: { - minValue: 0, - maxValue: 59, + displayOptions: { + show: { + field: ['months'], + }, }, - description: 'The minute past the hour to trigger (0-59)', + default: 1, + description: 'Would run every month unless specified otherwise', }, - ], - }, - { - displayName: 'Weeks', - name: 'weeks', - type: 'number', - default: 0, - values: [ { - name: 'value', - displayName: 'Weeks Between Triggers', + name: 'triggerAtDayOfMonth', + displayName: 'Trigger at Day of Month', type: 'number', + displayOptions: { + show: { + field: ['months'], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 31, + }, default: 1, - description: 'Interval value', + description: 'The day of the month to trigger (1-31)', + hint: 'If a month doesn’t have this day, the node won’t trigger', }, { name: 'triggerAtDay', displayName: 'Trigger on Weekdays', type: 'multiOptions', + displayOptions: { + show: { + field: ['weeks'], + }, + }, typeOptions: { maxValue: 7, }, @@ -299,169 +227,11 @@ export class ScheduleTrigger implements INodeType { displayName: 'Trigger at Hour', type: 'options', default: 0, - options: [ - { - name: 'Midnight', - displayName: 'Midnight', - value: 0, - }, - { - name: '1am', - displayName: '1am', - value: 1, - }, - { - name: '2am', - displayName: '2am', - value: 2, - }, - { - name: '3am', - displayName: '3am', - value: 3, - }, - { - name: '4am', - displayName: '4am', - value: 4, - }, - { - name: '5am', - displayName: '5am', - value: 5, - }, - { - name: '6am', - displayName: '6am', - value: 6, - }, - { - name: '7am', - displayName: '7am', - value: 7, - }, - { - name: '8am', - displayName: '8am', - value: 8, - }, - { - name: '9am', - displayName: '9am', - value: 9, - }, - { - name: '10am', - displayName: '10am', - value: 10, - }, - { - name: '11am', - displayName: '11am', - value: 11, - }, - { - name: 'Noon', - displayName: 'Noon', - value: 12, - }, - { - name: '1pm', - displayName: '1pm', - value: 13, - }, - { - name: '2pm', - displayName: '2pm', - value: 14, - }, - { - name: '3pm', - displayName: '3pm', - value: 15, - }, - { - name: '4pm', - displayName: '4pm', - value: 16, - }, - { - name: '5pm', - displayName: '5pm', - value: 17, - }, - { - name: '6pm', - displayName: '6pm', - value: 18, - }, - { - name: '7pm', - displayName: '7pm', - value: 19, - }, - { - name: '8pm', - displayName: '8pm', - value: 20, - }, - { - name: '9pm', - displayName: '9pm', - value: 21, - }, - { - name: '10pm', - displayName: '10pm', - value: 22, - }, - { - name: '11pm', - displayName: '11pm', - value: 23, + displayOptions: { + show: { + field: ['days', 'weeks', 'months'], }, - ], - description: 'The hour of the day to trigger', - }, - { - name: 'triggerAtMinute', - displayName: 'Trigger at Minute', - type: 'number', - default: 0, - description: 'The minute past the hour to trigger (0-59)', - }, - ], - }, - { - displayName: 'Months', - name: 'months', - type: 'number', - default: 1, - values: [ - { - name: 'value', - displayName: 'Months Between Triggers', - type: 'number', - default: 1, - description: 'Would run every month unless specified otherwise', - }, - { - name: 'triggerAtDayOfMonth', - displayName: 'Trigger at Day of Month', - type: 'number', - typeOptions: { - minValue: 1, - maxValue: 31, }, - default: 1, - description: 'The day of the month to trigger (1-31)', - hint: 'Will run on the last day of the month if there aren"t enough days in the month', - }, - { - name: 'triggerAtHour', - displayName: 'Trigger at Hour', - type: 'options', - default: 0, options: [ { name: 'Midnight', @@ -590,28 +360,31 @@ export class ScheduleTrigger implements INodeType { name: 'triggerAtMinute', displayName: 'Trigger at Minute', type: 'number', + default: 0, + displayOptions: { + show: { + field: ['hours', 'days', 'weeks', 'months'], + }, + }, typeOptions: { minValue: 0, maxValue: 59, }, - default: 0, description: 'The minute past the hour to trigger (0-59)', }, - ], - }, - { - displayName: 'Cron Expression', - name: 'cronExpression', - type: 'string', - default: '', - values: [ { - name: 'value', + name: 'expression', displayName: 'Expression', type: 'string', default: '', + placeholder: '5 12 26 1-30 mon-sun', + displayOptions: { + show: { + field: ['cronExpression'], + }, + }, description: - 'You can find help generating your cron expression ', + 'You can find help generating your cron expression here', hint: '[Second] [Minute] [Hour] [Day of Month] [Month] [Day of Week]', }, ], From 5b048c45ac64060794801296b773d2e1791b7caf Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 14 Sep 2022 11:13:51 +0200 Subject: [PATCH 08/14] =?UTF-8?q?=F0=9F=8E=A8=20Changed=20logic=20to=20ref?= =?UTF-8?q?lect=20UI=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.ts | 168 ++++++++---------- 1 file changed, 75 insertions(+), 93 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index c434707df0ba7..75636c3629652 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -1,13 +1,5 @@ import { ITriggerFunctions } from 'n8n-core'; -import { - IDataObject, - INodeType, - INodeTypeDescription, - ITriggerResponse, - NodeOperationError, - toCronExpression, - TriggerTime, -} from 'n8n-workflow'; +import { IDataObject, INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow'; import { CronJob } from 'cron'; import { ICronExpression } from './CronInterface'; @@ -385,7 +377,7 @@ export class ScheduleTrigger implements INodeType { }, description: 'You can find help generating your cron expression here', - hint: '[Second] [Minute] [Hour] [Day of Month] [Month] [Day of Week]', + hint: '[Minute] [Hour] [Day of Month] [Month] [Day of Week]', }, ], }, @@ -396,100 +388,90 @@ export class ScheduleTrigger implements INodeType { async trigger(this: ITriggerFunctions): Promise { const rule = this.getNodeParameter('rule', []) as IDataObject; + const interval = rule.interval as IDataObject[]; const timezone = this.getTimezone(); const date = moment.tz(timezone).week(); let cronJobs: CronJob[] = []; let intervalObj: NodeJS.Timeout; const executeTrigger = () => { - // @ts-ignore - if (!rule.weeks || (52 - date) % rule.weeks.value) { - const resultData = { - timestamp: moment.tz(timezone).toISOString(), - 'Readable date': moment.tz(timezone).format('MMMM Do YYYY, h:mm:ss a'), - 'Readable time': moment.tz(timezone).format('h:mm:ss a'), - 'Day of week': moment.tz(timezone).format('dddd'), - Year: moment.tz(timezone).format('YYYY'), - Month: moment.tz(timezone).format('MMMM'), - 'Day of month': moment.tz(timezone).format('DD'), - Hour: moment.tz(timezone).format('HH'), - Minute: moment.tz(timezone).format('mm'), - Second: moment.tz(timezone).format('ss'), - Timezone: moment.tz(timezone).format('z Z'), - }; - this.emit([this.helpers.returnJsonArray([resultData])]); - } + const resultData = { + timestamp: moment.tz(timezone).toISOString(), + 'Readable date': moment.tz(timezone).format('MMMM Do YYYY, h:mm:ss a'), + 'Readable time': moment.tz(timezone).format('h:mm:ss a'), + 'Day of week': moment.tz(timezone).format('dddd'), + Year: moment.tz(timezone).format('YYYY'), + Month: moment.tz(timezone).format('MMMM'), + 'Day of month': moment.tz(timezone).format('DD'), + Hour: moment.tz(timezone).format('HH'), + Minute: moment.tz(timezone).format('mm'), + Second: moment.tz(timezone).format('ss'), + Timezone: moment.tz(timezone).format('z Z'), + }; + this.emit([this.helpers.returnJsonArray([resultData])]); }; - let intervalValue = 1000; - if (rule.cronExpression) { - const cronExpression = rule.cronExpression as IDataObject; - const cronArray: string[] = []; - cronArray.push(cronExpression.value as string); - cronJobs = cronArray.map( - (cronArray) => new CronJob(cronArray, executeTrigger, undefined, true, timezone), - ); - } - if (rule.seconds) { - const seconds = rule.seconds as IDataObject; - intervalValue *= seconds.value as number; - intervalObj = setInterval(executeTrigger, intervalValue); - } - if (rule.minutes) { - const minutes = rule.minutes as IDataObject; - // @ts-ignore - intervalValue *= (60 * minutes.value) as number; - intervalObj = setInterval(executeTrigger, intervalValue); - } + for (let i = 0; i < interval.length; i++) { + let intervalValue = 1000; + if (interval[i].field === 'cronExpression') { + const cronExpression = interval[i].expression as string; + const cronArray: string[] = []; + cronArray.push(cronExpression); + cronJobs = cronArray.map( + (cronArray) => new CronJob(cronArray, executeTrigger, undefined, true, timezone), + ); + } - if (rule.hours) { - const expression: TriggerTime[] = [ - { - mode: 'everyHour', - // @ts-ignore - hour: rule.hours.value, - // @ts-ignore - minute: rule.hours.triggerAtMinute, - }, - ]; - const cronTimes = (expression || []).map(toCronExpression); - cronJobs = cronTimes.map( - (crontime) => new CronJob(crontime, executeTrigger, undefined, true, timezone), - ); - } + if (interval[i].field === 'seconds') { + const seconds = interval[i].secondsInterval as number; + intervalValue *= seconds; + intervalObj = setInterval(executeTrigger, intervalValue); + } - if (rule.days) { - const daysObject = rule.days as IDataObject; - const day = daysObject.value?.toString() as string; - const hour = daysObject.triggerAtHour?.toString() as string; - const minute = daysObject.triggerAtMinute?.toString() as string; - const cronTimes: ICronExpression = [minute, hour, `*/${day}`, '*', '*']; - const cronExpression: string = cronTimes.join(' '); - const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); - cronJobs.push(cronJob); - } + if (interval[i].field === 'minutes') { + const minutes = interval[i].minutesInterval as number; + intervalValue *= 60 * minutes; + intervalObj = setInterval(executeTrigger, intervalValue); + } - if (rule.weeks) { - const weeksObject = rule.weeks as IDataObject; + if (interval[i].field === 'hours') { + const hour = interval[i].triggerAtHour?.toString() as string; + const minute = interval[i].triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, '*', '*', '*']; + const cronExpression = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } - const days = weeksObject.triggerAtDay as IDataObject[]; - const day = days.join(',') as string; - const hour = weeksObject.triggerAtHour?.toString() as string; - const minute = weeksObject.triggerAtMinute?.toString() as string; - const cronTimes: ICronExpression = [minute, hour, '*', '*', day]; - const cronExpression: string = cronTimes.join(' '); - const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); - cronJobs.push(cronJob); - } + if (interval[i].field === 'days') { + const day = interval[i].daysInterval?.toString() as string; + const hour = interval[i].triggerAtHour?.toString() as string; + const minute = interval[i].triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, `*/${day}`, '*', '*']; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } + + if (interval[i].field === 'weeks') { + const days = interval[i].triggerAtDay as IDataObject[]; + const day = days.join(',') as string; + const hour = interval[i].triggerAtHour?.toString() as string; + const minute = interval[i].triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, '*', '*', day]; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } - if (rule.months) { - const monthsObejct = rule.months as IDataObject; - const month = monthsObejct.value?.toString() as string; - const day = monthsObejct.triggerAtDayOfMonth?.toString() as string; - const hour = monthsObejct.triggerAtHour?.toString() as string; - const minute = monthsObejct.triggerAtMinute?.toString() as string; - const cronTimes: ICronExpression = [minute, hour, day, `*/${month}`, '*']; - const cronExpression: string = cronTimes.join(' '); - const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); - cronJobs.push(cronJob); + if (interval[i].field === 'months') { + const month = interval[i].monthsInterval?.toString() as string; + const day = interval[i].triggerAtDayOfMonth?.toString() as string; + const hour = interval[i].triggerAtHour?.toString() as string; + const minute = interval[i].triggerAtMinute?.toString() as string; + const cronTimes: ICronExpression = [minute, hour, day, `*/${month}`, '*']; + const cronExpression: string = cronTimes.join(' '); + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } } async function closeFunction() { From f7d2ecb5d822fd0389d8011c66131aaced02e26a Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 14 Sep 2022 11:37:05 +0200 Subject: [PATCH 09/14] Fix auto intitialising --- .../nodes-base/nodes/Schedule/ScheduleTrigger.node.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 75636c3629652..5935f584d237d 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -39,7 +39,13 @@ export class ScheduleTrigger implements INodeType { typeOptions: { multipleValues: true, }, - default: {}, + default: { + interval: [ + { + field: 'hours', + }, + ], + }, options: [ { name: 'interval', @@ -49,7 +55,7 @@ export class ScheduleTrigger implements INodeType { displayName: 'Trigger Interval', name: 'field', type: 'options', - default: 'hours', + default: '', options: [ { name: 'Seconds', From 95c9c9d7cb3669e8a790ecd5b61837479f07319c Mon Sep 17 00:00:00 2001 From: agobrech Date: Fri, 16 Sep 2022 18:01:38 +0200 Subject: [PATCH 10/14] Deprecated interval and cron in favor of schedule node --- packages/nodes-base/nodes/Cron/Cron.node.json | 14 +---- packages/nodes-base/nodes/Cron/Cron.node.ts | 5 +- .../nodes/Interval/Interval.node.ts | 1 + .../nodes/Schedule/ScheduleTrigger.node.json | 2 +- .../nodes/Schedule/ScheduleTrigger.node.ts | 56 +++++++++++++------ 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/packages/nodes-base/nodes/Cron/Cron.node.json b/packages/nodes-base/nodes/Cron/Cron.node.json index 96487b083a225..52f4d6c80d842 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.json +++ b/packages/nodes-base/nodes/Cron/Cron.node.json @@ -3,9 +3,7 @@ "nodeVersion": "1.0", "codexVersion": "1.0", "details": "The Cron node uses Cron under the hood - a time-based job scheduler in Unix-like computer operating systems. Use this node when you want to trigger workflows periodically, especially in more complex scenarios like \"every Tuesday at 9 am\" or \"Weekdays\".", - "categories": [ - "Core Nodes" - ], + "categories": ["Core Nodes"], "resources": { "primaryDocumentation": [ { @@ -130,14 +128,8 @@ } ] }, - "alias": [ - "Time", - "Scheduler", - "Polling" - ], + "alias": ["Time", "Scheduler", "Polling", "Cron", "Interval"], "subcategories": { - "Core Nodes": [ - "Flow" - ] + "Core Nodes": ["Flow"] } } diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index 718d15f8f0d02..4607b1b3f6dfb 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -17,6 +17,7 @@ export class Cron implements INodeType { icon: 'fa:calendar', group: ['trigger', 'schedule'], version: 1, + hidden: true, description: 'Triggers the workflow at a specific time', eventTriggerDescription: '', activationMessage: @@ -69,7 +70,9 @@ export class Cron implements INodeType { const timezone = this.getTimezone(); // Start the cron-jobs - const cronJobs = cronTimes.map(cronTime => 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/nodes-base/nodes/Interval/Interval.node.ts b/packages/nodes-base/nodes/Interval/Interval.node.ts index cf36eb15af2af..57693b38fc0f0 100644 --- a/packages/nodes-base/nodes/Interval/Interval.node.ts +++ b/packages/nodes-base/nodes/Interval/Interval.node.ts @@ -13,6 +13,7 @@ export class Interval implements INodeType { icon: 'fa:hourglass', group: ['trigger', 'schedule'], version: 1, + hidden: true, description: 'Triggers the workflow in a given interval', eventTriggerDescription: '', activationMessage: diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json index 73cb130c7d37c..fcaab357f9c90 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.json @@ -11,7 +11,7 @@ ], "generic": [] }, - "alias": ["Time", "Scheduler", "Polling"], + "alias": ["Time", "Scheduler", "Polling", "Cron", "Interval"], "subcategories": { "Core Nodes": ["Flow"] } diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 5935f584d237d..20e9edcfa7245 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -1,5 +1,11 @@ import { ITriggerFunctions } from 'n8n-core'; -import { IDataObject, INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow'; +import { + IDataObject, + INodeType, + INodeTypeDescription, + ITriggerResponse, + NodeOperationError, +} from 'n8n-workflow'; import { CronJob } from 'cron'; import { ICronExpression } from './CronInterface'; @@ -55,7 +61,7 @@ export class ScheduleTrigger implements INodeType { displayName: 'Trigger Interval', name: 'field', type: 'options', - default: '', + default: 'days', options: [ { name: 'Seconds', @@ -82,7 +88,7 @@ export class ScheduleTrigger implements INodeType { value: 'months', }, { - name: 'Cron Expression', + name: 'Custom (Cron)', value: 'cronExpression', }, ], @@ -97,7 +103,7 @@ export class ScheduleTrigger implements INodeType { field: ['seconds'], }, }, - description: 'Interval value', + description: 'Number of seconds between each workflow trigger.', }, { name: 'minutesInterval', @@ -109,7 +115,7 @@ export class ScheduleTrigger implements INodeType { field: ['minutes'], }, }, - description: 'Interval value', + description: 'Number of minutes between each workflow trigger.', }, { name: 'hoursInterval', @@ -121,7 +127,7 @@ export class ScheduleTrigger implements INodeType { }, }, default: 1, - description: 'Interval value', + description: 'Number of hours between each workflow trigger', }, { name: 'daysInterval', @@ -133,7 +139,7 @@ export class ScheduleTrigger implements INodeType { }, }, default: 1, - description: 'Interval value', + description: 'Number of days between each workflow trigger', }, { name: 'weeksInterval', @@ -370,20 +376,30 @@ export class ScheduleTrigger implements INodeType { }, description: 'The minute past the hour to trigger (0-59)', }, + { + displayName: + 'You can find help generating your cron expression here', + name: 'notice', + type: 'notice', + displayOptions: { + show: { + field: ['cronExpression'], + }, + }, + default: '', + }, { name: 'expression', displayName: 'Expression', type: 'string', default: '', - placeholder: '5 12 26 1-30 mon-sun', + placeholder: 'eg. 0 15 * 1 sun', displayOptions: { show: { field: ['cronExpression'], }, }, - description: - 'You can find help generating your cron expression here', - hint: '[Minute] [Hour] [Day of Month] [Month] [Day of Week]', + hint: 'Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]', }, ], }, @@ -397,11 +413,11 @@ export class ScheduleTrigger implements INodeType { const interval = rule.interval as IDataObject[]; const timezone = this.getTimezone(); const date = moment.tz(timezone).week(); - let cronJobs: CronJob[] = []; + const cronJobs: CronJob[] = []; let intervalObj: NodeJS.Timeout; const executeTrigger = () => { const resultData = { - timestamp: moment.tz(timezone).toISOString(), + timestamp: moment.tz(timezone).toISOString(true), 'Readable date': moment.tz(timezone).format('MMMM Do YYYY, h:mm:ss a'), 'Readable time': moment.tz(timezone).format('h:mm:ss a'), 'Day of week': moment.tz(timezone).format('dddd'), @@ -419,11 +435,15 @@ export class ScheduleTrigger implements INodeType { let intervalValue = 1000; if (interval[i].field === 'cronExpression') { const cronExpression = interval[i].expression as string; - const cronArray: string[] = []; - cronArray.push(cronExpression); - cronJobs = cronArray.map( - (cronArray) => new CronJob(cronArray, executeTrigger, undefined, true, timezone), - ); + try { + const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); + cronJobs.push(cronJob); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Invalid cron expression', { + description: + 'More information on how to build them here', + }); + } } if (interval[i].field === 'seconds') { From 062a31c51aa5d9b531c97493a6b97ad489d83949 Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 20 Sep 2022 15:24:35 +0200 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=90=9B=20Ui=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 20e9edcfa7245..48a3ce3b6527a 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -18,7 +18,7 @@ export class ScheduleTrigger implements INodeType { icon: 'file:schedule.svg', group: ['trigger', 'schedule'], version: 1, - description: 'Triggers the workflow in a given schedule', + description: 'Triggers the workflow on a given schedule', eventTriggerDescription: '', activationMessage: 'Your schedule trigger will now trigger executions on the schedule you have defined.', @@ -48,7 +48,7 @@ export class ScheduleTrigger implements INodeType { default: { interval: [ { - field: 'hours', + field: 'days', }, ], }, @@ -440,8 +440,7 @@ export class ScheduleTrigger implements INodeType { cronJobs.push(cronJob); } catch (error) { throw new NodeOperationError(this.getNode(), 'Invalid cron expression', { - description: - 'More information on how to build them here', + description: 'More information on how to build them at http://www.cronmaker.com', }); } } From 3669fcf707ac954fba583a9be57acf9e12b6ab6d Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 28 Sep 2022 15:08:37 +0200 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=90=9B=20Fix=20issue=20with=20week?= =?UTF-8?q?=20intervals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 48a3ce3b6527a..5d7a8f00fc402 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -460,7 +460,8 @@ export class ScheduleTrigger implements INodeType { if (interval[i].field === 'hours') { const hour = interval[i].triggerAtHour?.toString() as string; const minute = interval[i].triggerAtMinute?.toString() as string; - const cronTimes: ICronExpression = [minute, hour, '*', '*', '*']; + const week = interval[i].triggerAtWeek as number; + const cronTimes: ICronExpression = [minute, hour, `*/${week * 7}`, '*', '*']; const cronExpression = cronTimes.join(' '); const cronJob = new CronJob(cronExpression, executeTrigger, undefined, true, timezone); cronJobs.push(cronJob); From edb70c00aa0aa1ce6e7857e289a2684372b5fb75 Mon Sep 17 00:00:00 2001 From: agobrech Date: Wed, 28 Sep 2022 15:58:09 +0200 Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=9A=A8=20Lint=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/Schedule/ScheduleTrigger.node.ts | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 5d7a8f00fc402..399b21a65602a 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -64,38 +64,38 @@ export class ScheduleTrigger implements INodeType { default: 'days', options: [ { - name: 'Seconds', - value: 'seconds', + name: 'Custom (Cron)', + value: 'cronExpression', }, { - name: 'Minutes', - value: 'minutes', + name: 'Days', + value: 'days', }, { name: 'Hours', value: 'hours', }, { - name: 'Days', - value: 'days', - }, - { - name: 'Weeks', - value: 'weeks', + name: 'Minutes', + value: 'minutes', }, { name: 'Months', value: 'months', }, { - name: 'Custom (Cron)', - value: 'cronExpression', + name: 'Seconds', + value: 'seconds', + }, + { + name: 'Weeks', + value: 'weeks', }, ], }, { - name: 'secondsInterval', displayName: 'Seconds Between Triggers', + name: 'secondsInterval', type: 'number', default: 30, displayOptions: { @@ -103,11 +103,11 @@ export class ScheduleTrigger implements INodeType { field: ['seconds'], }, }, - description: 'Number of seconds between each workflow trigger.', + description: 'Number of seconds between each workflow trigger', }, { - name: 'minutesInterval', displayName: 'Minutes Between Triggers', + name: 'minutesInterval', type: 'number', default: 5, displayOptions: { @@ -115,11 +115,11 @@ export class ScheduleTrigger implements INodeType { field: ['minutes'], }, }, - description: 'Number of minutes between each workflow trigger.', + description: 'Number of minutes between each workflow trigger', }, { - name: 'hoursInterval', displayName: 'Hours Between Triggers', + name: 'hoursInterval', type: 'number', displayOptions: { show: { @@ -130,8 +130,8 @@ export class ScheduleTrigger implements INodeType { description: 'Number of hours between each workflow trigger', }, { - name: 'daysInterval', displayName: 'Days Between Triggers', + name: 'daysInterval', type: 'number', displayOptions: { show: { @@ -142,8 +142,8 @@ export class ScheduleTrigger implements INodeType { description: 'Number of days between each workflow trigger', }, { - name: 'weeksInterval', displayName: 'Weeks Between Triggers', + name: 'weeksInterval', type: 'number', displayOptions: { show: { @@ -154,8 +154,8 @@ export class ScheduleTrigger implements INodeType { description: 'Would run every week unless specified otherwise', }, { - name: 'monthsInterval', displayName: 'Months Between Triggers', + name: 'monthsInterval', type: 'number', displayOptions: { show: { @@ -166,8 +166,8 @@ export class ScheduleTrigger implements INodeType { description: 'Would run every month unless specified otherwise', }, { - name: 'triggerAtDayOfMonth', displayName: 'Trigger at Day of Month', + name: 'triggerAtDayOfMonth', type: 'number', displayOptions: { show: { @@ -183,8 +183,8 @@ export class ScheduleTrigger implements INodeType { hint: 'If a month doesn’t have this day, the node won’t trigger', }, { - name: 'triggerAtDay', displayName: 'Trigger on Weekdays', + name: 'triggerAtDay', type: 'multiOptions', displayOptions: { show: { @@ -227,8 +227,8 @@ export class ScheduleTrigger implements INodeType { default: [7], }, { - name: 'triggerAtHour', displayName: 'Trigger at Hour', + name: 'triggerAtHour', type: 'options', default: 0, displayOptions: { @@ -361,8 +361,8 @@ export class ScheduleTrigger implements INodeType { description: 'The hour of the day to trigger', }, { - name: 'triggerAtMinute', displayName: 'Trigger at Minute', + name: 'triggerAtMinute', type: 'number', default: 0, displayOptions: { @@ -389,8 +389,8 @@ export class ScheduleTrigger implements INodeType { default: '', }, { - name: 'expression', displayName: 'Expression', + name: 'expression', type: 'string', default: '', placeholder: 'eg. 0 15 * 1 sun', From da02c2fbf8d869ffe8a784f1932bfd1bc72d82ba Mon Sep 17 00:00:00 2001 From: agobrech Date: Tue, 11 Oct 2022 22:56:49 +0200 Subject: [PATCH 14/14] change order of days in the week to chronological order --- .../nodes/Schedule/ScheduleTrigger.node.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts index 399b21a65602a..34833f2e8b41c 100644 --- a/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts +++ b/packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts @@ -195,33 +195,34 @@ export class ScheduleTrigger implements INodeType { maxValue: 7, }, options: [ - { - name: 'Friday', - value: 5, - }, { name: 'Monday', value: 1, }, { - name: 'Saturday', - value: 6, + name: 'Tuesday', + value: 2, }, { - name: 'Sunday', - value: 7, + name: 'Wednesday', + value: 3, }, { name: 'Thursday', value: 4, }, { - name: 'Tuesday', - value: 2, + name: 'Friday', + value: 5, }, + { - name: 'Wednesday', - value: 3, + name: 'Saturday', + value: 6, + }, + { + name: 'Sunday', + value: 7, }, ], default: [7],