mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
✨ Run workflows in own independent subprocess
This commit is contained in:
parent
abb0a52b08
commit
d59a043e3f
|
@ -2,24 +2,20 @@ import Vorpal = require('vorpal');
|
||||||
import { Args } from 'vorpal';
|
import { Args } from 'vorpal';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import {
|
import {
|
||||||
CredentialTypes,
|
ActiveExecutions,
|
||||||
Db,
|
Db,
|
||||||
|
GenericHelpers,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
GenericHelpers,
|
WorkflowCredentials,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowRunner,
|
||||||
} from "../src";
|
} from "../src";
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
|
||||||
UserSettings,
|
UserSettings,
|
||||||
WorkflowExecute,
|
|
||||||
} from "n8n-core";
|
} from "n8n-core";
|
||||||
import {
|
|
||||||
INode,
|
|
||||||
Workflow,
|
|
||||||
} from "n8n-workflow";
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = (vorpal: Vorpal) => {
|
module.exports = (vorpal: Vorpal) => {
|
||||||
|
@ -99,22 +95,16 @@ module.exports = (vorpal: Vorpal) => {
|
||||||
// Add the found types to an instance other parts of the application can use
|
// Add the found types to an instance other parts of the application can use
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||||
const credentialTypes = CredentialTypes();
|
|
||||||
await credentialTypes.init(loadNodesAndCredentials.credentialTypes);
|
|
||||||
|
|
||||||
if (!WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
if (!WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
||||||
workflowId = undefined;
|
workflowId = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowInstance = new Workflow(workflowId, workflowData!.nodes, workflowData!.connections, true, nodeTypes, workflowData!.staticData);
|
|
||||||
|
|
||||||
// Check if the workflow contains the required "Start" node
|
// Check if the workflow contains the required "Start" node
|
||||||
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
||||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||||
let startNodeFound = false;
|
let startNodeFound = false;
|
||||||
let node: INode;
|
for (const node of workflowData!.nodes) {
|
||||||
for (const nodeName of Object.keys(workflowInstance.nodes)) {
|
|
||||||
node = workflowInstance.nodes[nodeName];
|
|
||||||
if (requiredNodeTypes.includes(node.type)) {
|
if (requiredNodeTypes.includes(node.type)) {
|
||||||
startNodeFound = true;
|
startNodeFound = true;
|
||||||
}
|
}
|
||||||
|
@ -127,12 +117,17 @@ module.exports = (vorpal: Vorpal) => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const mode = 'cli';
|
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(mode, workflowData!, workflowInstance);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, mode);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const executionId = await workflowExecute.run(workflowInstance);
|
const credentials = await WorkflowCredentials(workflowData!.nodes);
|
||||||
|
|
||||||
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
|
credentials,
|
||||||
|
executionMode: 'cli',
|
||||||
|
workflowData: workflowData!,
|
||||||
|
};
|
||||||
|
|
||||||
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
const executionId = await workflowRunner.run(runData);
|
||||||
|
|
||||||
const activeExecutions = ActiveExecutions.getInstance();
|
const activeExecutions = ActiveExecutions.getInstance();
|
||||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||||
|
|
|
@ -1,43 +1,42 @@
|
||||||
import {
|
import {
|
||||||
IRun,
|
IRun,
|
||||||
IRunExecutionData,
|
|
||||||
Workflow,
|
|
||||||
WorkflowExecuteMode,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createDeferredPromise,
|
createDeferredPromise,
|
||||||
IExecutingWorkflowData,
|
|
||||||
IExecutionsCurrentSummary,
|
IExecutionsCurrentSummary,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecutingWorkflowData,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
|
import { ChildProcess } from 'child_process';
|
||||||
|
|
||||||
|
|
||||||
export class ActiveExecutions {
|
export class ActiveExecutions {
|
||||||
private nextId = 1;
|
private nextId = 1;
|
||||||
private activeExecutions: {
|
private activeExecutions: {
|
||||||
[index: string]: IExecutingWorkflowData;
|
[index: string]: IExecutingWorkflowData;
|
||||||
} = {};
|
} = {};
|
||||||
private stopExecutions: string[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new active execution
|
* Add a new active execution
|
||||||
*
|
*
|
||||||
* @param {Workflow} workflow
|
* @param {ChildProcess} process
|
||||||
* @param {IRunExecutionData} runExecutionData
|
* @param {IWorkflowExecutionDataProcess} executionData
|
||||||
* @param {WorkflowExecuteMode} mode
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @memberof ActiveExecutions
|
* @memberof ActiveExecutions
|
||||||
*/
|
*/
|
||||||
add(workflow: Workflow, runExecutionData: IRunExecutionData, mode: WorkflowExecuteMode): string {
|
add(process: ChildProcess, executionData: IWorkflowExecutionDataProcess): string {
|
||||||
const executionId = this.nextId++;
|
const executionId = this.nextId++;
|
||||||
|
|
||||||
this.activeExecutions[executionId] = {
|
this.activeExecutions[executionId] = {
|
||||||
runExecutionData,
|
executionData,
|
||||||
|
process,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
mode,
|
|
||||||
workflow,
|
|
||||||
postExecutePromises: [],
|
postExecutePromises: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +52,7 @@ export class ActiveExecutions {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
* @memberof ActiveExecutions
|
* @memberof ActiveExecutions
|
||||||
*/
|
*/
|
||||||
remove(executionId: string, fullRunData: IRun): void {
|
remove(executionId: string, fullRunData?: IRun): void {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
if (this.activeExecutions[executionId] === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -65,12 +64,6 @@ export class ActiveExecutions {
|
||||||
|
|
||||||
// Remove from the list of active executions
|
// Remove from the list of active executions
|
||||||
delete this.activeExecutions[executionId];
|
delete this.activeExecutions[executionId];
|
||||||
|
|
||||||
const stopExecutionIndex = this.stopExecutions.indexOf(executionId);
|
|
||||||
if (stopExecutionIndex !== -1) {
|
|
||||||
// If it was on the stop-execution list remove it
|
|
||||||
this.stopExecutions.splice(stopExecutionIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,16 +80,20 @@ export class ActiveExecutions {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.stopExecutions.includes(executionId)) {
|
// In case something goes wrong make sure that promise gets first
|
||||||
// Add the execution to the stop list if it is not already on it
|
// returned that it gets then also resolved correctly.
|
||||||
this.stopExecutions.push(executionId);
|
setTimeout(() => {
|
||||||
}
|
if (this.activeExecutions[executionId].process.connected) {
|
||||||
|
this.activeExecutions[executionId].process.send({
|
||||||
|
type: 'stopExecution'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
|
||||||
return this.getPostExecutePromise(executionId);
|
return this.getPostExecutePromise(executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise which will resolve with the data of the execution
|
* Returns a promise which will resolve with the data of the execution
|
||||||
* with the given id
|
* with the given id
|
||||||
|
@ -105,9 +102,9 @@ export class ActiveExecutions {
|
||||||
* @returns {Promise<IRun>}
|
* @returns {Promise<IRun>}
|
||||||
* @memberof ActiveExecutions
|
* @memberof ActiveExecutions
|
||||||
*/
|
*/
|
||||||
async getPostExecutePromise(executionId: string): Promise<IRun> {
|
async getPostExecutePromise(executionId: string): Promise<IRun | undefined> {
|
||||||
// Create the promise which will be resolved when the execution finished
|
// Create the promise which will be resolved when the execution finished
|
||||||
const waitPromise = await createDeferredPromise<IRun>();
|
const waitPromise = await createDeferredPromise<IRun | undefined>();
|
||||||
|
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
if (this.activeExecutions[executionId] === undefined) {
|
||||||
throw new Error(`There is no active execution with id "${executionId}".`);
|
throw new Error(`There is no active execution with id "${executionId}".`);
|
||||||
|
@ -119,20 +116,6 @@ export class ActiveExecutions {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the execution should be stopped
|
|
||||||
*
|
|
||||||
* @param {string} executionId The execution id to check
|
|
||||||
* @returns {boolean}
|
|
||||||
* @memberof ActiveExecutions
|
|
||||||
*/
|
|
||||||
shouldBeStopped(executionId: string): boolean {
|
|
||||||
return this.stopExecutions.includes(executionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the currently active executions
|
* Returns all the currently active executions
|
||||||
*
|
*
|
||||||
|
@ -142,15 +125,15 @@ export class ActiveExecutions {
|
||||||
getActiveExecutions(): IExecutionsCurrentSummary[] {
|
getActiveExecutions(): IExecutionsCurrentSummary[] {
|
||||||
const returnData: IExecutionsCurrentSummary[] = [];
|
const returnData: IExecutionsCurrentSummary[] = [];
|
||||||
|
|
||||||
let executionData;
|
let data;
|
||||||
for (const id of Object.keys(this.activeExecutions)) {
|
for (const id of Object.keys(this.activeExecutions)) {
|
||||||
executionData = this.activeExecutions[id];
|
data = this.activeExecutions[id];
|
||||||
returnData.push(
|
returnData.push(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
startedAt: executionData.startedAt,
|
startedAt: data.startedAt,
|
||||||
mode: executionData.mode,
|
mode: data.executionData.executionMode,
|
||||||
workflowId: executionData.workflow.id!,
|
workflowId: data.executionData.workflowData.id! as string,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -4,18 +4,28 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
IResponseCallbackData,
|
IResponseCallbackData,
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
WebhookHelpers,
|
WebhookHelpers,
|
||||||
|
WorkflowCredentials,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
|
WorkflowRunner,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveWorkflows,
|
ActiveWorkflows,
|
||||||
ActiveWebhooks,
|
ActiveWebhooks,
|
||||||
|
NodeExecuteFunctions,
|
||||||
|
WorkflowExecute,
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IExecuteData,
|
||||||
|
IGetExecuteTriggerFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
IRunExecutionData,
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
|
@ -209,6 +219,57 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return trigger 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 {IGetExecuteTriggerFunctions}
|
||||||
|
* @memberof ActiveWorkflowRunner
|
||||||
|
*/
|
||||||
|
getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): IGetExecuteTriggerFunctions{
|
||||||
|
return ((workflow: Workflow, node: INode) => {
|
||||||
|
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode);
|
||||||
|
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
return returnFunctions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a workflow active
|
* Makes a workflow active
|
||||||
*
|
*
|
||||||
|
@ -240,12 +301,13 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mode = 'trigger';
|
const mode = 'trigger';
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(mode, workflowData, workflowInstance);
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(mode, credentials);
|
||||||
|
const getTriggerFunctions = this.getExecuteTriggerFunctions(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);
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -265,6 +327,8 @@ export class ActiveWorkflowRunner {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If for example webhooks get created it sometimes has to save the
|
||||||
|
// id of them in the static data. So make sure that data gets persisted.
|
||||||
await WorkflowHelpers.saveStaticData(workflowInstance!);
|
await WorkflowHelpers.saveStaticData(workflowInstance!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,22 @@ import {
|
||||||
IExecutionError,
|
IExecutionError,
|
||||||
INode,
|
INode,
|
||||||
IRun,
|
IRun,
|
||||||
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
IWorkflowCredentials,
|
||||||
IWorkflowSettings,
|
IWorkflowSettings,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDeferredPromise,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
|
||||||
import { ObjectID, Repository } from "typeorm";
|
import { ObjectID, Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { ChildProcess } from 'child_process';
|
||||||
import { Url } from 'url';
|
import { Url } from 'url';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
|
||||||
|
@ -171,6 +178,13 @@ export interface IExecutionDeleteFilter {
|
||||||
ids?: string[];
|
ids?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IExecutingWorkflowData {
|
||||||
|
executionData: IWorkflowExecutionDataProcess;
|
||||||
|
process: ChildProcess;
|
||||||
|
startedAt: Date;
|
||||||
|
postExecutePromises: Array<IDeferredPromise<IRun | undefined>>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IN8nConfig {
|
export interface IN8nConfig {
|
||||||
database: IN8nConfigDatabase;
|
database: IN8nConfigDatabase;
|
||||||
endpoints: IN8nConfigEndpoints;
|
endpoints: IN8nConfigEndpoints;
|
||||||
|
@ -282,6 +296,14 @@ export interface IResponseCallbackData {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITransferNodeTypes {
|
||||||
|
[key: string]: {
|
||||||
|
className: string;
|
||||||
|
sourcePath: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowErrorData {
|
export interface IWorkflowErrorData {
|
||||||
[key: string]: IDataObject | string | number | IExecutionError;
|
[key: string]: IDataObject | string | number | IExecutionError;
|
||||||
execution: {
|
execution: {
|
||||||
|
@ -295,3 +317,25 @@ export interface IWorkflowErrorData {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IProcessMessageDataHook {
|
||||||
|
hook: string;
|
||||||
|
parameters: any[]; // tslint:disable-line:no-any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkflowExecutionDataProcess {
|
||||||
|
credentials: IWorkflowCredentials;
|
||||||
|
destinationNode?: string;
|
||||||
|
executionMode: WorkflowExecuteMode;
|
||||||
|
executionData?: IRunExecutionData;
|
||||||
|
runData?: IRunData;
|
||||||
|
retryOf?: number | string | ObjectID;
|
||||||
|
sessionId?: string;
|
||||||
|
startNodes?: string[];
|
||||||
|
workflowData: IWorkflowBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess {
|
||||||
|
executionId: string;
|
||||||
|
nodeTypeData: ITransferNodeTypes;
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
import {
|
import {
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
INodeType,
|
INodeType,
|
||||||
|
INodeTypeData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
@ -25,9 +26,7 @@ const fsStatAsync = promisify(fsStat);
|
||||||
|
|
||||||
|
|
||||||
class LoadNodesAndCredentialsClass {
|
class LoadNodesAndCredentialsClass {
|
||||||
nodeTypes: {
|
nodeTypes: INodeTypeData = {};
|
||||||
[key: string]: INodeType
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
credentialTypes: {
|
credentialTypes: {
|
||||||
[key: string]: ICredentialType
|
[key: string]: ICredentialType
|
||||||
|
@ -37,7 +36,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
|
|
||||||
nodeModulesPath = '';
|
nodeModulesPath = '';
|
||||||
|
|
||||||
async init(directory?: string) {
|
async init() {
|
||||||
// Get the path to the node-modules folder to be later able
|
// Get the path to the node-modules folder to be later able
|
||||||
// to load the credentials and nodes
|
// to load the credentials and nodes
|
||||||
const checkPaths = [
|
const checkPaths = [
|
||||||
|
@ -172,12 +171,15 @@ class LoadNodesAndCredentialsClass {
|
||||||
tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5));
|
tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the node should be skipped
|
// Check if the node should be skiped
|
||||||
if (this.excludeNodes !== undefined && this.excludeNodes.includes(fullNodeName)) {
|
if (this.excludeNodes !== undefined && this.excludeNodes.includes(fullNodeName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.nodeTypes[fullNodeName] = tempNode;
|
this.nodeTypes[fullNodeName] = {
|
||||||
|
type: tempNode,
|
||||||
|
sourcePath: filePath,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
import {
|
import {
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypes,
|
INodeTypes,
|
||||||
|
INodeTypeData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
|
||||||
class NodeTypesClass implements INodeTypes {
|
class NodeTypesClass implements INodeTypes {
|
||||||
|
|
||||||
nodeTypes: {
|
nodeTypes: INodeTypeData = {};
|
||||||
[key: string]: INodeType
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
|
|
||||||
async init(nodeTypes: {[key: string]: INodeType }): Promise<void> {
|
async init(nodeTypes: INodeTypeData): Promise<void> {
|
||||||
this.nodeTypes = nodeTypes;
|
this.nodeTypes = nodeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(): INodeType[] {
|
getAll(): INodeType[] {
|
||||||
return Object.values(this.nodeTypes);
|
return Object.values(this.nodeTypes).map((data) => data.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(nodeType: string): INodeType | undefined {
|
getByName(nodeType: string): INodeType | undefined {
|
||||||
return this.nodeTypes[nodeType];
|
return this.nodeTypes[nodeType].type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,16 @@ import * as history from 'connect-history-api-fallback';
|
||||||
import * as requestPromise from 'request-promise-native';
|
import * as requestPromise from 'request-promise-native';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IActivationError,
|
ActiveExecutions,
|
||||||
ActiveWorkflowRunner,
|
ActiveWorkflowRunner,
|
||||||
|
CredentialTypes,
|
||||||
|
Db,
|
||||||
|
IActivationError,
|
||||||
ICustomRequest,
|
ICustomRequest,
|
||||||
ICredentialsDb,
|
ICredentialsDb,
|
||||||
ICredentialsDecryptedDb,
|
ICredentialsDecryptedDb,
|
||||||
ICredentialsDecryptedResponse,
|
ICredentialsDecryptedResponse,
|
||||||
ICredentialsResponse,
|
ICredentialsResponse,
|
||||||
CredentialTypes,
|
|
||||||
Db,
|
|
||||||
IExecutionDeleteFilter,
|
IExecutionDeleteFilter,
|
||||||
IExecutionFlatted,
|
IExecutionFlatted,
|
||||||
IExecutionFlattedDb,
|
IExecutionFlattedDb,
|
||||||
|
@ -25,22 +26,23 @@ import {
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
IWorkflowShortResponse,
|
IWorkflowShortResponse,
|
||||||
IWorkflowResponse,
|
IWorkflowResponse,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
Push,
|
Push,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
|
WorkflowCredentials,
|
||||||
WebhookHelpers,
|
WebhookHelpers,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
|
WorkflowRunner,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
|
||||||
Credentials,
|
Credentials,
|
||||||
LoadNodeParameterOptions,
|
LoadNodeParameterOptions,
|
||||||
UserSettings,
|
UserSettings,
|
||||||
WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -127,7 +129,7 @@ class App {
|
||||||
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const authIgnoreRegex = new RegExp(`^\/(rest|${this.endpointWebhook}|${this.endpointWebhookTest})\/.*$`)
|
const authIgnoreRegex = new RegExp(`^\/(rest|${this.endpointWebhook}|${this.endpointWebhookTest})\/.*$`);
|
||||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (req.url.match(authIgnoreRegex)) {
|
if (req.url.match(authIgnoreRegex)) {
|
||||||
return next();
|
return next();
|
||||||
|
@ -386,45 +388,45 @@ class App {
|
||||||
const runData: IRunData | undefined = req.body.runData;
|
const runData: IRunData | undefined = req.body.runData;
|
||||||
const startNodes: string[] | undefined = req.body.startNodes;
|
const startNodes: string[] | undefined = req.body.startNodes;
|
||||||
const destinationNode: string | undefined = req.body.destinationNode;
|
const destinationNode: string | undefined = req.body.destinationNode;
|
||||||
const nodeTypes = NodeTypes();
|
|
||||||
const executionMode = 'manual';
|
const executionMode = 'manual';
|
||||||
|
|
||||||
const sessionId = GenericHelpers.getSessionId(req);
|
const sessionId = GenericHelpers.getSessionId(req);
|
||||||
|
|
||||||
// Do not supply the saved static data! Tests always run with initially empty static data.
|
// Check if workflow is saved as webhooks can only be tested with saved workflows.
|
||||||
// The reason is that it contains information like webhook-ids. If a workflow is currently
|
// If that is the case check if any webhooks calls are present we have to wait for and
|
||||||
// active it would see its id and would so not create an own test-webhook. Additionally would
|
// if that is the case wait till we receive it.
|
||||||
// it also delete the webhook at the service in the end. So that the active workflow would end
|
if (WorkflowHelpers.isWorkflowIdValid(workflowData.id) === true && (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined)) {
|
||||||
// up without still being active but not receiving and webhook requests anymore as it does
|
// Webhooks can only be tested with saved workflows
|
||||||
// not exist anymore.
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
const workflowInstance = new Workflow(workflowData.id, workflowData.nodes, workflowData.connections, false, nodeTypes, undefined, workflowData.settings);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(executionMode, credentials);
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(executionMode, workflowData, workflowInstance, sessionId);
|
const workflowInstance = new Workflow(workflowData.id, workflowData.nodes, workflowData.connections, false, nodeTypes, undefined, workflowData.settings);
|
||||||
|
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, sessionId, destinationNode);
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
if (needsWebhook === true) {
|
||||||
|
return {
|
||||||
let executionId: string;
|
waitingForWebhook: true,
|
||||||
|
};
|
||||||
if (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined) {
|
|
||||||
// Execute all nodes
|
|
||||||
|
|
||||||
if (WorkflowHelpers.isWorkflowIdValid(workflowData.id) === true) {
|
|
||||||
// Webhooks can only be tested with saved workflows
|
|
||||||
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, sessionId, destinationNode);
|
|
||||||
if (needsWebhook === true) {
|
|
||||||
return {
|
|
||||||
waitingForWebhook: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can execute without webhook so go on
|
|
||||||
executionId = await workflowExecute.run(workflowInstance, undefined, destinationNode);
|
|
||||||
} else {
|
|
||||||
// Execute only the nodes between start and destination nodes
|
|
||||||
executionId = await workflowExecute.runPartialWorkflow(workflowInstance, runData, startNodes, destinationNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For manual testing always set to not active
|
||||||
|
workflowData.active = false;
|
||||||
|
|
||||||
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
|
|
||||||
|
// Start the workflow
|
||||||
|
const data: IWorkflowExecutionDataProcess = {
|
||||||
|
credentials,
|
||||||
|
destinationNode,
|
||||||
|
executionMode,
|
||||||
|
runData,
|
||||||
|
sessionId,
|
||||||
|
startNodes,
|
||||||
|
workflowData,
|
||||||
|
};
|
||||||
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
const executionId = await workflowRunner.run(data);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
executionId,
|
executionId,
|
||||||
};
|
};
|
||||||
|
@ -444,12 +446,11 @@ class App {
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
const executionMode = 'manual';
|
const executionMode = 'manual';
|
||||||
|
|
||||||
const sessionId = GenericHelpers.getSessionId(req);
|
|
||||||
|
|
||||||
const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, credentials);
|
const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, credentials);
|
||||||
|
|
||||||
const workflowData = loadDataInstance.getWorkflowData() as IWorkflowBase;
|
const workflowData = loadDataInstance.getWorkflowData() as IWorkflowBase;
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(executionMode, workflowData, loadDataInstance.workflow, sessionId);
|
const workflowCredentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(executionMode, workflowCredentials);
|
||||||
|
|
||||||
return loadDataInstance.getOptions(methodName, additionalData);
|
return loadDataInstance.getOptions(methodName, additionalData);
|
||||||
}));
|
}));
|
||||||
|
@ -843,13 +844,22 @@ class App {
|
||||||
|
|
||||||
const executionMode = 'retry';
|
const executionMode = 'retry';
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const credentials = await WorkflowCredentials(fullExecutionData.workflowData.nodes);
|
||||||
const workflowInstance = new Workflow(req.params.id, fullExecutionData.workflowData.nodes, fullExecutionData.workflowData.connections, false, nodeTypes, fullExecutionData.workflowData.staticData, fullExecutionData.workflowData.settings);
|
|
||||||
|
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(executionMode, fullExecutionData.workflowData, workflowInstance, undefined, req.params.id);
|
fullExecutionData.workflowData.active = false;
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
|
||||||
|
|
||||||
return workflowExecute.runExecutionData(workflowInstance, fullExecutionData.data);
|
// Start the workflow
|
||||||
|
const data: IWorkflowExecutionDataProcess = {
|
||||||
|
credentials,
|
||||||
|
executionMode,
|
||||||
|
executionData: fullExecutionData.data,
|
||||||
|
retryOf: req.params.id,
|
||||||
|
workflowData: fullExecutionData.workflowData,
|
||||||
|
};
|
||||||
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
const executionId = await workflowRunner.run(data);
|
||||||
|
|
||||||
|
return executionId;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActiveExecutions,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
IExecutionDb,
|
IExecutionDb,
|
||||||
IResponseCallbackData,
|
IResponseCallbackData,
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
|
WorkflowRunner,
|
||||||
|
WorkflowCredentials,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BINARY_ENCODING,
|
BINARY_ENCODING,
|
||||||
ActiveExecutions,
|
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -124,8 +126,8 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare everything that is needed to run the workflow
|
// Prepare everything that is needed to run the workflow
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(executionMode, workflowData, webhookData.workflow, sessionId);
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(executionMode, credentials);
|
||||||
|
|
||||||
// Add the Response and Request so that this data can be accessed in the node
|
// Add the Response and Request so that this data can be accessed in the node
|
||||||
additionalData.httpRequest = req;
|
additionalData.httpRequest = req;
|
||||||
|
@ -207,8 +209,17 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
|
credentials,
|
||||||
|
executionMode,
|
||||||
|
executionData: runExecutionData,
|
||||||
|
sessionId,
|
||||||
|
workflowData,
|
||||||
|
};
|
||||||
|
|
||||||
// Start now to run the workflow
|
// Start now to run the workflow
|
||||||
const executionId = await workflowExecute.runExecutionData(webhookData.workflow, runExecutionData);
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
const executionId = await workflowRunner.run(runData);
|
||||||
|
|
||||||
// Get a promise which resolves when the workflow did execute and send then response
|
// Get a promise which resolves when the workflow did execute and send then response
|
||||||
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<IExecutionDb | undefined>;
|
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<IExecutionDb | undefined>;
|
||||||
|
|
|
@ -3,14 +3,11 @@ import {
|
||||||
IExecutionDb,
|
IExecutionDb,
|
||||||
IExecutionFlattedDb,
|
IExecutionFlattedDb,
|
||||||
IPushDataExecutionFinished,
|
IPushDataExecutionFinished,
|
||||||
IPushDataExecutionStarted,
|
|
||||||
IPushDataNodeExecuteAfter,
|
|
||||||
IPushDataNodeExecuteBefore,
|
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
Push,
|
Push,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
WebhookHelpers,
|
WebhookHelpers,
|
||||||
WorkflowCredentials,
|
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
|
@ -19,11 +16,13 @@ import {
|
||||||
} from "n8n-core";
|
} from "n8n-core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IDataObject,
|
||||||
IRun,
|
IRun,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
IWorkflowCredentials,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
|
IWorkflowExecuteHooks,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
@ -68,7 +67,7 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
||||||
* @param {string} executionIdActive The id of the finished execution
|
* @param {string} executionIdActive The id of the finished execution
|
||||||
* @param {string} [executionIdDb] The database id of finished execution
|
* @param {string} [executionIdDb] The database id of finished execution
|
||||||
*/
|
*/
|
||||||
function pushExecutionFinished(fullRunData: IRun, executionIdActive: string, executionIdDb?: string) {
|
export function pushExecutionFinished(fullRunData: IRun, executionIdActive: string, executionIdDb?: string) {
|
||||||
// Clone the object except the runData. That one is not supposed
|
// Clone the object except the runData. That one is not supposed
|
||||||
// to be send. Because that data got send piece by piece after
|
// to be send. Because that data got send piece by piece after
|
||||||
// each node which finished executing
|
// each node which finished executing
|
||||||
|
@ -94,70 +93,79 @@ function pushExecutionFinished(fullRunData: IRun, executionIdActive: string, exe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowInstance: Workflow, sessionId?: string, retryOf?: string) => {
|
/**
|
||||||
|
* Returns the workflow execution hooks
|
||||||
|
*
|
||||||
|
* @param {WorkflowExecuteMode} mode
|
||||||
|
* @param {IWorkflowBase} workflowData
|
||||||
|
* @param {string} executionId
|
||||||
|
* @param {string} [sessionId]
|
||||||
|
* @param {string} [retryOf]
|
||||||
|
* @returns {IWorkflowExecuteHooks}
|
||||||
|
*/
|
||||||
|
const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, executionId: string, sessionId?: string, retryOf?: string): IWorkflowExecuteHooks => {
|
||||||
return {
|
return {
|
||||||
nodeExecuteBefore: [
|
nodeExecuteBefore: [
|
||||||
async (executionId: string, nodeName: string): Promise<void> => {
|
async (nodeName: string): Promise<void> => {
|
||||||
|
// Push data to session which started workflow before each
|
||||||
|
// node which starts rendering
|
||||||
if (sessionId === undefined) {
|
if (sessionId === undefined) {
|
||||||
// Only push data to the session which started it
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendData: IPushDataNodeExecuteBefore = {
|
pushInstance.send('nodeExecuteBefore', {
|
||||||
executionId,
|
executionId,
|
||||||
nodeName,
|
nodeName,
|
||||||
};
|
}, sessionId);
|
||||||
|
|
||||||
pushInstance.send('nodeExecuteBefore', sendData, sessionId);
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
nodeExecuteAfter: [
|
nodeExecuteAfter: [
|
||||||
async (executionId: string, nodeName: string, data: ITaskData): Promise<void> => {
|
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||||
|
// Push data to session which started workflow after each rendered node
|
||||||
if (sessionId === undefined) {
|
if (sessionId === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendData: IPushDataNodeExecuteAfter = {
|
pushInstance.send('nodeExecuteAfter', {
|
||||||
executionId,
|
executionId,
|
||||||
nodeName,
|
nodeName,
|
||||||
data,
|
data,
|
||||||
};
|
}, sessionId);
|
||||||
|
|
||||||
pushInstance.send('nodeExecuteAfter', sendData, sessionId);
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
workflowExecuteBefore: [
|
workflowExecuteBefore: [
|
||||||
async (executionId: string): Promise<void> => {
|
async (): Promise<void> => {
|
||||||
// Push data to editor-ui once workflow finished
|
// Push data to editor-ui once workflow finished
|
||||||
const sendData: IPushDataExecutionStarted = {
|
pushInstance.send('executionStarted', {
|
||||||
executionId,
|
executionId,
|
||||||
mode,
|
mode,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
retryOf,
|
retryOf,
|
||||||
workflowId: workflowData.id as string,
|
workflowId: workflowData.id as string,
|
||||||
workflowName: workflowData.name,
|
workflowName: workflowData.name,
|
||||||
};
|
});
|
||||||
|
|
||||||
pushInstance.send('executionStarted', sendData);
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
workflowExecuteAfter: [
|
workflowExecuteAfter: [
|
||||||
async (fullRunData: IRun, executionId: string): Promise<void> => {
|
async (fullRunData: IRun, newStaticData: IDataObject): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const workflowSavePromise = WorkflowHelpers.saveStaticData(workflowInstance);
|
if (WorkflowHelpers.isWorkflowIdValid(workflowData.id as string) === true) {
|
||||||
|
// Workflow is saved so update in database
|
||||||
|
try {
|
||||||
|
await WorkflowHelpers.saveStaticDataById(workflowData.id as string, newStaticData);
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Add proper logging!
|
||||||
|
console.error(`There was a problem saving the workflow with id "${workflowData.id}" to save changed staticData: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean;
|
let saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean;
|
||||||
if (workflowInstance.settings !== undefined && workflowInstance.settings.saveManualExecutions !== undefined) {
|
if (workflowData.settings !== undefined && workflowData.settings.saveManualExecutions !== undefined) {
|
||||||
// Apply to workflow override
|
// Apply to workflow override
|
||||||
saveManualExecutions = workflowInstance.settings.saveManualExecutions as boolean;
|
saveManualExecutions = workflowData.settings.saveManualExecutions as boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'manual' && saveManualExecutions === false) {
|
if (mode === 'manual' && saveManualExecutions === false) {
|
||||||
if (workflowSavePromise !== undefined) {
|
|
||||||
// If workflow had to be saved wait till it is done
|
|
||||||
await workflowSavePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushExecutionFinished(fullRunData, executionId);
|
pushExecutionFinished(fullRunData, executionId);
|
||||||
executeErrorWorkflow(workflowData, fullRunData, mode);
|
executeErrorWorkflow(workflowData, fullRunData, mode);
|
||||||
return;
|
return;
|
||||||
|
@ -166,9 +174,9 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI
|
||||||
// Check config to know if execution should be saved or not
|
// Check config to know if execution should be saved or not
|
||||||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||||
if (workflowInstance.settings !== undefined) {
|
if (workflowData.settings !== undefined) {
|
||||||
saveDataErrorExecution = (workflowInstance.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
saveDataErrorExecution = (workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
||||||
saveDataSuccessExecution = (workflowInstance.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
saveDataSuccessExecution = (workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||||
|
@ -208,11 +216,6 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI
|
||||||
await Db.collections.Execution!.update(retryOf, { retrySuccessId: executionResult.id });
|
await Db.collections.Execution!.update(retryOf, { retrySuccessId: executionResult.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflowSavePromise !== undefined) {
|
|
||||||
// If workflow had to be saved wait till it is done
|
|
||||||
await workflowSavePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushExecutionFinished(fullRunData, executionId, executionResult.id as string);
|
pushExecutionFinished(fullRunData, executionId, executionResult.id as string);
|
||||||
executeErrorWorkflow(workflowData, fullRunData, mode, executionResult ? executionResult.id as string : undefined);
|
executeErrorWorkflow(workflowData, fullRunData, mode, executionResult ? executionResult.id as string : undefined);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -225,7 +228,15 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export async function get(mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowInstance: Workflow, sessionId?: string, retryOf?: string): Promise<IWorkflowExecuteAdditionalData> {
|
/**
|
||||||
|
* Returns the base additional data without webhooks
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {WorkflowExecuteMode} mode
|
||||||
|
* @param {IWorkflowCredentials} credentials
|
||||||
|
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
||||||
|
*/
|
||||||
|
export async function getBase(mode: WorkflowExecuteMode, credentials: IWorkflowCredentials): Promise<IWorkflowExecuteAdditionalData> {
|
||||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||||
|
|
||||||
const timezone = config.get('generic.timezone') as string;
|
const timezone = config.get('generic.timezone') as string;
|
||||||
|
@ -238,11 +249,23 @@ export async function get(mode: WorkflowExecuteMode, workflowData: IWorkflowBase
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
credentials: await WorkflowCredentials(workflowData.nodes),
|
credentials,
|
||||||
hooks: hooks(mode, workflowData, workflowInstance, sessionId, retryOf),
|
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
timezone,
|
timezone,
|
||||||
webhookBaseUrl,
|
webhookBaseUrl,
|
||||||
webhookTestBaseUrl,
|
webhookTestBaseUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the workflow hooks
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {IWorkflowExecutionDataProcess} data
|
||||||
|
* @param {string} executionId
|
||||||
|
* @returns {IWorkflowExecuteHooks}
|
||||||
|
*/
|
||||||
|
export function getHookMethods(data: IWorkflowExecutionDataProcess, executionId: string): IWorkflowExecuteHooks {
|
||||||
|
return hooks(data.executionMode, data.workflowData, executionId, data.sessionId, data.retryOf as string | undefined);
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import {
|
import {
|
||||||
Db,
|
Db,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
IWorkflowErrorData,
|
IWorkflowErrorData,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowCredentials,
|
||||||
|
WorkflowRunner,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
WorkflowExecute,
|
IDataObject,
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
INode,
|
INode,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
|
@ -80,10 +79,7 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.get(executionMode, workflowData, workflowInstance);
|
|
||||||
|
|
||||||
// Can execute without webhook so go on
|
// Can execute without webhook so go on
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
|
||||||
|
|
||||||
// Initialize the data of the webhook node
|
// Initialize the data of the webhook node
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [];
|
||||||
|
@ -115,9 +111,17 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start now to run the workflow
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
await workflowExecute.runExecutionData(workflowInstance, runExecutionData);
|
|
||||||
|
|
||||||
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
|
credentials,
|
||||||
|
executionMode,
|
||||||
|
executionData: runExecutionData,
|
||||||
|
workflowData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
await workflowRunner.run(runData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`ERROR: Calling Error Workflow for "${workflowErrorData.workflow.id}": ${error.message}`);
|
console.error(`ERROR: Calling Error Workflow for "${workflowErrorData.workflow.id}": ${error.message}`);
|
||||||
}
|
}
|
||||||
|
@ -138,10 +142,7 @@ export async function saveStaticData(workflow: Workflow): Promise <void> {
|
||||||
if (isWorkflowIdValid(workflow.id) === true) {
|
if (isWorkflowIdValid(workflow.id) === true) {
|
||||||
// Workflow is saved so update in database
|
// Workflow is saved so update in database
|
||||||
try {
|
try {
|
||||||
await Db.collections.Workflow!
|
await saveStaticDataById(workflow.id!, workflow.staticData);
|
||||||
.update(workflow.id!, {
|
|
||||||
staticData: workflow.staticData,
|
|
||||||
});
|
|
||||||
workflow.staticData.__dataChanged = false;
|
workflow.staticData.__dataChanged = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Add proper logging!
|
// TODO: Add proper logging!
|
||||||
|
@ -150,3 +151,20 @@ export async function saveStaticData(workflow: Workflow): Promise <void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given static data on workflow
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {(string | number)} workflowId The id of the workflow to save data on
|
||||||
|
* @param {IDataObject} newStaticData The static data to save
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function saveStaticDataById(workflowId: string | number, newStaticData: IDataObject): Promise<void> {
|
||||||
|
await Db.collections.Workflow!
|
||||||
|
.update(workflowId, {
|
||||||
|
staticData: newStaticData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
191
packages/cli/src/WorkflowRunner.ts
Normal file
191
packages/cli/src/WorkflowRunner.ts
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActiveExecutions,
|
||||||
|
IProcessMessageDataHook,
|
||||||
|
ITransferNodeTypes,
|
||||||
|
IWorkflowExecutionDataProcess,
|
||||||
|
IWorkflowExecutionDataProcessWithExecution,
|
||||||
|
NodeTypes,
|
||||||
|
Push,
|
||||||
|
WorkflowExecuteAdditionalData,
|
||||||
|
} from './';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IProcessMessage,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecutionError,
|
||||||
|
INode,
|
||||||
|
IRun,
|
||||||
|
IWorkflowExecuteHooks,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
|
||||||
|
|
||||||
|
export class WorkflowRunner {
|
||||||
|
activeExecutions: ActiveExecutions.ActiveExecutions;
|
||||||
|
push: Push.Push;
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.push = Push.getInstance();
|
||||||
|
this.activeExecutions = ActiveExecutions.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data of the node types that are needed
|
||||||
|
* to execute the given nodes
|
||||||
|
*
|
||||||
|
* @param {INode[]} nodes
|
||||||
|
* @returns {ITransferNodeTypes}
|
||||||
|
* @memberof WorkflowRunner
|
||||||
|
*/
|
||||||
|
getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
|
// Check which node-types have to be loaded
|
||||||
|
const neededNodeTypes: string[] = [];
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!neededNodeTypes.includes(node.type)) {
|
||||||
|
neededNodeTypes.push(node.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the data of the needed node types that they
|
||||||
|
// can be loaded again in the process
|
||||||
|
const returnData: ITransferNodeTypes = {};
|
||||||
|
for (const nodeTypeName of neededNodeTypes) {
|
||||||
|
if (nodeTypes.nodeTypes[nodeTypeName] === undefined) {
|
||||||
|
throw new Error(`The NodeType "${nodeTypeName}" could not be found!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData[nodeTypeName] = {
|
||||||
|
className: nodeTypes.nodeTypes[nodeTypeName].type.constructor.name,
|
||||||
|
sourcePath: nodeTypes.nodeTypes[nodeTypeName].sourcePath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The process did send a hook message so execute the appropiate hook
|
||||||
|
*
|
||||||
|
* @param {IWorkflowExecuteHooks} hookFunctions
|
||||||
|
* @param {IProcessMessageDataHook} hookData
|
||||||
|
* @memberof WorkflowRunner
|
||||||
|
*/
|
||||||
|
processHookMessage(hookFunctions: IWorkflowExecuteHooks, hookData: IProcessMessageDataHook) {
|
||||||
|
if (hookFunctions[hookData.hook] !== undefined && Array.isArray(hookFunctions[hookData.hook])) {
|
||||||
|
|
||||||
|
for (const hookFunction of hookFunctions[hookData.hook]!) {
|
||||||
|
// TODO: Not sure if that is 100% correct or something is still missing like to wait
|
||||||
|
hookFunction.apply(this, hookData.parameters)
|
||||||
|
.catch((error: Error) => {
|
||||||
|
// Catch all errors here because when "executeHook" gets called
|
||||||
|
// we have the most time no "await" and so the errors would so
|
||||||
|
// not be uncaught by anything.
|
||||||
|
|
||||||
|
// TODO: Add proper logging
|
||||||
|
console.error(`There was a problem executing hook: "${hookData.hook}"`);
|
||||||
|
console.error('Parameters:');
|
||||||
|
console.error(hookData.parameters);
|
||||||
|
console.error('Error:');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The process did error
|
||||||
|
*
|
||||||
|
* @param {IExecutionError} error
|
||||||
|
* @param {Date} startedAt
|
||||||
|
* @param {WorkflowExecuteMode} executionMode
|
||||||
|
* @param {string} executionId
|
||||||
|
* @memberof WorkflowRunner
|
||||||
|
*/
|
||||||
|
processError(error: IExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) {
|
||||||
|
const fullRunData: IRun = {
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
error,
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
finished: false,
|
||||||
|
mode: executionMode,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove from active execution with empty data. That will
|
||||||
|
// set the execution to failed.
|
||||||
|
this.activeExecutions.remove(executionId, fullRunData);
|
||||||
|
|
||||||
|
// Also send to Editor UI
|
||||||
|
WorkflowExecuteAdditionalData.pushExecutionFinished(fullRunData, executionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the workflow in subprocess
|
||||||
|
*
|
||||||
|
* @param {IWorkflowExecutionDataProcess} data
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
* @memberof WorkflowRunner
|
||||||
|
*/
|
||||||
|
async run(data: IWorkflowExecutionDataProcess): Promise<string> {
|
||||||
|
const startedAt = new Date();
|
||||||
|
const subprocess = fork('./dist/src/WorkflowRunnerProcess.js');
|
||||||
|
|
||||||
|
// Register the active execution
|
||||||
|
const executionId = this.activeExecutions.add(subprocess, data);
|
||||||
|
|
||||||
|
const nodeTypeData = this.getNodeTypeData(data.workflowData.nodes);
|
||||||
|
|
||||||
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||||
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||||
|
|
||||||
|
const hookFunctions = WorkflowExecuteAdditionalData.getHookMethods(data, executionId);
|
||||||
|
|
||||||
|
// Send all data to subprocess it needs to run the workflow
|
||||||
|
subprocess.send({ type: 'startWorkflow', data } as IProcessMessage);
|
||||||
|
|
||||||
|
// Listen to data from the subprocess
|
||||||
|
subprocess.on('message', (message: IProcessMessage) => {
|
||||||
|
if (message.type === 'end') {
|
||||||
|
this.activeExecutions.remove(executionId!, message.data.runData);
|
||||||
|
} else if (message.type === 'processError') {
|
||||||
|
|
||||||
|
const executionError = message.data.executionError as IExecutionError;
|
||||||
|
|
||||||
|
this.processError(executionError, startedAt, data.executionMode, executionId);
|
||||||
|
|
||||||
|
} else if (message.type === 'processHook') {
|
||||||
|
this.processHookMessage(hookFunctions, message.data as IProcessMessageDataHook);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also get informed when the processes does exit especially when it did crash
|
||||||
|
subprocess.on('exit', (code, signal) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
// Process did exit with error code, so something went wrong.
|
||||||
|
const executionError = {
|
||||||
|
message: 'Workflow execution process did crash for an unknown reason!',
|
||||||
|
} as IExecutionError;
|
||||||
|
|
||||||
|
this.processError(executionError, startedAt, data.executionMode, executionId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return executionId;
|
||||||
|
}
|
||||||
|
}
|
208
packages/cli/src/WorkflowRunnerProcess.ts
Normal file
208
packages/cli/src/WorkflowRunnerProcess.ts
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
IProcessMessageDataHook,
|
||||||
|
IWorkflowExecutionDataProcessWithExecution,
|
||||||
|
NodeTypes,
|
||||||
|
WorkflowExecuteAdditionalData,
|
||||||
|
} from './';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IProcessMessage,
|
||||||
|
WorkflowExecute,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
IExecutionError,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeData,
|
||||||
|
IRun,
|
||||||
|
ITaskData,
|
||||||
|
IWorkflowExecuteHooks,
|
||||||
|
Workflow,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { ChildProcess } from 'child_process';
|
||||||
|
|
||||||
|
export class WorkflowRunnerProcess {
|
||||||
|
data: IWorkflowExecutionDataProcessWithExecution | undefined;
|
||||||
|
startedAt = new Date();
|
||||||
|
workflow: Workflow | undefined;
|
||||||
|
workflowExecute: WorkflowExecute | undefined;
|
||||||
|
|
||||||
|
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
||||||
|
this.data = inputData;
|
||||||
|
let className: string;
|
||||||
|
let tempNode: INodeType;
|
||||||
|
let filePath: string;
|
||||||
|
|
||||||
|
this.startedAt = new Date();
|
||||||
|
|
||||||
|
const nodeTypesData: INodeTypeData = {};
|
||||||
|
for (const nodeTypeName of Object.keys(this.data.nodeTypeData)) {
|
||||||
|
className = this.data.nodeTypeData[nodeTypeName].className;
|
||||||
|
|
||||||
|
filePath = this.data.nodeTypeData[nodeTypeName].sourcePath;
|
||||||
|
const tempModule = require(filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
tempNode = new tempModule[className]() as INodeType;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error loading node "${nodeTypeName}" from: "${filePath}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeTypesData[nodeTypeName] = {
|
||||||
|
type: tempNode,
|
||||||
|
sourcePath: filePath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
await nodeTypes.init(nodeTypesData);
|
||||||
|
|
||||||
|
this.workflow = new Workflow(this.data.workflowData.id as string | undefined, this.data.workflowData!.nodes, this.data.workflowData!.connections, this.data.workflowData!.active, nodeTypes, this.data.workflowData!.staticData);
|
||||||
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.executionMode, this.data.credentials);
|
||||||
|
additionalData.hooks = this.getProcessForwardHooks();
|
||||||
|
|
||||||
|
|
||||||
|
if (this.data.executionData !== undefined) {
|
||||||
|
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode, this.data.executionData);
|
||||||
|
return this.workflowExecute.processRunExecutionData(this.workflow);
|
||||||
|
} else if (this.data.runData === undefined || this.data.startNodes === undefined || this.data.startNodes.length === 0 || this.data.destinationNode === undefined) {
|
||||||
|
// Execute all nodes
|
||||||
|
|
||||||
|
// Can execute without webhook so go on
|
||||||
|
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
||||||
|
return this.workflowExecute.run(this.workflow, undefined, this.data.destinationNode);
|
||||||
|
} else {
|
||||||
|
// Execute only the nodes between start and destination nodes
|
||||||
|
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
||||||
|
return this.workflowExecute.runPartialWorkflow(this.workflow, this.data.runData, this.data.startNodes, this.data.destinationNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any
|
||||||
|
(process as unknown as ChildProcess).send({
|
||||||
|
type: 'processHook',
|
||||||
|
data: {
|
||||||
|
hook,
|
||||||
|
parameters,
|
||||||
|
} as IProcessMessageDataHook,
|
||||||
|
} as IProcessMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wrapper for hooks which simply forwards the data to
|
||||||
|
* the parent process where they then can be executed with access
|
||||||
|
* to database and to PushService
|
||||||
|
*
|
||||||
|
* @param {ChildProcess} process
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getProcessForwardHooks(): IWorkflowExecuteHooks {
|
||||||
|
return {
|
||||||
|
nodeExecuteBefore: [
|
||||||
|
async (nodeName: string): Promise<void> => {
|
||||||
|
this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nodeExecuteAfter: [
|
||||||
|
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||||
|
this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
workflowExecuteBefore: [
|
||||||
|
async (): Promise<void> => {
|
||||||
|
this.sendHookToParentProcess('workflowExecuteBefore', []);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
workflowExecuteAfter: [
|
||||||
|
async (fullRunData: IRun, newStaticData?: IDataObject): Promise<void> => {
|
||||||
|
this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]);
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends data to parent process
|
||||||
|
*
|
||||||
|
* @param {string} type The type of data to send
|
||||||
|
* @param {*} data The data
|
||||||
|
*/
|
||||||
|
function sendToParentProcess(type: string, data: any): void { // tslint:disable-line:no-any
|
||||||
|
process.send!({
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const workflowRunner = new WorkflowRunnerProcess();
|
||||||
|
|
||||||
|
|
||||||
|
// Listen to messages from parent process which send the data of
|
||||||
|
// the worflow to process
|
||||||
|
process.on('message', async (message: IProcessMessage) => {
|
||||||
|
try {
|
||||||
|
if (message.type === 'startWorkflow') {
|
||||||
|
const runData = await workflowRunner.runWorkflow(message.data);
|
||||||
|
|
||||||
|
sendToParentProcess('end', {
|
||||||
|
runData,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Once the workflow got executed make sure the process gets killed again
|
||||||
|
process.exit();
|
||||||
|
} else if (message.type === 'stopExecution') {
|
||||||
|
// The workflow execution should be stopped
|
||||||
|
let fullRunData: IRun;
|
||||||
|
|
||||||
|
if (workflowRunner.workflowExecute !== undefined) {
|
||||||
|
// Workflow started already executing
|
||||||
|
|
||||||
|
fullRunData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
|
||||||
|
|
||||||
|
// If there is any data send it to parent process
|
||||||
|
await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!);
|
||||||
|
} else {
|
||||||
|
// Workflow did not get started yet
|
||||||
|
fullRunData = {
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
finished: true,
|
||||||
|
mode: workflowRunner.data!.executionMode,
|
||||||
|
startedAt: workflowRunner.startedAt,
|
||||||
|
stoppedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [fullRunData]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToParentProcess('end', {
|
||||||
|
fullRunData,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop process
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Catch all uncaught errors and forward them to parent process
|
||||||
|
const executionError = {
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
} as IExecutionError;
|
||||||
|
|
||||||
|
sendToParentProcess('processError', {
|
||||||
|
executionError,
|
||||||
|
});
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
});
|
|
@ -3,8 +3,9 @@ export * from './Interfaces';
|
||||||
export * from './LoadNodesAndCredentials';
|
export * from './LoadNodesAndCredentials';
|
||||||
export * from './NodeTypes';
|
export * from './NodeTypes';
|
||||||
export * from './WorkflowCredentials';
|
export * from './WorkflowCredentials';
|
||||||
|
export * from './WorkflowRunner';
|
||||||
|
|
||||||
|
import * as ActiveExecutions from './ActiveExecutions';
|
||||||
import * as ActiveWorkflowRunner from './ActiveWorkflowRunner';
|
import * as ActiveWorkflowRunner from './ActiveWorkflowRunner';
|
||||||
import * as Db from './Db';
|
import * as Db from './Db';
|
||||||
import * as GenericHelpers from './GenericHelpers';
|
import * as GenericHelpers from './GenericHelpers';
|
||||||
|
@ -16,6 +17,7 @@ import * as WebhookHelpers from './WebhookHelpers';
|
||||||
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
||||||
import * as WorkflowHelpers from './WorkflowHelpers';
|
import * as WorkflowHelpers from './WorkflowHelpers';
|
||||||
export {
|
export {
|
||||||
|
ActiveExecutions,
|
||||||
ActiveWorkflowRunner,
|
ActiveWorkflowRunner,
|
||||||
Db,
|
Db,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import {
|
import {
|
||||||
|
IGetExecuteTriggerFunctions,
|
||||||
ITriggerResponse,
|
ITriggerResponse,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
|
||||||
NodeExecuteFunctions,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
export interface WorkflowData {
|
export interface WorkflowData {
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
|
@ -65,7 +62,7 @@ export class ActiveWorkflows {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflows
|
* @memberof ActiveWorkflows
|
||||||
*/
|
*/
|
||||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData): Promise<void> {
|
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions): Promise<void> {
|
||||||
console.log('ADD ID (active): ' + id);
|
console.log('ADD ID (active): ' + id);
|
||||||
|
|
||||||
this.workflowData[id] = {
|
this.workflowData[id] = {
|
||||||
|
@ -75,7 +72,7 @@ export class ActiveWorkflows {
|
||||||
|
|
||||||
let triggerResponse: ITriggerResponse | undefined;
|
let triggerResponse: ITriggerResponse | undefined;
|
||||||
for (const triggerNode of triggerNodes) {
|
for (const triggerNode of triggerNodes) {
|
||||||
triggerResponse = await workflow.runTrigger(triggerNode, NodeExecuteFunctions, additionalData, 'trigger');
|
triggerResponse = await workflow.runTrigger(triggerNode, getTriggerFunctions, additionalData, 'trigger');
|
||||||
if (triggerResponse !== undefined) {
|
if (triggerResponse !== undefined) {
|
||||||
// If a response was given save it
|
// If a response was given save it
|
||||||
this.workflowData[id].triggerResponse = triggerResponse;
|
this.workflowData[id].triggerResponse = triggerResponse;
|
||||||
|
|
|
@ -8,18 +8,12 @@ import {
|
||||||
ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
|
ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeType,
|
INodeType,
|
||||||
IRun,
|
|
||||||
IRunExecutionData,
|
|
||||||
ITriggerFunctions as ITriggerFunctionsBase,
|
ITriggerFunctions as ITriggerFunctionsBase,
|
||||||
IWebhookFunctions as IWebhookFunctionsBase,
|
IWebhookFunctions as IWebhookFunctionsBase,
|
||||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||||
Workflow,
|
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
|
||||||
IDeferredPromise
|
|
||||||
} from '.';
|
|
||||||
|
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
import * as requestPromise from 'request-promise-native';
|
import * as requestPromise from 'request-promise-native';
|
||||||
|
@ -29,6 +23,12 @@ interface Constructable<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IProcessMessage {
|
||||||
|
data?: any; // tslint:disable-line:no-any
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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>;
|
||||||
|
@ -45,13 +45,6 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecutingWorkflowData {
|
|
||||||
runExecutionData: IRunExecutionData;
|
|
||||||
startedAt: Date;
|
|
||||||
mode: WorkflowExecuteMode;
|
|
||||||
workflow: Workflow;
|
|
||||||
postExecutePromises: Array<IDeferredPromise<IRun>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExecutionsCurrentSummary {
|
export interface IExecutionsCurrentSummary {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -314,33 +314,12 @@ export function getWebhookDescription(name: string, workflow: Workflow, node: IN
|
||||||
* @param {WorkflowExecuteMode} mode
|
* @param {WorkflowExecuteMode} mode
|
||||||
* @returns {ITriggerFunctions}
|
* @returns {ITriggerFunctions}
|
||||||
*/
|
*/
|
||||||
|
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
||||||
export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions {
|
export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions {
|
||||||
return ((workflow: Workflow, node: INode) => {
|
return ((workflow: Workflow, node: INode) => {
|
||||||
return {
|
return {
|
||||||
emit: (data: INodeExecutionData[][]): void => {
|
emit: (data: INodeExecutionData[][]): void => {
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, mode);
|
throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!');
|
||||||
const nodeExecutionStack: IExecuteData[] = [
|
|
||||||
{
|
|
||||||
node,
|
|
||||||
data: {
|
|
||||||
main: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
|
||||||
startData: {},
|
|
||||||
resultData: {
|
|
||||||
runData: {},
|
|
||||||
},
|
|
||||||
executionData: {
|
|
||||||
contextData: {},
|
|
||||||
nodeExecutionStack,
|
|
||||||
waitingExecution: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
workflowExecute.runExecutionData(workflow, runExecutionData);
|
|
||||||
},
|
},
|
||||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||||
return getCredentials(workflow, node, type, additionalData);
|
return getCredentials(workflow, node, type, additionalData);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
IConnection,
|
IConnection,
|
||||||
|
IDataObject,
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
IExecutionError,
|
IExecutionError,
|
||||||
INode,
|
INode,
|
||||||
|
@ -16,21 +17,30 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
export class WorkflowExecute {
|
export class WorkflowExecute {
|
||||||
|
runExecutionData: IRunExecutionData;
|
||||||
private additionalData: IWorkflowExecuteAdditionalData;
|
private additionalData: IWorkflowExecuteAdditionalData;
|
||||||
private mode: WorkflowExecuteMode;
|
private mode: WorkflowExecuteMode;
|
||||||
private activeExecutions: ActiveExecutions.ActiveExecutions;
|
|
||||||
private executionId: string | null = null;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode) {
|
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
||||||
this.additionalData = additionalData;
|
this.additionalData = additionalData;
|
||||||
this.activeExecutions = ActiveExecutions.getInstance();
|
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.runExecutionData = runExecutionData || {
|
||||||
|
startData: {
|
||||||
|
},
|
||||||
|
resultData: {
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
executionData: {
|
||||||
|
contextData: {},
|
||||||
|
nodeExecutionStack: [],
|
||||||
|
waitingExecution: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +54,7 @@ export class WorkflowExecute {
|
||||||
* @returns {(Promise<string>)}
|
* @returns {(Promise<string>)}
|
||||||
* @memberof WorkflowExecute
|
* @memberof WorkflowExecute
|
||||||
*/
|
*/
|
||||||
async run(workflow: Workflow, startNode?: INode, destinationNode?: string): Promise<string> {
|
async run(workflow: Workflow, startNode?: INode, destinationNode?: string): Promise<IRun> {
|
||||||
// Get the nodes to start workflow execution from
|
// Get the nodes to start workflow execution from
|
||||||
startNode = startNode || workflow.getStartNode(destinationNode);
|
startNode = startNode || workflow.getStartNode(destinationNode);
|
||||||
|
|
||||||
|
@ -75,7 +85,7 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
this.runExecutionData = {
|
||||||
startData: {
|
startData: {
|
||||||
destinationNode,
|
destinationNode,
|
||||||
runNodeFilter,
|
runNodeFilter,
|
||||||
|
@ -90,7 +100,7 @@ export class WorkflowExecute {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.runExecutionData(workflow, runExecutionData);
|
return this.processRunExecutionData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,8 +115,7 @@ export class WorkflowExecute {
|
||||||
* @returns {(Promise<string>)}
|
* @returns {(Promise<string>)}
|
||||||
* @memberof WorkflowExecute
|
* @memberof WorkflowExecute
|
||||||
*/
|
*/
|
||||||
async runPartialWorkflow(workflow: Workflow, runData: IRunData, startNodes: string[], destinationNode: string): Promise<string> {
|
async runPartialWorkflow(workflow: Workflow, runData: IRunData, startNodes: string[], destinationNode: string): Promise<IRun> {
|
||||||
|
|
||||||
let incomingNodeConnections: INodeConnections | undefined;
|
let incomingNodeConnections: INodeConnections | undefined;
|
||||||
let connection: IConnection;
|
let connection: IConnection;
|
||||||
|
|
||||||
|
@ -185,8 +194,7 @@ export class WorkflowExecute {
|
||||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||||
runNodeFilter.push(destinationNode);
|
runNodeFilter.push(destinationNode);
|
||||||
|
|
||||||
|
this.runExecutionData = {
|
||||||
const runExecutionData: IRunExecutionData = {
|
|
||||||
startData: {
|
startData: {
|
||||||
destinationNode,
|
destinationNode,
|
||||||
runNodeFilter,
|
runNodeFilter,
|
||||||
|
@ -201,7 +209,7 @@ export class WorkflowExecute {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.runExecutionData(workflow, runExecutionData);
|
return await this.processRunExecutionData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,7 +248,7 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
addNodeToBeExecuted(workflow: Workflow, runExecutionData: IRunExecutionData, connectionData: IConnection, outputIndex: number, parentNodeName: string, nodeSuccessData: INodeExecutionData[][], runIndex: number): void {
|
addNodeToBeExecuted(workflow: Workflow, connectionData: IConnection, outputIndex: number, parentNodeName: string, nodeSuccessData: INodeExecutionData[][], runIndex: number): void {
|
||||||
let stillDataMissing = false;
|
let stillDataMissing = false;
|
||||||
|
|
||||||
// Check if node has multiple inputs as then we have to wait for all input data
|
// Check if node has multiple inputs as then we have to wait for all input data
|
||||||
|
@ -250,33 +258,33 @@ export class WorkflowExecute {
|
||||||
let nodeWasWaiting = true;
|
let nodeWasWaiting = true;
|
||||||
|
|
||||||
// Check if there is already data for the node
|
// Check if there is already data for the node
|
||||||
if (runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined) {
|
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined) {
|
||||||
// Node does not have data yet so create a new empty one
|
// Node does not have data yet so create a new empty one
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||||
nodeWasWaiting = false;
|
nodeWasWaiting = false;
|
||||||
}
|
}
|
||||||
if (runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] === undefined) {
|
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] === undefined) {
|
||||||
// Node does not have data for runIndex yet so create also empty one and init it
|
// Node does not have data for runIndex yet so create also empty one and init it
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||||
main: []
|
main: []
|
||||||
};
|
};
|
||||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the new data
|
// Add the new data
|
||||||
if (nodeSuccessData === null) {
|
if (nodeSuccessData === null) {
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = null;
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = null;
|
||||||
} else {
|
} else {
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = nodeSuccessData[outputIndex];
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = nodeSuccessData[outputIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all data exists now
|
// Check if all data exists now
|
||||||
let thisExecutionData: INodeExecutionData[] | null;
|
let thisExecutionData: INodeExecutionData[] | null;
|
||||||
let allDataFound = true;
|
let allDataFound = true;
|
||||||
for (let i = 0; i < runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
for (let i = 0; i < this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
||||||
thisExecutionData = runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
thisExecutionData = this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
||||||
if (thisExecutionData === null) {
|
if (thisExecutionData === null) {
|
||||||
allDataFound = false;
|
allDataFound = false;
|
||||||
break;
|
break;
|
||||||
|
@ -286,17 +294,17 @@ export class WorkflowExecute {
|
||||||
if (allDataFound === true) {
|
if (allDataFound === true) {
|
||||||
// All data exists for node to be executed
|
// All data exists for node to be executed
|
||||||
// So add it to the execution stack
|
// So add it to the execution stack
|
||||||
runExecutionData.executionData!.nodeExecutionStack.push({
|
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||||
node: workflow.nodes[connectionData.node],
|
node: workflow.nodes[connectionData.node],
|
||||||
data: runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex]
|
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove the data from waiting
|
// Remove the data from waiting
|
||||||
delete runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
||||||
|
|
||||||
if (Object.keys(runExecutionData.executionData!.waitingExecution[connectionData.node]).length === 0) {
|
if (Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node]).length === 0) {
|
||||||
// No more data left for the node so also delete that one
|
// No more data left for the node so also delete that one
|
||||||
delete runExecutionData.executionData!.waitingExecution[connectionData.node];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -327,7 +335,7 @@ export class WorkflowExecute {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionStackNodes = runExecutionData.executionData!.nodeExecutionStack.map((stackData) => stackData.node.name);
|
const executionStackNodes = this.runExecutionData.executionData!.nodeExecutionStack.map((stackData) => stackData.node.name);
|
||||||
|
|
||||||
// Check if that node is also an output connection of the
|
// Check if that node is also an output connection of the
|
||||||
// previously processed one
|
// previously processed one
|
||||||
|
@ -345,7 +353,7 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if node got processed already
|
// Check if node got processed already
|
||||||
if (runExecutionData.resultData.runData[inputData.node] !== undefined) {
|
if (this.runExecutionData.resultData.runData[inputData.node] !== undefined) {
|
||||||
// Node got processed already so no need to add it
|
// Node got processed already so no need to add it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -376,7 +384,7 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if node got processed already
|
// Check if node got processed already
|
||||||
if (runExecutionData.resultData.runData[parentNode] !== undefined) {
|
if (this.runExecutionData.resultData.runData[parentNode] !== undefined) {
|
||||||
// Node got processed already so we can use the
|
// Node got processed already so we can use the
|
||||||
// output data as input of this node
|
// output data as input of this node
|
||||||
break;
|
break;
|
||||||
|
@ -393,7 +401,7 @@ export class WorkflowExecute {
|
||||||
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
||||||
// Add only node if it does not have any inputs becuase else it will
|
// Add only node if it does not have any inputs becuase else it will
|
||||||
// be added by its input node later anyway.
|
// be added by its input node later anyway.
|
||||||
runExecutionData.executionData!.nodeExecutionStack.push(
|
this.runExecutionData.executionData!.nodeExecutionStack.push(
|
||||||
{
|
{
|
||||||
node: workflow.getNode(nodeToAdd) as INode,
|
node: workflow.getNode(nodeToAdd) as INode,
|
||||||
data: {
|
data: {
|
||||||
|
@ -428,15 +436,15 @@ export class WorkflowExecute {
|
||||||
|
|
||||||
if (stillDataMissing === true) {
|
if (stillDataMissing === true) {
|
||||||
// Additional data is needed to run node so add it to waiting
|
// Additional data is needed to run node so add it to waiting
|
||||||
if (!runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)) {
|
if (!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)) {
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||||
}
|
}
|
||||||
runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||||
main: connectionDataArray
|
main: connectionDataArray
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// All data is there so add it directly to stack
|
// All data is there so add it directly to stack
|
||||||
runExecutionData.executionData!.nodeExecutionStack.push({
|
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||||
node: workflow.nodes[connectionData.node],
|
node: workflow.nodes[connectionData.node],
|
||||||
data: {
|
data: {
|
||||||
main: connectionDataArray
|
main: connectionDataArray
|
||||||
|
@ -450,12 +458,11 @@ export class WorkflowExecute {
|
||||||
* Runs the given execution data.
|
* Runs the given execution data.
|
||||||
*
|
*
|
||||||
* @param {Workflow} workflow
|
* @param {Workflow} workflow
|
||||||
* @param {IRunExecutionData} runExecutionData
|
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @memberof WorkflowExecute
|
* @memberof WorkflowExecute
|
||||||
*/
|
*/
|
||||||
async runExecutionData(workflow: Workflow, runExecutionData: IRunExecutionData): Promise<string> {
|
async processRunExecutionData(workflow: Workflow): Promise<IRun> {
|
||||||
const startedAt = new Date().getTime();
|
const startedAt = new Date();
|
||||||
|
|
||||||
const workflowIssues = workflow.checkReadyForExecution();
|
const workflowIssues = workflow.checkReadyForExecution();
|
||||||
if (workflowIssues !== null) {
|
if (workflowIssues !== null) {
|
||||||
|
@ -471,39 +478,29 @@ export class WorkflowExecute {
|
||||||
let startTime: number;
|
let startTime: number;
|
||||||
let taskData: ITaskData;
|
let taskData: ITaskData;
|
||||||
|
|
||||||
if (runExecutionData.startData === undefined) {
|
if (this.runExecutionData.startData === undefined) {
|
||||||
runExecutionData.startData = {};
|
this.runExecutionData.startData = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.executionId = this.activeExecutions.add(workflow, runExecutionData, this.mode);
|
this.executeHook('workflowExecuteBefore', []);
|
||||||
|
|
||||||
this.executeHook('workflowExecuteBefore', [this.executionId]);
|
|
||||||
|
|
||||||
let currentExecutionTry = '';
|
let currentExecutionTry = '';
|
||||||
let lastExecutionTry = '';
|
let lastExecutionTry = '';
|
||||||
|
|
||||||
// Wait for the next tick so that the executionId gets already returned.
|
return (async () => {
|
||||||
// So it can directly be send to the editor-ui and is so aware of the
|
|
||||||
// executionId when the first push messages arrive.
|
|
||||||
process.nextTick(() => (async () => {
|
|
||||||
executionLoop:
|
executionLoop:
|
||||||
while (runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||||
if (this.activeExecutions.shouldBeStopped(this.executionId!) === true) {
|
|
||||||
// The execution should be stopped
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeSuccessData = null;
|
nodeSuccessData = null;
|
||||||
executionError = undefined;
|
executionError = undefined;
|
||||||
executionData = runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||||
executionNode = executionData.node;
|
executionNode = executionData.node;
|
||||||
|
|
||||||
this.executeHook('nodeExecuteBefore', [this.executionId, executionNode.name]);
|
this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||||
|
|
||||||
// Get the index of the current run
|
// Get the index of the current run
|
||||||
runIndex = 0;
|
runIndex = 0;
|
||||||
if (runExecutionData.resultData.runData.hasOwnProperty(executionNode.name)) {
|
if (this.runExecutionData.resultData.runData.hasOwnProperty(executionNode.name)) {
|
||||||
runIndex = runExecutionData.resultData.runData[executionNode.name].length;
|
runIndex = this.runExecutionData.resultData.runData[executionNode.name].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentExecutionTry = `${executionNode.name}:${runIndex}`;
|
currentExecutionTry = `${executionNode.name}:${runIndex}`;
|
||||||
|
@ -512,7 +509,7 @@ export class WorkflowExecute {
|
||||||
throw new Error('Did stop execution because execution seems to be in endless loop.');
|
throw new Error('Did stop execution because execution seems to be in endless loop.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runExecutionData.startData!.runNodeFilter !== undefined && runExecutionData.startData!.runNodeFilter!.indexOf(executionNode.name) === -1) {
|
if (this.runExecutionData.startData!.runNodeFilter !== undefined && this.runExecutionData.startData!.runNodeFilter!.indexOf(executionNode.name) === -1) {
|
||||||
// If filter is set and node is not on filter skip it, that avoids the problem that it executes
|
// If filter is set and node is not on filter skip it, that avoids the problem that it executes
|
||||||
// leafs that are parallel to a selected destinationNode. Normally it would execute them because
|
// leafs that are parallel to a selected destinationNode. Normally it would execute them because
|
||||||
// they have the same parent and it executes all child nodes.
|
// they have the same parent and it executes all child nodes.
|
||||||
|
@ -539,7 +536,7 @@ export class WorkflowExecute {
|
||||||
if (!executionData.data!.hasOwnProperty('main')) {
|
if (!executionData.data!.hasOwnProperty('main')) {
|
||||||
// ExecutionData does not even have the connection set up so can
|
// ExecutionData does not even have the connection set up so can
|
||||||
// not have that data, so add it again to be executed later
|
// not have that data, so add it again to be executed later
|
||||||
runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||||
lastExecutionTry = currentExecutionTry;
|
lastExecutionTry = currentExecutionTry;
|
||||||
continue executionLoop;
|
continue executionLoop;
|
||||||
}
|
}
|
||||||
|
@ -549,7 +546,7 @@ export class WorkflowExecute {
|
||||||
// of both inputs has to be available to be able to process the node.
|
// of both inputs has to be available to be able to process the node.
|
||||||
if (executionData.data!.main!.length < connectionIndex || executionData.data!.main![connectionIndex] === null) {
|
if (executionData.data!.main!.length < connectionIndex || executionData.data!.main![connectionIndex] === null) {
|
||||||
// Does not have the data of the connections so add back to stack
|
// Does not have the data of the connections so add back to stack
|
||||||
runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||||
lastExecutionTry = currentExecutionTry;
|
lastExecutionTry = currentExecutionTry;
|
||||||
continue executionLoop;
|
continue executionLoop;
|
||||||
}
|
}
|
||||||
|
@ -591,15 +588,8 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check again if the execution should be stopped else it
|
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||||
// could take forever to stop when each try takes a long time
|
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
||||||
if (this.activeExecutions.shouldBeStopped(this.executionId!) === true) {
|
|
||||||
// The execution should be stopped
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
|
||||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
|
||||||
|
|
||||||
if (nodeSuccessData === null) {
|
if (nodeSuccessData === null) {
|
||||||
// If null gets returned it means that the node did succeed
|
// If null gets returned it means that the node did succeed
|
||||||
|
@ -620,8 +610,8 @@ export class WorkflowExecute {
|
||||||
// Add the data to return to the user
|
// Add the data to return to the user
|
||||||
// (currently does not get cloned as data does not get changed, maybe later we should do that?!?!)
|
// (currently does not get cloned as data does not get changed, maybe later we should do that?!?!)
|
||||||
|
|
||||||
if (!runExecutionData.resultData.runData.hasOwnProperty(executionNode.name)) {
|
if (!this.runExecutionData.resultData.runData.hasOwnProperty(executionNode.name)) {
|
||||||
runExecutionData.resultData.runData[executionNode.name] = [];
|
this.runExecutionData.resultData.runData[executionNode.name] = [];
|
||||||
}
|
}
|
||||||
taskData = {
|
taskData = {
|
||||||
startTime,
|
startTime,
|
||||||
|
@ -642,12 +632,12 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Node execution did fail so add error and stop execution
|
// Node execution did fail so add error and stop execution
|
||||||
runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||||
|
|
||||||
// Add the execution data again so that it can get restarted
|
// Add the execution data again so that it can get restarted
|
||||||
runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||||
|
|
||||||
this.executeHook('nodeExecuteAfter', [this.executionId, executionNode.name, taskData]);
|
this.executeHook('nodeExecuteAfter', [executionNode.name, taskData]);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -658,11 +648,11 @@ export class WorkflowExecute {
|
||||||
'main': nodeSuccessData
|
'main': nodeSuccessData
|
||||||
} as ITaskDataConnections);
|
} as ITaskDataConnections);
|
||||||
|
|
||||||
this.executeHook('nodeExecuteAfter', [this.executionId, executionNode.name, taskData]);
|
this.executeHook('nodeExecuteAfter', [executionNode.name, taskData]);
|
||||||
|
|
||||||
runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||||
|
|
||||||
if (runExecutionData.startData && runExecutionData.startData.destinationNode && runExecutionData.startData.destinationNode === executionNode.name) {
|
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode && this.runExecutionData.startData.destinationNode === executionNode.name) {
|
||||||
// If destination node is defined and got executed stop execution
|
// If destination node is defined and got executed stop execution
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -686,7 +676,7 @@ export class WorkflowExecute {
|
||||||
return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
|
return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addNodeToBeExecuted(workflow, runExecutionData, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,45 +686,61 @@ export class WorkflowExecute {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
})()
|
})()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const fullRunData: IRun = {
|
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||||
data: runExecutionData,
|
|
||||||
mode: this.mode,
|
|
||||||
startedAt: new Date(startedAt),
|
|
||||||
stoppedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (executionError !== undefined) {
|
|
||||||
fullRunData.data.resultData.error = executionError;
|
|
||||||
} else {
|
|
||||||
fullRunData.finished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeExecutions.remove(this.executionId!, fullRunData);
|
|
||||||
|
|
||||||
await this.executeHook('workflowExecuteAfter', [fullRunData, this.executionId!]);
|
|
||||||
|
|
||||||
return fullRunData;
|
|
||||||
})
|
})
|
||||||
.catch(async (error) => {
|
.catch(async (error) => {
|
||||||
const fullRunData: IRun = {
|
const fullRunData = this.getFullRunData(startedAt);
|
||||||
data: runExecutionData,
|
|
||||||
mode: this.mode,
|
|
||||||
startedAt: new Date(startedAt),
|
|
||||||
stoppedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
fullRunData.data.resultData.error = {
|
fullRunData.data.resultData.error = {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.activeExecutions.remove(this.executionId!, fullRunData);
|
// Check if static data changed
|
||||||
|
let newStaticData: IDataObject | undefined;
|
||||||
|
if (workflow.staticData.__dataChanged === true) {
|
||||||
|
// Static data of workflow changed
|
||||||
|
newStaticData = workflow.staticData;
|
||||||
|
}
|
||||||
|
|
||||||
await this.executeHook('workflowExecuteAfter', [fullRunData, this.executionId!]);
|
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]);
|
||||||
|
|
||||||
return fullRunData;
|
return fullRunData;
|
||||||
}));
|
});
|
||||||
|
|
||||||
return this.executionId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: IExecutionError): Promise<IRun> {
|
||||||
|
const fullRunData = this.getFullRunData(startedAt);
|
||||||
|
|
||||||
|
if (executionError !== undefined) {
|
||||||
|
fullRunData.data.resultData.error = executionError;
|
||||||
|
} else {
|
||||||
|
fullRunData.finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if static data changed
|
||||||
|
let newStaticData: IDataObject | undefined;
|
||||||
|
if (workflow.staticData.__dataChanged === true) {
|
||||||
|
// Static data of workflow changed
|
||||||
|
newStaticData = workflow.staticData;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]);
|
||||||
|
|
||||||
|
return fullRunData;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullRunData(startedAt: Date): IRun {
|
||||||
|
const fullRunData: IRun = {
|
||||||
|
data: this.runExecutionData,
|
||||||
|
mode: this.mode,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return fullRunData;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,9 @@ export * from './LoadNodeParameterOptions';
|
||||||
export * from './NodeExecuteFunctions';
|
export * from './NodeExecuteFunctions';
|
||||||
export * from './WorkflowExecute';
|
export * from './WorkflowExecute';
|
||||||
|
|
||||||
import * as ActiveExecutions from './ActiveExecutions';
|
|
||||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||||
import * as UserSettings from './UserSettings';
|
import * as UserSettings from './UserSettings';
|
||||||
export {
|
export {
|
||||||
ActiveExecutions,
|
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
UserSettings,
|
UserSettings,
|
||||||
};
|
};
|
||||||
|
|
|
@ -160,11 +160,17 @@ export const pushConnection = mixins(
|
||||||
|
|
||||||
const runDataExecuted = pushData.data;
|
const runDataExecuted = pushData.data;
|
||||||
|
|
||||||
|
|
||||||
if (runDataExecuted.finished !== true) {
|
if (runDataExecuted.finished !== true) {
|
||||||
// There was a problem with executing the workflow
|
// There was a problem with executing the workflow
|
||||||
|
let errorMessage = 'There was a problem executing the workflow!';
|
||||||
|
if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) {
|
||||||
|
errorMessage = `There was a problem executing the workflow:<br /><strong>"${runDataExecuted.data.resultData.error.message}"</strong>`;
|
||||||
|
}
|
||||||
|
|
||||||
this.$showMessage({
|
this.$showMessage({
|
||||||
title: 'Problem executing workflow',
|
title: 'Problem executing workflow',
|
||||||
message: 'There was a problem executing the workflow!',
|
message: errorMessage,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -106,6 +106,31 @@ export interface IDataObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetExecuteTriggerFunctions {
|
||||||
|
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetExecuteFunctions {
|
||||||
|
(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetExecuteSingleFunctions {
|
||||||
|
(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, itemIndex: number, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteSingleFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetExecuteHookFunctions {
|
||||||
|
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetExecuteWebhookFunctions {
|
||||||
|
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, webhookData: IWebhookData): IWebhookFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteData {
|
export interface IExecuteData {
|
||||||
data: ITaskDataConnections;
|
data: ITaskDataConnections;
|
||||||
node: INode;
|
node: INode;
|
||||||
|
@ -250,11 +275,11 @@ export interface INodeExecutionData {
|
||||||
|
|
||||||
|
|
||||||
export interface INodeExecuteFunctions {
|
export interface INodeExecuteFunctions {
|
||||||
getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions;
|
getExecuteTriggerFunctions: IGetExecuteTriggerFunctions;
|
||||||
getExecuteFunctions(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteFunctions;
|
getExecuteFunctions: IGetExecuteFunctions;
|
||||||
getExecuteSingleFunctions(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, itemIndex: number, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteSingleFunctions;
|
getExecuteSingleFunctions: IGetExecuteSingleFunctions;
|
||||||
getExecuteHookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions;
|
getExecuteHookFunctions: IGetExecuteHookFunctions;
|
||||||
getExecuteWebhookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, webhookData: IWebhookData): IWebhookFunctions;
|
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -452,17 +477,21 @@ export interface IWebhookResonseData {
|
||||||
export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryBinary';
|
export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryBinary';
|
||||||
export type WebhookResponseMode = 'onReceived' | 'lastNode';
|
export type WebhookResponseMode = 'onReceived' | 'lastNode';
|
||||||
|
|
||||||
export interface INodeTypesObject {
|
|
||||||
[key: string]: INodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodeTypes {
|
export interface INodeTypes {
|
||||||
init(nodeTypes?: INodeTypesObject): Promise<void>;
|
nodeTypes: INodeTypeData;
|
||||||
|
init(nodeTypes?: INodeTypeData): Promise<void>;
|
||||||
getAll(): INodeType[];
|
getAll(): INodeType[];
|
||||||
getByName(nodeType: string): INodeType | undefined;
|
getByName(nodeType: string): INodeType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface INodeTypeData {
|
||||||
|
[key: string]: {
|
||||||
|
type: INodeType;
|
||||||
|
sourcePath: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRun {
|
export interface IRun {
|
||||||
data: IRunExecutionData;
|
data: IRunExecutionData;
|
||||||
finished?: boolean;
|
finished?: boolean;
|
||||||
|
@ -537,19 +566,17 @@ export interface IWorkflowCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowExecuteHooks {
|
export interface IWorkflowExecuteHooks {
|
||||||
afterExecute? (data: IRun, waitingExecutionData: IWaitingForExecution): Promise<void>;
|
[key: string]: Array<((...args: any[]) => Promise<void>)> | undefined; // tslint:disable-line:no-any
|
||||||
|
nodeExecuteAfter?: Array<((nodeName: string, data: ITaskData) => Promise<void>)>;
|
||||||
|
nodeExecuteBefore?: Array<((nodeName: string) => Promise<void>)>;
|
||||||
|
workflowExecuteAfter?: Array<((data: IRun, newStaticData: IDataObject) => Promise<void>)>;
|
||||||
|
workflowExecuteBefore?: Array<(() => Promise<void>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowExecuteAdditionalData {
|
export interface IWorkflowExecuteAdditionalData {
|
||||||
credentials: IWorkflowCredentials;
|
credentials: IWorkflowCredentials;
|
||||||
encryptionKey: string;
|
encryptionKey: string;
|
||||||
hooks?: {
|
hooks?: IWorkflowExecuteHooks;
|
||||||
[key: string]: Array<((...args: any[]) => Promise<void>)> | undefined; // tslint:disable-line:no-any
|
|
||||||
nodeExecuteAfter?: Array<((executionId: string, nodeName: string, data: ITaskData) => Promise<void>)>;
|
|
||||||
nodeExecuteBefore?: Array<((nodeName: string, executionId: string) => Promise<void>)>;
|
|
||||||
workflowExecuteAfter?: Array<((data: IRun, executionId: string) => Promise<void>)>;
|
|
||||||
workflowExecuteBefore?: Array<((executionId: string) => Promise<void>)>;
|
|
||||||
};
|
|
||||||
httpResponse?: express.Response;
|
httpResponse?: express.Response;
|
||||||
httpRequest?: express.Request;
|
httpRequest?: express.Request;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IConnections,
|
IConnections,
|
||||||
|
IGetExecuteTriggerFunctions,
|
||||||
INode,
|
INode,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
INodes,
|
INodes,
|
||||||
|
@ -954,14 +955,14 @@ export class Workflow {
|
||||||
* when the node has data.
|
* when the node has data.
|
||||||
*
|
*
|
||||||
* @param {INode} node
|
* @param {INode} node
|
||||||
* @param {INodeExecuteFunctions} nodeExecuteFunctions
|
* @param {IGetExecuteTriggerFunctions} getTriggerFunctions
|
||||||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||||
* @param {WorkflowExecuteMode} mode
|
* @param {WorkflowExecuteMode} mode
|
||||||
* @returns {(Promise<ITriggerResponse | undefined>)}
|
* @returns {(Promise<ITriggerResponse | undefined>)}
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
async runTrigger(node: INode, nodeExecuteFunctions: INodeExecuteFunctions, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): Promise<ITriggerResponse | undefined> {
|
async runTrigger(node: INode, getTriggerFunctions: IGetExecuteTriggerFunctions, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): Promise<ITriggerResponse | undefined> {
|
||||||
const thisArgs = nodeExecuteFunctions.getExecuteTriggerFunctions(this, node, additionalData, mode);
|
const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode);
|
||||||
|
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByName(node.type);
|
||||||
|
|
||||||
|
@ -976,11 +977,11 @@ export class Workflow {
|
||||||
if (mode === 'manual') {
|
if (mode === 'manual') {
|
||||||
// In manual mode we do not just start the trigger function we also
|
// In manual mode we do not just start the trigger function we also
|
||||||
// want to be able to get informed as soon as the first data got emitted
|
// want to be able to get informed as soon as the first data got emitted
|
||||||
const triggerReponse = await nodeType.trigger!.call(thisArgs);
|
const triggerReponse = await nodeType.trigger!.call(triggerFunctions);
|
||||||
|
|
||||||
// Add the manual trigger response which resolves when the first time data got emitted
|
// Add the manual trigger response which resolves when the first time data got emitted
|
||||||
triggerReponse!.manualTriggerResponse = new Promise((resolve) => {
|
triggerReponse!.manualTriggerResponse = new Promise((resolve) => {
|
||||||
thisArgs.emit = ((resolve) => (data: INodeExecutionData[][]) => {
|
triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
})(resolve);
|
})(resolve);
|
||||||
});
|
});
|
||||||
|
@ -988,7 +989,7 @@ export class Workflow {
|
||||||
return triggerReponse;
|
return triggerReponse;
|
||||||
} else {
|
} else {
|
||||||
// In all other modes simply start the trigger
|
// In all other modes simply start the trigger
|
||||||
return nodeType.trigger!.call(thisArgs);
|
return nodeType.trigger!.call(triggerFunctions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1089,7 +1090,7 @@ export class Workflow {
|
||||||
} 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
|
||||||
const triggerResponse = await this.runTrigger(node, nodeExecuteFunctions, additionalData, mode);
|
const triggerResponse = await this.runTrigger(node, nodeExecuteFunctions.getExecuteTriggerFunctions, additionalData, mode);
|
||||||
|
|
||||||
if (triggerResponse === undefined) {
|
if (triggerResponse === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Reference in a new issue