2023-03-09 09:13:15 -08:00
import type {
ITriggerFunctions ,
IDataObject ,
INodeType ,
INodeTypeDescription ,
ITriggerResponse ,
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { NodeOperationError } from 'n8n-workflow' ;
2022-10-18 04:59:17 -07:00
import { CronJob } from 'cron' ;
2024-01-02 03:50:31 -08:00
import moment from 'moment-timezone' ;
2023-02-01 13:53:05 -08:00
import type { IRecurencyRule } from './SchedulerInterface' ;
2024-04-18 02:57:36 -07:00
import { addFallbackValue , convertToUnixFormat , recurencyCheck } from './GenericFunctions' ;
2022-10-18 04:59:17 -07:00
export class ScheduleTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'Schedule Trigger' ,
name : 'scheduleTrigger' ,
2022-10-18 06:45:55 -07:00
icon : 'fa:clock' ,
2022-10-18 04:59:17 -07:00
group : [ 'trigger' , 'schedule' ] ,
2024-04-18 02:57:36 -07:00
version : [ 1 , 1.1 , 1.2 ] ,
2022-10-18 04:59:17 -07:00
description : 'Triggers the workflow on a given schedule' ,
eventTriggerDescription : '' ,
activationMessage :
'Your schedule trigger will now trigger executions on the schedule you have defined.' ,
defaults : {
name : 'Schedule Trigger' ,
2022-10-18 06:45:55 -07:00
color : '#31C49F' ,
2022-10-18 04:59:17 -07:00
} ,
2023-08-01 06:32:33 -07:00
2022-10-18 04:59:17 -07:00
inputs : [ ] ,
outputs : [ 'main' ] ,
properties : [
{
displayName :
2024-01-15 06:12:43 -08:00
'This workflow will run on the schedule you define here once you <a data-key="activate">activate</a> it.<br><br>For testing, you can also trigger it manually: by going back to the canvas and clicking \'test workflow\'' ,
2022-10-18 04:59:17 -07:00
name : 'notice' ,
type : 'notice' ,
default : '' ,
} ,
{
displayName : 'Trigger Rules' ,
name : 'rule' ,
placeholder : 'Add Rule' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
} ,
default : {
interval : [
{
field : 'days' ,
} ,
] ,
} ,
options : [
{
name : 'interval' ,
displayName : 'Trigger Interval' ,
values : [
{
displayName : 'Trigger Interval' ,
name : 'field' ,
type : 'options' ,
default : 'days' ,
2022-10-19 00:36:25 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
2022-10-18 04:59:17 -07:00
options : [
{
2022-10-18 08:47:26 -07:00
name : 'Seconds' ,
value : 'seconds' ,
2022-10-18 04:59:17 -07:00
} ,
{
2022-10-18 08:47:26 -07:00
name : 'Minutes' ,
value : 'minutes' ,
2022-10-18 04:59:17 -07:00
} ,
{
name : 'Hours' ,
value : 'hours' ,
} ,
{
2022-10-18 08:47:26 -07:00
name : 'Days' ,
value : 'days' ,
2022-10-18 04:59:17 -07:00
} ,
{
2022-10-18 08:47:26 -07:00
name : 'Weeks' ,
value : 'weeks' ,
2022-10-18 04:59:17 -07:00
} ,
{
2022-10-18 08:47:26 -07:00
name : 'Months' ,
value : 'months' ,
2022-10-18 04:59:17 -07:00
} ,
{
2022-10-18 08:47:26 -07:00
name : 'Custom (Cron)' ,
value : 'cronExpression' ,
2022-10-18 04:59:17 -07:00
} ,
] ,
} ,
{
displayName : 'Seconds Between Triggers' ,
name : 'secondsInterval' ,
type : 'number' ,
default : 30 ,
displayOptions : {
show : {
field : [ 'seconds' ] ,
} ,
} ,
description : 'Number of seconds between each workflow trigger' ,
} ,
{
displayName : 'Minutes Between Triggers' ,
name : 'minutesInterval' ,
type : 'number' ,
default : 5 ,
displayOptions : {
show : {
field : [ 'minutes' ] ,
} ,
} ,
description : 'Number of minutes between each workflow trigger' ,
} ,
{
displayName : 'Hours Between Triggers' ,
name : 'hoursInterval' ,
type : 'number' ,
displayOptions : {
show : {
field : [ 'hours' ] ,
} ,
} ,
default : 1 ,
description : 'Number of hours between each workflow trigger' ,
} ,
{
displayName : 'Days Between Triggers' ,
name : 'daysInterval' ,
type : 'number' ,
displayOptions : {
show : {
field : [ 'days' ] ,
} ,
} ,
default : 1 ,
description : 'Number of days between each workflow trigger' ,
} ,
{
displayName : 'Weeks Between Triggers' ,
name : 'weeksInterval' ,
type : 'number' ,
displayOptions : {
show : {
field : [ 'weeks' ] ,
} ,
} ,
default : 1 ,
description : 'Would run every week unless specified otherwise' ,
} ,
{
displayName : 'Months Between Triggers' ,
name : 'monthsInterval' ,
type : 'number' ,
displayOptions : {
show : {
field : [ 'months' ] ,
} ,
} ,
default : 1 ,
description : 'Would run every month unless specified otherwise' ,
} ,
{
displayName : 'Trigger at Day of Month' ,
name : 'triggerAtDayOfMonth' ,
type : 'number' ,
displayOptions : {
show : {
field : [ 'months' ] ,
} ,
} ,
typeOptions : {
minValue : 1 ,
maxValue : 31 ,
} ,
default : 1 ,
description : 'The day of the month to trigger (1-31)' ,
hint : 'If a month doesn’ t have this day, the node won’ t trigger' ,
} ,
{
displayName : 'Trigger on Weekdays' ,
name : 'triggerAtDay' ,
type : 'multiOptions' ,
displayOptions : {
show : {
field : [ 'weeks' ] ,
} ,
} ,
typeOptions : {
maxValue : 7 ,
} ,
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' ,
2022-10-19 08:28:41 -07:00
value : 0 ,
2022-10-18 04:59:17 -07:00
} ,
] ,
2022-10-19 08:28:41 -07:00
default : [ 0 ] ,
2022-10-18 04:59:17 -07:00
} ,
{
displayName : 'Trigger at Hour' ,
name : 'triggerAtHour' ,
type : 'options' ,
default : 0 ,
displayOptions : {
show : {
field : [ 'days' , 'weeks' , 'months' ] ,
} ,
} ,
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 ,
} ,
] ,
description : 'The hour of the day to trigger' ,
} ,
{
displayName : 'Trigger at Minute' ,
name : 'triggerAtMinute' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
field : [ 'hours' , 'days' , 'weeks' , 'months' ] ,
} ,
} ,
typeOptions : {
minValue : 0 ,
maxValue : 59 ,
} ,
description : 'The minute past the hour to trigger (0-59)' ,
} ,
{
displayName :
2023-03-30 04:07:16 -07:00
'You can find help generating your cron expression <a href="https://crontab.guru/examples.html" target="_blank">here</a>' ,
2022-10-18 04:59:17 -07:00
name : 'notice' ,
type : 'notice' ,
displayOptions : {
show : {
field : [ 'cronExpression' ] ,
} ,
} ,
default : '' ,
} ,
{
displayName : 'Expression' ,
name : 'expression' ,
type : 'string' ,
default : '' ,
placeholder : 'eg. 0 15 * 1 sun' ,
displayOptions : {
show : {
field : [ 'cronExpression' ] ,
} ,
} ,
hint : 'Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]' ,
} ,
] ,
} ,
] ,
} ,
] ,
} ;
async trigger ( this : ITriggerFunctions ) : Promise < ITriggerResponse > {
const rule = this . getNodeParameter ( 'rule' , [ ] ) as IDataObject ;
const interval = rule . interval as IDataObject [ ] ;
const timezone = this . getTimezone ( ) ;
2024-04-18 02:57:36 -07:00
const nodeVersion = this . getNode ( ) . typeVersion ;
2022-10-18 04:59:17 -07:00
const cronJobs : CronJob [ ] = [ ] ;
2022-10-19 08:28:41 -07:00
const intervalArr : NodeJS.Timeout [ ] = [ ] ;
2022-11-21 01:32:23 -08:00
const staticData = this . getWorkflowStaticData ( 'node' ) as {
recurrencyRules : number [ ] ;
} ;
if ( ! staticData . recurrencyRules ) {
staticData . recurrencyRules = [ ] ;
}
2024-04-18 02:57:36 -07:00
const fallbackToZero = addFallbackValue ( nodeVersion >= 1.2 , '0' ) ;
2023-02-01 13:53:05 -08:00
const executeTrigger = async ( recurency : IRecurencyRule ) = > {
2022-10-18 04:59:17 -07:00
const resultData = {
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' ) ,
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' ) ,
} ;
2022-11-21 01:32:23 -08:00
2023-02-01 13:53:05 -08:00
if ( ! recurency . activated ) {
this . emit ( [ this . helpers . returnJsonArray ( [ resultData ] ) ] ) ;
} else {
if ( recurencyCheck ( recurency , staticData . recurrencyRules , timezone ) ) {
2022-11-21 01:32:23 -08:00
this . emit ( [ this . helpers . returnJsonArray ( [ resultData ] ) ] ) ;
}
}
2022-10-18 04:59:17 -07:00
} ;
2022-11-21 01:32:23 -08:00
2022-10-18 04:59:17 -07:00
for ( let i = 0 ; i < interval . length ; i ++ ) {
let intervalValue = 1000 ;
if ( interval [ i ] . field === 'cronExpression' ) {
2024-04-18 02:57:36 -07:00
if ( nodeVersion > 1 ) {
2023-06-13 09:57:17 -07:00
// ! Remove this part if we use a cron library that follows unix cron expression
convertToUnixFormat ( interval [ i ] ) ;
}
2022-10-18 04:59:17 -07:00
const cronExpression = interval [ i ] . expression as string ;
try {
2023-02-01 13:53:05 -08:00
const cronJob = new CronJob (
cronExpression ,
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
undefined ,
true ,
timezone ,
) ;
2022-10-18 04:59:17 -07:00
cronJobs . push ( cronJob ) ;
} catch ( error ) {
throw new NodeOperationError ( this . getNode ( ) , 'Invalid cron expression' , {
2023-03-30 04:07:16 -07:00
description : 'More information on how to build them at https://crontab.guru/' ,
2022-10-18 04:59:17 -07:00
} ) ;
}
}
if ( interval [ i ] . field === 'seconds' ) {
const seconds = interval [ i ] . secondsInterval as number ;
intervalValue *= seconds ;
2023-02-01 13:53:05 -08:00
const intervalObj = setInterval (
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
intervalValue ,
) as NodeJS . Timeout ;
2022-10-19 08:28:41 -07:00
intervalArr . push ( intervalObj ) ;
2022-10-18 04:59:17 -07:00
}
if ( interval [ i ] . field === 'minutes' ) {
const minutes = interval [ i ] . minutesInterval as number ;
intervalValue *= 60 * minutes ;
2023-02-01 13:53:05 -08:00
const intervalObj = setInterval (
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
intervalValue ,
) as NodeJS . Timeout ;
2022-10-19 08:28:41 -07:00
intervalArr . push ( intervalObj ) ;
2022-10-18 04:59:17 -07:00
}
if ( interval [ i ] . field === 'hours' ) {
2023-02-01 13:53:05 -08:00
const hour = interval [ i ] . hoursInterval as number ;
2024-04-18 02:57:36 -07:00
const minute = fallbackToZero ( interval [ i ] . triggerAtMinute ? . toString ( ) as string ) ;
2023-02-01 13:53:05 -08:00
const cronTimes : string [ ] = [ minute , '*' , '*' , '*' , '*' ] ;
2022-10-19 08:28:41 -07:00
const cronExpression : string = cronTimes . join ( ' ' ) ;
2023-02-01 13:53:05 -08:00
if ( hour === 1 ) {
const cronJob = new CronJob (
cronExpression ,
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
} else {
const cronJob = new CronJob (
cronExpression ,
async ( ) = >
2024-01-17 07:08:50 -08:00
await executeTrigger ( {
2023-02-01 13:53:05 -08:00
activated : true ,
index : i ,
intervalSize : hour ,
typeInterval : 'hours' ,
} as IRecurencyRule ) ,
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
}
2022-10-18 04:59:17 -07:00
}
if ( interval [ i ] . field === 'days' ) {
2023-02-01 13:53:05 -08:00
const day = interval [ i ] . daysInterval as number ;
2022-10-18 04:59:17 -07:00
const hour = interval [ i ] . triggerAtHour ? . toString ( ) as string ;
2024-04-18 02:57:36 -07:00
const minute = fallbackToZero ( interval [ i ] . triggerAtMinute ? . toString ( ) as string ) ;
2023-02-01 13:53:05 -08:00
const cronTimes : string [ ] = [ minute , hour , '*' , '*' , '*' ] ;
2022-10-18 04:59:17 -07:00
const cronExpression : string = cronTimes . join ( ' ' ) ;
2023-02-01 13:53:05 -08:00
if ( day === 1 ) {
const cronJob = new CronJob (
cronExpression ,
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
} else {
const cronJob = new CronJob (
cronExpression ,
async ( ) = >
2024-01-17 07:08:50 -08:00
await executeTrigger ( {
2023-02-01 13:53:05 -08:00
activated : true ,
index : i ,
intervalSize : day ,
typeInterval : 'days' ,
} as IRecurencyRule ) ,
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
}
2022-10-18 04:59:17 -07:00
}
if ( interval [ i ] . field === 'weeks' ) {
const hour = interval [ i ] . triggerAtHour ? . toString ( ) as string ;
2024-04-18 02:57:36 -07:00
const minute = fallbackToZero ( interval [ i ] . triggerAtMinute ? . toString ( ) as string ) ;
2022-10-19 08:28:41 -07:00
const week = interval [ i ] . weeksInterval as number ;
const days = interval [ i ] . triggerAtDay as IDataObject [ ] ;
2022-12-02 12:54:28 -08:00
const day = days . length === 0 ? '*' : days . join ( ',' ) ;
2022-11-21 01:32:23 -08:00
const cronTimes : string [ ] = [ minute , hour , '*' , '*' , day ] ;
2022-10-19 08:28:41 -07:00
const cronExpression = cronTimes . join ( ' ' ) ;
2022-11-21 01:32:23 -08:00
if ( week === 1 ) {
2023-02-01 13:53:05 -08:00
const cronJob = new CronJob (
cronExpression ,
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
undefined ,
true ,
timezone ,
) ;
2022-11-21 01:32:23 -08:00
cronJobs . push ( cronJob ) ;
} else {
const cronJob = new CronJob (
cronExpression ,
2023-02-01 13:53:05 -08:00
async ( ) = >
2024-01-17 07:08:50 -08:00
await executeTrigger ( {
2023-02-01 13:53:05 -08:00
activated : true ,
index : i ,
intervalSize : week ,
typeInterval : 'weeks' ,
} as IRecurencyRule ) ,
2022-11-21 01:32:23 -08:00
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
}
2022-10-18 04:59:17 -07:00
}
if ( interval [ i ] . field === 'months' ) {
2023-02-01 13:53:05 -08:00
const month = interval [ i ] . monthsInterval ;
2022-10-18 04:59:17 -07:00
const day = interval [ i ] . triggerAtDayOfMonth ? . toString ( ) as string ;
const hour = interval [ i ] . triggerAtHour ? . toString ( ) as string ;
2024-04-18 02:57:36 -07:00
const minute = fallbackToZero ( interval [ i ] . triggerAtMinute ? . toString ( ) as string ) ;
2023-02-01 13:53:05 -08:00
const cronTimes : string [ ] = [ minute , hour , day , '*' , '*' ] ;
2022-10-18 04:59:17 -07:00
const cronExpression : string = cronTimes . join ( ' ' ) ;
2023-02-01 13:53:05 -08:00
if ( month === 1 ) {
const cronJob = new CronJob (
cronExpression ,
2024-01-17 07:08:50 -08:00
async ( ) = > await executeTrigger ( { activated : false } as IRecurencyRule ) ,
2023-02-01 13:53:05 -08:00
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
} else {
const cronJob = new CronJob (
cronExpression ,
async ( ) = >
2024-01-17 07:08:50 -08:00
await executeTrigger ( {
2023-02-01 13:53:05 -08:00
activated : true ,
index : i ,
intervalSize : month ,
typeInterval : 'months' ,
} as IRecurencyRule ) ,
undefined ,
true ,
timezone ,
) ;
cronJobs . push ( cronJob ) ;
}
2022-10-18 04:59:17 -07:00
}
}
async function closeFunction() {
for ( const cronJob of cronJobs ) {
cronJob . stop ( ) ;
}
2022-12-02 12:54:28 -08:00
for ( const entry of intervalArr ) {
clearInterval ( entry ) ;
2022-10-19 08:28:41 -07:00
}
2022-10-18 04:59:17 -07:00
}
async function manualTriggerFunction() {
2023-02-01 13:53:05 -08:00
void executeTrigger ( { activated : false } as IRecurencyRule ) ;
2022-10-18 04:59:17 -07:00
}
return {
closeFunction ,
manualTriggerFunction ,
} ;
}
}