sync upstream changes

This commit is contained in:
Ricardo Espinoza 2020-01-02 17:36:24 -05:00
commit d9cc3fce64
20 changed files with 914 additions and 82 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n", "name": "n8n",
"version": "0.41.0", "version": "0.42.0",
"description": "n8n Workflow Automation Tool", "description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -92,10 +92,10 @@
"localtunnel": "^2.0.0", "localtunnel": "^2.0.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"mongodb": "^3.2.3", "mongodb": "^3.2.3",
"n8n-core": "~0.18.0", "n8n-core": "~0.19.0",
"n8n-editor-ui": "~0.29.0", "n8n-editor-ui": "~0.30.0",
"n8n-nodes-base": "~0.36.0", "n8n-nodes-base": "~0.37.0",
"n8n-workflow": "~0.18.0", "n8n-workflow": "~0.19.0",
"open": "^7.0.0", "open": "^7.0.0",
"pg": "^7.11.0", "pg": "^7.11.0",
"request-promise-native": "^1.0.7", "request-promise-native": "^1.0.7",

View file

@ -21,6 +21,7 @@ import {
import { import {
IExecuteData, IExecuteData,
IGetExecutePollFunctions,
IGetExecuteTriggerFunctions, IGetExecuteTriggerFunctions,
INode, INode,
INodeExecutionData, INodeExecutionData,
@ -218,6 +219,73 @@ export class ActiveWorkflowRunner {
} }
/**
* Runs the given workflow
*
* @param {IWorkflowDb} workflowData
* @param {INode} node
* @param {INodeExecutionData[][]} data
* @param {IWorkflowExecuteAdditionalDataWorkflow} additionalData
* @param {WorkflowExecuteMode} mode
* @returns
* @memberof ActiveWorkflowRunner
*/
runWorkflow(workflowData: IWorkflowDb, node: INode, data: INodeExecutionData[][], additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode) {
const nodeExecutionStack: IExecuteData[] = [
{
node,
data: {
main: data,
}
}
];
const executionData: IRunExecutionData = {
startData: {},
resultData: {
runData: {},
},
executionData: {
contextData: {},
nodeExecutionStack,
waitingExecution: {},
},
};
// Start the workflow
const runData: IWorkflowExecutionDataProcess = {
credentials: additionalData.credentials,
executionMode: mode,
executionData,
workflowData,
};
const workflowRunner = new WorkflowRunner();
return workflowRunner.run(runData, true);
}
/**
* Return poll function which gets the global functions from n8n-core
* and overwrites the __emit to be able to start it in subprocess
*
* @param {IWorkflowDb} workflowData
* @param {IWorkflowExecuteAdditionalDataWorkflow} additionalData
* @param {WorkflowExecuteMode} mode
* @returns {IGetExecutePollFunctions}
* @memberof ActiveWorkflowRunner
*/
getExecutePollFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): IGetExecutePollFunctions {
return ((workflow: Workflow, node: INode) => {
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode);
returnFunctions.__emit = (data: INodeExecutionData[][]): void => {
this.runWorkflow(workflowData, node, data, additionalData, mode);
};
return returnFunctions;
});
}
/** /**
* Return trigger function which gets the global functions from n8n-core * Return trigger function which gets the global functions from n8n-core
* and overwrites the emit to be able to start it in subprocess * and overwrites the emit to be able to start it in subprocess
@ -232,43 +300,13 @@ export class ActiveWorkflowRunner {
return ((workflow: Workflow, node: INode) => { return ((workflow: Workflow, node: INode) => {
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode); const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode);
returnFunctions.emit = (data: INodeExecutionData[][]): void => { returnFunctions.emit = (data: INodeExecutionData[][]): void => {
this.runWorkflow(workflowData, node, data, additionalData, mode);
const nodeExecutionStack: IExecuteData[] = [
{
node,
data: {
main: data,
}
}
];
const executionData: IRunExecutionData = {
startData: {},
resultData: {
runData: {},
},
executionData: {
contextData: {},
nodeExecutionStack,
waitingExecution: {},
},
};
// Start the workflow
const runData: IWorkflowExecutionDataProcess = {
credentials: additionalData.credentials,
executionMode: mode,
executionData,
workflowData,
};
const workflowRunner = new WorkflowRunner();
workflowRunner.run(runData, true);
}; };
return returnFunctions; return returnFunctions;
}); });
} }
/** /**
* Makes a workflow active * Makes a workflow active
* *
@ -303,10 +341,11 @@ export class ActiveWorkflowRunner {
const credentials = await WorkflowCredentials(workflowData.nodes); const credentials = await WorkflowCredentials(workflowData.nodes);
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials); const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode); const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode);
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode);
// Add the workflows which have webhooks defined // Add the workflows which have webhooks defined
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode); await this.addWorkflowWebhooks(workflowInstance, additionalData, mode);
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions); await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions);
if (this.activationErrors[workflowId] !== undefined) { if (this.activationErrors[workflowId] !== undefined) {
// If there were any activation errors delete them // If there were any activation errors delete them

View file

@ -2,6 +2,7 @@ import {
INodeType, INodeType,
INodeTypes, INodeTypes,
INodeTypeData, INodeTypeData,
NodeHelpers,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -11,6 +12,15 @@ class NodeTypesClass implements INodeTypes {
async init(nodeTypes: INodeTypeData): Promise<void> { async init(nodeTypes: INodeTypeData): Promise<void> {
// Some nodeTypes need to get special parameters applied like the
// polling nodes the polling times
for (const nodeTypeData of Object.values(nodeTypes)) {
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type)
if (applyParameters.length) {
nodeTypeData.type.description.properties.unshift.apply(nodeTypeData.type.description.properties, applyParameters);
}
}
this.nodeTypes = nodeTypes; this.nodeTypes = nodeTypes;
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-core", "name": "n8n-core",
"version": "0.18.0", "version": "0.19.0",
"description": "Core functionality of n8n", "description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -39,10 +39,11 @@
"typescript": "~3.7.4" "typescript": "~3.7.4"
}, },
"dependencies": { "dependencies": {
"cron": "^1.7.2",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"mmmagic": "^0.5.2", "mmmagic": "^0.5.2",
"n8n-workflow": "~0.18.0", "n8n-workflow": "~0.19.0",
"request-promise-native": "^1.0.7" "request-promise-native": "^1.0.7"
}, },
"jest": { "jest": {

View file

@ -1,19 +1,23 @@
import { CronJob } from 'cron';
import { import {
IGetExecutePollFunctions,
IGetExecuteTriggerFunctions, IGetExecuteTriggerFunctions,
INode,
IPollResponse,
ITriggerResponse, ITriggerResponse,
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
Workflow, Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import {
export interface WorkflowData { ITriggerTime,
workflow: Workflow; IWorkflowData,
triggerResponse?: ITriggerResponse; } from './';
}
export class ActiveWorkflows { export class ActiveWorkflows {
private workflowData: { private workflowData: {
[key: string]: WorkflowData; [key: string]: IWorkflowData;
} = {}; } = {};
@ -48,7 +52,7 @@ export class ActiveWorkflows {
* @returns {(WorkflowData | undefined)} * @returns {(WorkflowData | undefined)}
* @memberof ActiveWorkflows * @memberof ActiveWorkflows
*/ */
get(id: string): WorkflowData | undefined { get(id: string): IWorkflowData | undefined {
return this.workflowData[id]; return this.workflowData[id];
} }
@ -62,7 +66,7 @@ export class ActiveWorkflows {
* @returns {Promise<void>} * @returns {Promise<void>}
* @memberof ActiveWorkflows * @memberof ActiveWorkflows
*/ */
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions): Promise<void> { async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
console.log('ADD ID (active): ' + id); console.log('ADD ID (active): ' + id);
this.workflowData[id] = { this.workflowData[id] = {
@ -78,9 +82,118 @@ export class ActiveWorkflows {
this.workflowData[id].triggerResponse = triggerResponse; this.workflowData[id].triggerResponse = triggerResponse;
} }
} }
const pollNodes = workflow.getPollNodes();
for (const pollNode of pollNodes) {
this.workflowData[id].pollResponse = await this.activatePolling(pollNode, workflow, additionalData, getPollFunctions);
}
} }
/**
* Activates polling for the given node
*
* @param {INode} node
* @param {Workflow} workflow
* @param {IWorkflowExecuteAdditionalData} additionalData
* @param {IGetExecutePollFunctions} getPollFunctions
* @returns {Promise<IPollResponse>}
* @memberof ActiveWorkflows
*/
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions): Promise<IPollResponse> {
const mode = 'trigger';
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode);
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
item: ITriggerTime[];
};
// 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);
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(' '));
}
}
// The trigger function to execute when the cron-time got reached
const executeTrigger = async () => {
const pollResponse = await workflow.runPoll(node, pollFunctions);
if (pollResponse !== null) {
// TODO: Run workflow
pollFunctions.__emit(pollResponse);
}
};
// Execute the trigger directly to be able to know if it works
await executeTrigger();
const timezone = pollFunctions.getTimezone();
// Start the cron-jobs
const cronJobs: CronJob[] = [];
for (const cronTime of cronTimes) {
cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone));
}
// Stop the cron-jobs
async function closeFunction() {
for (const cronJob of cronJobs) {
cronJob.stop();
}
}
return {
closeFunction,
};
}
/** /**
* Makes a workflow inactive * Makes a workflow inactive
@ -103,6 +216,10 @@ export class ActiveWorkflows {
await workflowData.triggerResponse.closeFunction(); await workflowData.triggerResponse.closeFunction();
} }
if (workflowData.pollResponse && workflowData.pollResponse.closeFunction) {
await workflowData.pollResponse.closeFunction();
}
delete this.workflowData[id]; delete this.workflowData[id];
} }

View file

@ -8,13 +8,16 @@ import {
ILoadOptionsFunctions as ILoadOptionsFunctionsBase, ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
IPollFunctions as IPollFunctionsBase,
IPollResponse,
ITriggerFunctions as ITriggerFunctionsBase, ITriggerFunctions as ITriggerFunctionsBase,
ITriggerResponse,
IWebhookFunctions as IWebhookFunctionsBase, IWebhookFunctions as IWebhookFunctionsBase,
IWorkflowSettings as IWorkflowSettingsWorkflow, IWorkflowSettings as IWorkflowSettingsWorkflow,
Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as request from 'request';
import * as requestPromise from 'request-promise-native'; import * as requestPromise from 'request-promise-native';
interface Constructable<T> { interface Constructable<T> {
@ -31,7 +34,7 @@ export interface IProcessMessage {
export interface IExecuteFunctions extends IExecuteFunctionsBase { export interface IExecuteFunctions extends IExecuteFunctionsBase {
helpers: { helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>; prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: request.RequestAPI<requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl>, request: requestPromise.RequestPromiseAPI,
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
}; };
} }
@ -40,7 +43,16 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase {
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase { export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
helpers: { helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>; prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: request.RequestAPI < requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl >, request: requestPromise.RequestPromiseAPI,
};
}
export interface IPollFunctions extends IPollFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
}; };
} }
@ -48,12 +60,22 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
export interface ITriggerFunctions extends ITriggerFunctionsBase { export interface ITriggerFunctions extends ITriggerFunctionsBase {
helpers: { helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>; prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: request.RequestAPI<requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl>, request: requestPromise.RequestPromiseAPI,
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
}; };
} }
export interface ITriggerTime {
mode: string;
hour: number;
minute: number;
dayOfMonth: number;
weekeday: number;
[key: string]: string | number;
}
export interface IUserSettings { export interface IUserSettings {
encryptionKey?: string; encryptionKey?: string;
tunnelSubdomain?: string; tunnelSubdomain?: string;
@ -61,14 +83,14 @@ export interface IUserSettings {
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase { export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
helpers: { helpers: {
request?: request.RequestAPI<requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl>, request?: requestPromise.RequestPromiseAPI,
}; };
} }
export interface IHookFunctions extends IHookFunctionsBase { export interface IHookFunctions extends IHookFunctionsBase {
helpers: { helpers: {
request: request.RequestAPI<requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl>, request: requestPromise.RequestPromiseAPI,
}; };
} }
@ -76,7 +98,7 @@ export interface IHookFunctions extends IHookFunctionsBase {
export interface IWebhookFunctions extends IWebhookFunctionsBase { export interface IWebhookFunctions extends IWebhookFunctionsBase {
helpers: { helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>; prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: request.RequestAPI<requestPromise.RequestPromise, requestPromise.RequestPromiseOptions, request.RequiredUriUrl>, request: requestPromise.RequestPromiseAPI,
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
}; };
} }
@ -98,3 +120,10 @@ export interface INodeDefinitionFile {
export interface INodeInputDataConnections { export interface INodeInputDataConnections {
[key: string]: INodeExecutionData[][]; [key: string]: INodeExecutionData[][];
} }
export interface IWorkflowData {
pollResponse?: IPollResponse;
triggerResponse?: ITriggerResponse;
workflow: Workflow;
}

View file

@ -17,6 +17,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeParameters, INodeParameters,
INodeType, INodeType,
IPollFunctions,
IRunExecutionData, IRunExecutionData,
ITaskDataConnections, ITaskDataConnections,
ITriggerFunctions, ITriggerFunctions,
@ -310,6 +311,57 @@ export function getWebhookDescription(name: string, workflow: Workflow, node: IN
/**
* Returns the execute functions the poll nodes have access to.
*
* @export
* @param {Workflow} workflow
* @param {INode} node
* @param {IWorkflowExecuteAdditionalData} additionalData
* @param {WorkflowExecuteMode} mode
* @returns {ITriggerFunctions}
*/
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
export function getExecutePollFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IPollFunctions {
return ((workflow: Workflow, node: INode) => {
return {
__emit: (data: INodeExecutionData[][]): void => {
throw new Error('Overwrite NodeExecuteFunctions.getExecutePullFunctions.__emit function!');
},
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
return getCredentials(workflow, node, type, additionalData);
},
getMode: (): WorkflowExecuteMode => {
return mode;
},
getNodeParameter: (parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
const connectionInputData: INodeExecutionData[] = [];
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, fallbackValue);
},
getRestApiUrl: (): string => {
return additionalData.restApiUrl;
},
getTimezone: (): string => {
return getTimezone(workflow, additionalData);
},
getWorkflowStaticData(type: string): IDataObject {
return workflow.getStaticData(type, node);
},
helpers: {
prepareBinaryData,
request: requestPromise,
returnJsonArray,
},
};
})(workflow, node);
}
/** /**
* Returns the execute functions the trigger nodes have access to. * Returns the execute functions the trigger nodes have access to.
* *

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-editor-ui", "name": "n8n-editor-ui",
"version": "0.29.0", "version": "0.30.1",
"description": "Workflow Editor UI for n8n", "description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -43,7 +43,7 @@
"@vue/cli-plugin-eslint": "^4.1.2", "@vue/cli-plugin-eslint": "^4.1.2",
"@vue/cli-plugin-typescript": "~4.1.2", "@vue/cli-plugin-typescript": "~4.1.2",
"@vue/cli-plugin-unit-jest": "^4.1.2", "@vue/cli-plugin-unit-jest": "^4.1.2",
"@vue/cli-service": "^4.1.2", "@vue/cli-service": "^3.11.0",
"@vue/eslint-config-standard": "^5.0.1", "@vue/eslint-config-standard": "^5.0.1",
"@vue/eslint-config-typescript": "~5.0.1", "@vue/eslint-config-typescript": "~5.0.1",
"@vue/test-utils": "^1.0.0-beta.24", "@vue/test-utils": "^1.0.0-beta.24",
@ -63,7 +63,7 @@
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"n8n-workflow": "~0.18.0", "n8n-workflow": "~0.19.0",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"quill": "^2.0.0-dev.3", "quill": "^2.0.0-dev.3",

View file

@ -33,6 +33,7 @@ import {
faCogs, faCogs,
faClone, faClone,
faCloud, faCloud,
faCloudDownloadAlt,
faCopy, faCopy,
faCut, faCut,
faDotCircle, faDotCircle,
@ -106,6 +107,7 @@ library.add(faCog);
library.add(faCogs); library.add(faCogs);
library.add(faClone); library.add(faClone);
library.add(faCloud); library.add(faCloud);
library.add(faCloudDownloadAlt);
library.add(faCopy); library.add(faCopy);
library.add(faCut); library.add(faCut);
library.add(faDotCircle); library.add(faDotCircle);

View file

@ -0,0 +1,24 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class TogglApi implements ICredentialType {
name = 'togglApi';
displayName = 'Toggl API';
properties = [
{
displayName: 'Username',
name: 'username',
type: 'string' as NodePropertyTypes,
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
default: '',
},
];
}

View file

@ -55,27 +55,31 @@ export class Cron implements INodeType {
options: [ options: [
{ {
name: 'Every Minute', name: 'Every Minute',
value: 'everyMinute' value: 'everyMinute',
}, },
{ {
name: 'Every Hour', name: 'Every Hour',
value: 'everyHour' value: 'everyHour',
}, },
{ {
name: 'Every Day', name: 'Every Day',
value: 'everyDay' value: 'everyDay',
}, },
{ {
name: 'Every Week', name: 'Every Week',
value: 'everyWeek' value: 'everyWeek',
}, },
{ {
name: 'Every Month', name: 'Every Month',
value: 'everyMonth' value: 'everyMonth',
},
{
name: 'Every X',
value: 'everyX',
}, },
{ {
name: 'Custom', name: 'Custom',
value: 'custom' value: 'custom',
}, },
], ],
default: 'everyDay', default: 'everyDay',
@ -94,7 +98,8 @@ export class Cron implements INodeType {
mode: [ mode: [
'custom', 'custom',
'everyHour', 'everyHour',
'everyMinute' 'everyMinute',
'everyX',
], ],
}, },
}, },
@ -113,7 +118,8 @@ export class Cron implements INodeType {
hide: { hide: {
mode: [ mode: [
'custom', 'custom',
'everyMinute' 'everyMinute',
'everyX',
], ],
}, },
}, },
@ -196,6 +202,48 @@ export class Cron implements INodeType {
default: '* * * * * *', default: '* * * * * *',
description: 'Use custom cron expression. Values and ranges as follows:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>', description: 'Use custom cron expression. Values and ranges as follows:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>',
}, },
{
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.',
},
] ]
}, },
], ],
@ -236,6 +284,14 @@ export class Cron implements INodeType {
cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`); cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`);
continue; 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) { for (parameterName of parameterOrder) {
if (item[parameterName] !== undefined) { if (item[parameterName] !== undefined) {

View file

@ -0,0 +1,57 @@
import * as EventSource from 'eventsource';
import { ITriggerFunctions } from 'n8n-core';
import {
INodeType,
INodeTypeDescription,
ITriggerResponse,
} from 'n8n-workflow';
export class SseTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'SSE Trigger',
name: 'sseTrigger',
icon: 'fa:cloud-download-alt',
group: ['trigger'],
version: 1,
description: 'Triggers worklfow on a new Server-Sent Event',
defaults: {
name: 'SSE Trigger',
color: '#225577',
},
inputs: [],
outputs: ['main'],
properties: [
{
displayName: 'URL',
name: 'url',
type: 'string',
default: '',
placeholder: 'http://example.com',
description: 'The URL to receive the SSE from.',
required: true,
},
]
};
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
const url = this.getNodeParameter('url') as string;
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
const eventData = JSON.parse(event.data);
this.emit([this.helpers.returnJsonArray([eventData])]);
};
async function closeFunction() {
eventSource.close();
}
return {
closeFunction,
};
}
}

View file

@ -0,0 +1,49 @@
import { OptionsWithUri } from 'request';
import {
IExecuteFunctions,
IHookFunctions,
ILoadOptionsFunctions,
IExecuteSingleFunctions,
IPollFunctions,
ITriggerFunctions,
} from 'n8n-core';
import {
IDataObject,
} from 'n8n-workflow';
export async function togglApiRequest(this: ITriggerFunctions | IPollFunctions | IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('togglApi');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const headerWithAuthentication = Object.assign({},
{ Authorization: ` Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64')}` });
const options: OptionsWithUri = {
headers: headerWithAuthentication,
method,
qs: query,
uri: uri || `https://www.toggl.com/api/v8${resource}`,
body,
json: true
};
if (Object.keys(options.body).length === 0) {
delete options.body;
}
try {
return await this.helpers.request!(options);
} catch (error) {
if (error.statusCode === 403) {
throw new Error('The Toggle credentials are probably invalid!');
}
const errorMessage = error.response.body && (error.response.body.message || error.response.body.Message);
if (errorMessage !== undefined) {
throw new Error(errorMessage);
}
throw error;
}
}

View file

@ -0,0 +1,79 @@
import { IPollFunctions } from 'n8n-core';
import {
INodeExecutionData,
INodeType,
INodeTypeDescription,
IDataObject,
} from 'n8n-workflow';
import * as moment from 'moment';
import { togglApiRequest } from './GenericFunctions';
export class TogglTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Toggl',
name: 'toggl',
icon: 'file:toggl.png',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when Toggl events occure',
defaults: {
name: 'Toggl',
color: '#00FF00',
},
credentials: [
{
name: 'togglApi',
required: true,
}
],
polling: true,
inputs: [],
outputs: ['main'],
properties: [
{
displayName: 'Event',
name: 'event',
type: 'options',
options: [
{
name: 'New Time Entry',
value: 'newTimeEntry',
}
],
required: true,
default: 'newTimeEntry',
},
]
};
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const webhookData = this.getWorkflowStaticData('node');
const event = this.getNodeParameter('event') as string;
let endpoint: string;
if (event === 'newTimeEntry') {
endpoint = '/time_entries';
} else {
throw new Error(`The defined event "${event}" is not supported`);
}
const qs: IDataObject = {};
let timeEntries = [];
qs.start_date = webhookData.lastTimeChecked;
qs.end_date = moment().format();
try {
timeEntries = await togglApiRequest.call(this, 'GET', endpoint, {}, qs);
webhookData.lastTimeChecked = qs.end_date;
} catch (err) {
throw new Error(`Toggl Trigger Error: ${err}`);
}
if (Array.isArray(timeEntries) && timeEntries.length !== 0) {
return [this.helpers.returnJsonArray(timeEntries)];
}
return null;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-nodes-base", "name": "n8n-nodes-base",
"version": "0.36.0", "version": "0.37.0",
"description": "Base nodes of n8n", "description": "Base nodes of n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -75,6 +75,7 @@
"dist/credentials/TypeformApi.credentials.js", "dist/credentials/TypeformApi.credentials.js",
"dist/credentials/VeroApi.credentials.js", "dist/credentials/VeroApi.credentials.js",
"dist/credentials/WordpressApi.credentials.js" "dist/credentials/WordpressApi.credentials.js"
"dist/credentials/TogglApi.credentials.js",
], ],
"nodes": [ "nodes": [
"dist/nodes/ActiveCampaign/ActiveCampaign.node.js", "dist/nodes/ActiveCampaign/ActiveCampaign.node.js",
@ -144,6 +145,7 @@
"dist/nodes/RenameKeys.node.js", "dist/nodes/RenameKeys.node.js",
"dist/nodes/RssFeedRead.node.js", "dist/nodes/RssFeedRead.node.js",
"dist/nodes/Set.node.js", "dist/nodes/Set.node.js",
"dist/nodes/SseTrigger.node.js",
"dist/nodes/SplitInBatches.node.js", "dist/nodes/SplitInBatches.node.js",
"dist/nodes/Slack/Slack.node.js", "dist/nodes/Slack/Slack.node.js",
"dist/nodes/SpreadsheetFile.node.js", "dist/nodes/SpreadsheetFile.node.js",
@ -158,6 +160,7 @@
"dist/nodes/Trello/TrelloTrigger.node.js", "dist/nodes/Trello/TrelloTrigger.node.js",
"dist/nodes/Twilio/Twilio.node.js", "dist/nodes/Twilio/Twilio.node.js",
"dist/nodes/Typeform/TypeformTrigger.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js",
"dist/nodes/Toggl/TogglTrigger.node.js",
"dist/nodes/Vero/Vero.node.js", "dist/nodes/Vero/Vero.node.js",
"dist/nodes/WriteBinaryFile.node.js", "dist/nodes/WriteBinaryFile.node.js",
"dist/nodes/Webhook.node.js", "dist/nodes/Webhook.node.js",
@ -170,6 +173,7 @@
"@types/basic-auth": "^1.1.2", "@types/basic-auth": "^1.1.2",
"@types/cheerio": "^0.22.15", "@types/cheerio": "^0.22.15",
"@types/cron": "^1.6.1", "@types/cron": "^1.6.1",
"@types/eventsource": "^1.1.2",
"@types/express": "^4.16.1", "@types/express": "^4.16.1",
"@types/gm": "^1.18.2", "@types/gm": "^1.18.2",
"@types/imap-simple": "^4.2.0", "@types/imap-simple": "^4.2.0",
@ -183,7 +187,7 @@
"@types/xml2js": "^0.4.3", "@types/xml2js": "^0.4.3",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"jest": "^24.9.0", "jest": "^24.9.0",
"n8n-workflow": "~0.18.0", "n8n-workflow": "~0.19.0",
"ts-jest": "^24.0.2", "ts-jest": "^24.0.2",
"tslint": "^5.17.0", "tslint": "^5.17.0",
"typescript": "~3.7.4" "typescript": "~3.7.4"
@ -192,7 +196,8 @@
"aws4": "^1.8.0", "aws4": "^1.8.0",
"basic-auth": "^2.0.1", "basic-auth": "^2.0.1",
"cheerio": "^1.0.0-rc.3", "cheerio": "^1.0.0-rc.3",
"cron": "^1.6.0", "cron": "^1.7.2",
"eventsource": "^1.0.7",
"glob-promise": "^3.4.0", "glob-promise": "^3.4.0",
"gm": "^1.23.1", "gm": "^1.23.1",
"googleapis": "^46.0.0", "googleapis": "^46.0.0",
@ -202,7 +207,7 @@
"lodash.unset": "^4.5.2", "lodash.unset": "^4.5.2",
"mongodb": "^3.3.2", "mongodb": "^3.3.2",
"mysql2": "^2.0.1", "mysql2": "^2.0.1",
"n8n-core": "~0.18.0", "n8n-core": "~0.19.0",
"nodemailer": "^5.1.1", "nodemailer": "^5.1.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"pg-promise": "^9.0.3", "pg-promise": "^9.0.3",

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-workflow", "name": "n8n-workflow",
"version": "0.18.0", "version": "0.19.0",
"description": "Workflow base code of n8n", "description": "Workflow base code of n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",

View file

@ -107,6 +107,10 @@ export interface IDataObject {
} }
export interface IGetExecutePollFunctions {
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IPollFunctions;
}
export interface IGetExecuteTriggerFunctions { export interface IGetExecuteTriggerFunctions {
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions; (workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions;
} }
@ -208,6 +212,19 @@ export interface IHookFunctions {
}; };
} }
export interface IPollFunctions {
__emit(data: INodeExecutionData[][]): void;
getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
getMode(): WorkflowExecuteMode;
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
getRestApiUrl(): string;
getTimezone(): string;
getWorkflowStaticData(type: string): IDataObject;
helpers: {
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
};
}
export interface ITriggerFunctions { export interface ITriggerFunctions {
emit(data: INodeExecutionData[][]): void; emit(data: INodeExecutionData[][]): void;
getCredentials(type: string): ICredentialDataDecryptedObject | undefined; getCredentials(type: string): ICredentialDataDecryptedObject | undefined;
@ -285,6 +302,7 @@ export interface INodeExecutionData {
export interface INodeExecuteFunctions { export interface INodeExecuteFunctions {
getExecutePollFunctions: IGetExecutePollFunctions;
getExecuteTriggerFunctions: IGetExecuteTriggerFunctions; getExecuteTriggerFunctions: IGetExecuteTriggerFunctions;
getExecuteFunctions: IGetExecuteFunctions; getExecuteFunctions: IGetExecuteFunctions;
getExecuteSingleFunctions: IGetExecuteSingleFunctions; getExecuteSingleFunctions: IGetExecuteSingleFunctions;
@ -363,6 +381,10 @@ export interface IParameterDependencies {
[key: string]: string[]; [key: string]: string[];
} }
export interface IPollResponse {
closeFunction?: () => Promise<void>;
}
export interface ITriggerResponse { export interface ITriggerResponse {
closeFunction?: () => Promise<void>; closeFunction?: () => Promise<void>;
// To manually trigger the run // To manually trigger the run
@ -376,6 +398,7 @@ export interface INodeType {
description: INodeTypeDescription; description: INodeTypeDescription;
execute?(this: IExecuteFunctions): Promise<INodeExecutionData[][] | null>; execute?(this: IExecuteFunctions): Promise<INodeExecutionData[][] | null>;
executeSingle?(this: IExecuteSingleFunctions): Promise<INodeExecutionData>; executeSingle?(this: IExecuteSingleFunctions): Promise<INodeExecutionData>;
poll?(this: IPollFunctions): Promise<INodeExecutionData[][] | null>;
trigger?(this: ITriggerFunctions): Promise<ITriggerResponse | undefined>; trigger?(this: ITriggerFunctions): Promise<ITriggerResponse | undefined>;
webhook?(this: IWebhookFunctions): Promise<IWebhookResponseData>; webhook?(this: IWebhookFunctions): Promise<IWebhookResponseData>;
hooks?: { hooks?: {
@ -447,6 +470,7 @@ export interface INodeTypeDescription {
properties: INodeProperties[]; properties: INodeProperties[];
credentials?: INodeCredentialDescription[]; credentials?: INodeCredentialDescription[];
maxNodes?: number; // How many nodes of that type can be created in a workflow maxNodes?: number; // How many nodes of that type can be created in a workflow
polling?: boolean;
subtitle?: string; subtitle?: string;
hooks?: { hooks?: {
[key: string]: INodeHookDescription[] | undefined; [key: string]: INodeHookDescription[] | undefined;

View file

@ -23,6 +23,242 @@ import {
import { get } from 'lodash'; import { get } from 'lodash';
/**
* Gets special parameters which should be added to nodeTypes depending
* on their type or configuration
*
* @export
* @param {INodeType} nodeType
* @returns
*/
export function getSpecialNodeParameters(nodeType: INodeType) {
if (nodeType.description.polling === true) {
return [
{
displayName: 'Poll Times',
name: 'pollTimes',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add Poll Time',
},
default: {},
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:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>',
},
{
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.',
},
],
},
],
},
];
}
return [];
}
/** /**
* Returns if the parameter should be displayed or not * Returns if the parameter should be displayed or not
* *

View file

@ -3,27 +3,28 @@ import {
IConnections, IConnections,
IGetExecuteTriggerFunctions, IGetExecuteTriggerFunctions,
INode, INode,
NodeHelpers,
INodes, INodes,
INodeExecuteFunctions, INodeExecuteFunctions,
INodeExecutionData, INodeExecutionData,
INodeParameters,
INodeIssues, INodeIssues,
NodeParameterValue, INodeParameters,
INodeType, INodeType,
INodeTypes, INodeTypes,
ObservableObject, IPollFunctions,
IRunExecutionData, IRunExecutionData,
ITaskDataConnections, ITaskDataConnections,
ITriggerResponse, ITriggerResponse,
IWebhookData, IWebhookData,
IWebhookResponseData, IWebhookResponseData,
WebhookSetupMethodNames,
WorkflowDataProxy,
IWorfklowIssues, IWorfklowIssues,
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
WorkflowExecuteMode,
IWorkflowSettings, IWorkflowSettings,
NodeHelpers,
NodeParameterValue,
ObservableObject,
WebhookSetupMethodNames,
WorkflowDataProxy,
WorkflowExecuteMode,
} from './'; } from './';
// @ts-ignore // @ts-ignore
@ -188,7 +189,7 @@ export class Workflow {
continue; continue;
} }
if (nodeType.trigger !== undefined || nodeType.webhook !== undefined) { if (nodeType.poll !== undefined || nodeType.trigger !== undefined || nodeType.webhook !== undefined) {
// Is a trigger node. So workflow can be activated. // Is a trigger node. So workflow can be activated.
return true; return true;
} }
@ -289,6 +290,30 @@ export class Workflow {
* @memberof Workflow * @memberof Workflow
*/ */
getTriggerNodes(): INode[] { getTriggerNodes(): INode[] {
return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger );
}
/**
* Returns all the poll nodes in the workflow
*
* @returns {INode[]}
* @memberof Workflow
*/
getPollNodes(): INode[] {
return this.queryNodes((nodeType: INodeType) => !!nodeType.poll );
}
/**
* Returns all the nodes in the workflow for which the given
* checkFunction return true
*
* @param {(nodeType: INodeType) => boolean} checkFunction
* @returns {INode[]}
* @memberof Workflow
*/
queryNodes(checkFunction: (nodeType: INodeType) => boolean): INode[] {
const returnNodes: INode[] = []; const returnNodes: INode[] = [];
// Check if it has any of them // Check if it has any of them
@ -304,7 +329,7 @@ export class Workflow {
nodeType = this.nodeTypes.getByName(node.type); nodeType = this.nodeTypes.getByName(node.type);
if (nodeType !== undefined && nodeType.trigger) { if (nodeType !== undefined && checkFunction(nodeType)) {
returnNodes.push(node); returnNodes.push(node);
} }
} }
@ -729,14 +754,14 @@ export class Workflow {
// Check which node to return as start node // Check which node to return as start node
// Check if there are any trigger nodes and then return the first one // Check if there are any trigger or poll nodes and then return the first one
let node: INode; let node: INode;
let nodeType: INodeType; let nodeType: INodeType;
for (const nodeName of nodeNames) { for (const nodeName of nodeNames) {
node = this.nodes[nodeName]; node = this.nodes[nodeName];
nodeType = this.nodeTypes.getByName(node.type) as INodeType; nodeType = this.nodeTypes.getByName(node.type) as INodeType;
if (nodeType.trigger !== undefined) { if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
return node; return node;
} }
} }
@ -994,6 +1019,30 @@ export class Workflow {
} }
/**
* Runs the given trigger node so that it can trigger the workflow
* when the node has data.
*
* @param {INode} node
* @param {IPollFunctions} pollFunctions
* @returns
* @memberof Workflow
*/
async runPoll(node: INode, pollFunctions: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const nodeType = this.nodeTypes.getByName(node.type);
if (nodeType === undefined) {
throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
}
if (!nodeType.poll) {
throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`);
}
return nodeType.poll!.call(pollFunctions);
}
/** /**
* Executes the webhook data to see what it should return and if the * Executes the webhook data to see what it should return and if the
* workflow should be started or not * workflow should be started or not
@ -1096,6 +1145,9 @@ export class Workflow {
} else if (nodeType.execute) { } else if (nodeType.execute) {
const thisArgs = nodeExecuteFunctions.getExecuteFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode); const thisArgs = nodeExecuteFunctions.getExecuteFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode);
return nodeType.execute.call(thisArgs); return nodeType.execute.call(thisArgs);
} else if (nodeType.poll) {
const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(this, node, additionalData, mode);
return nodeType.poll.call(thisArgs);
} else if (nodeType.trigger) { } else if (nodeType.trigger) {
if (mode === 'manual') { if (mode === 'manual') {
// In manual mode start the trigger // In manual mode start the trigger