mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
🎨 Set up linting and formatting (#2120)
* ⬆️ Upgrade TS to 4.3.5 * 👕 Add ESLint configs * 🎨 Add Prettier config * 📦 Add deps and commands * ⚡ Adjust global .editorconfig to new ruleset * 🔥 Remove unneeded local .editorconfig * 📦 Update deps in editor-ui * 🔨 Limit Prettier to only TS files * ⚡ Add recommended VSCode extensions * 👕 Fix build * 🔥 Remove Vue setting from global config * ⚡ Disable prefer-default-export per feedback * ✏️ Add forgotten divider * 👕 Disable no-plusplus * 👕 Disable class-methods-use-this * ✏️ Alphabetize overrides * 👕 Add one-var consecutive override * ⏪ Revert one-var consecutive override This reverts commit b9252cf935659ba6d76727ad484a1d3c00008fcc. * 🎨 👕 Lint and format workflow package (#2121) * 🎨 Format /workflow package * 👕 Lint /workflow package * 🎨 Re-format /workflow package * 👕 Re-lint /workflow package * ✏️ Fix typo * ⚡ Consolidate if-checks * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 🎨 👕 Lint and format node-dev package (#2122) * 🎨 Format /node-dev package * ⚡ Exclude templates from ESLint config This keeps the templates consistent with the codebase while preventing lint exceptions from being made part of the templates. * 👕 Lint /node-dev package * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🎨 👕 Lint and format core package (#2123) * 🎨 Format /core package * 👕 Lint /core package * 🎨 Re-format /core package * 👕 Re-lint /core package * 🔥 Remove prefer-default-export exceptions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 🎨 👕 Lint and format cli package (#2124) * 🎨 Format /cli package * 👕 Exclude migrations from linting * 👕 Lint /cli package * 🎨 Re-format /cli package * 👕 Re-lint /cli package * 👕 Fix build * 🔥 Remove prefer-default-export exceptions * ⚡ Update exceptions in ActiveExecutions * 🔥 Remove no-plusplus exceptions * 🔥 Remove class-methods-use-this exceptions * 👕 fix lint issues * 🔧 use package specific linter, remove tslint command * 🔨 resolve build issue, sync dependencies * 🔧 change lint command Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-labels */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
import {
|
||||
@@ -20,23 +31,27 @@ import {
|
||||
WorkflowExecuteMode,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get } from 'lodash';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
export class WorkflowExecute {
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
private additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
private mode: WorkflowExecuteMode;
|
||||
|
||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
||||
constructor(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
runExecutionData?: IRunExecutionData,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.mode = mode;
|
||||
this.runExecutionData = runExecutionData || {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
@@ -48,8 +63,6 @@ export class WorkflowExecute {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow.
|
||||
*
|
||||
@@ -59,7 +72,8 @@ export class WorkflowExecute {
|
||||
* @returns {(Promise<string>)}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// Get the nodes to start workflow execution from
|
||||
startNode = startNode || workflow.getStartNode(destinationNode);
|
||||
|
||||
@@ -68,7 +82,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
|
||||
// If a destination node is given we only run the direct parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
if (destinationNode) {
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
@@ -108,8 +122,6 @@ export class WorkflowExecute {
|
||||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow but only
|
||||
*
|
||||
@@ -121,7 +133,13 @@ export class WorkflowExecute {
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
// @ts-ignore
|
||||
async runPartialWorkflow(workflow: Workflow, runData: IRunData, startNodes: string[], destinationNode: string): PCancelable<IRun> {
|
||||
async runPartialWorkflow(
|
||||
workflow: Workflow,
|
||||
runData: IRunData,
|
||||
startNodes: string[],
|
||||
destinationNode: string,
|
||||
// @ts-ignore
|
||||
): PCancelable<IRun> {
|
||||
let incomingNodeConnections: INodeConnections | undefined;
|
||||
let connection: IConnection;
|
||||
|
||||
@@ -149,7 +167,8 @@ export class WorkflowExecute {
|
||||
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
||||
connection = connections[inputIndex];
|
||||
incomingData.push(
|
||||
runData[connection.node!][runIndex].data![connection.type][connection.index]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index]!,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -182,11 +201,12 @@ export class WorkflowExecute {
|
||||
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
||||
}
|
||||
|
||||
|
||||
if (runData[connection.node!] !== undefined) {
|
||||
if (runData[connection.node] !== undefined) {
|
||||
// Input data exists so add as waiting
|
||||
// incomingDataDestination.push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index],
|
||||
);
|
||||
} else {
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
||||
}
|
||||
@@ -196,7 +216,8 @@ export class WorkflowExecute {
|
||||
}
|
||||
|
||||
// Only run the parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
// eslint-disable-next-line prefer-const
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
|
||||
@@ -218,8 +239,6 @@ export class WorkflowExecute {
|
||||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the hook with the given name
|
||||
*
|
||||
@@ -228,22 +247,31 @@ export class WorkflowExecute {
|
||||
* @returns {Promise<IRun>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> {
|
||||
// tslint:disable-line:no-any
|
||||
if (this.additionalData.hooks === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the incoming connection does not receive any data
|
||||
*/
|
||||
incomingConnectionIsEmpty(runData: IRunData, inputConnections: IConnection[], runIndex: number): boolean {
|
||||
incomingConnectionIsEmpty(
|
||||
runData: IRunData,
|
||||
inputConnections: IConnection[],
|
||||
runIndex: number,
|
||||
): boolean {
|
||||
// for (const inputConnection of workflow.connectionsByDestinationNode[nodeToAdd].main[0]) {
|
||||
for (const inputConnection of inputConnections) {
|
||||
const nodeIncomingData = get(runData, `[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`);
|
||||
const nodeIncomingData = get(
|
||||
runData,
|
||||
`[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`,
|
||||
);
|
||||
if (nodeIncomingData !== undefined && (nodeIncomingData as object[]).length !== 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -251,79 +279,117 @@ export class WorkflowExecute {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
addNodeToBeExecuted(workflow: Workflow, 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;
|
||||
|
||||
// Check if node has multiple inputs as then we have to wait for all input data
|
||||
// to be present before we can add it to the node-execution-stack
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node]['main'].length > 1) {
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node].main.length > 1) {
|
||||
// Node has multiple inputs
|
||||
let nodeWasWaiting = true;
|
||||
|
||||
// Check if there is already data for the node
|
||||
if (this.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
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
nodeWasWaiting = false;
|
||||
}
|
||||
if (this.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
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
main: [],
|
||||
};
|
||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
||||
for (
|
||||
let i = 0;
|
||||
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
i++
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
].main.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new data
|
||||
if (nodeSuccessData === null) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = null;
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
connectionData.index
|
||||
] = null;
|
||||
} else {
|
||||
this.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
|
||||
let thisExecutionData: INodeExecutionData[] | null;
|
||||
let allDataFound = true;
|
||||
for (let i = 0; i < this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
||||
thisExecutionData = this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
||||
for (
|
||||
let i = 0;
|
||||
i <
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main
|
||||
.length;
|
||||
i++
|
||||
) {
|
||||
thisExecutionData =
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
i
|
||||
];
|
||||
if (thisExecutionData === null) {
|
||||
allDataFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDataFound === true) {
|
||||
if (allDataFound) {
|
||||
// All data exists for node to be executed
|
||||
// So add it to the execution stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.nodes[connectionData.node],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
],
|
||||
});
|
||||
|
||||
// Remove the data from waiting
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
||||
|
||||
if (Object.keys(this.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
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
stillDataMissing = true;
|
||||
}
|
||||
stillDataMissing = true;
|
||||
|
||||
if (nodeWasWaiting === false) {
|
||||
|
||||
if (!nodeWasWaiting) {
|
||||
// Get a list of all the output nodes that we can check for siblings easier
|
||||
const checkOutputNodes = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName].main) {
|
||||
if (!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[outputIndexParent]) {
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[
|
||||
outputIndexParent
|
||||
]) {
|
||||
checkOutputNodes.push(connectionDataCheck.node);
|
||||
}
|
||||
}
|
||||
@@ -332,14 +398,22 @@ export class WorkflowExecute {
|
||||
// checked. So we have to go through all the inputs and check if they
|
||||
// are already on the list to be processed.
|
||||
// If that is not the case add it.
|
||||
for (let inputIndex = 0; inputIndex < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; inputIndex++) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node]['main'][inputIndex]) {
|
||||
for (
|
||||
let inputIndex = 0;
|
||||
inputIndex < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
inputIndex++
|
||||
) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node].main[
|
||||
inputIndex
|
||||
]) {
|
||||
if (inputData.node === parentNodeName) {
|
||||
// Is the node we come from so its data will be available for sure
|
||||
continue;
|
||||
}
|
||||
|
||||
const executionStackNodes = this.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
|
||||
// previously processed one
|
||||
@@ -348,7 +422,13 @@ export class WorkflowExecute {
|
||||
// will then process this node next. So nothing to do
|
||||
// unless the incoming data of the node is empty
|
||||
// because then it would not be executed
|
||||
if (!this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[inputData.node].main[0], runIndex)) {
|
||||
if (
|
||||
!this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[inputData.node].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +481,10 @@ export class WorkflowExecute {
|
||||
nodeToAdd = parentNode;
|
||||
}
|
||||
const parentNodesNodeToAdd = workflow.getParentNodes(nodeToAdd as string);
|
||||
if (parentNodesNodeToAdd.includes(parentNodeName) && nodeSuccessData[outputIndex].length === 0) {
|
||||
if (
|
||||
parentNodesNodeToAdd.includes(parentNodeName) &&
|
||||
nodeSuccessData[outputIndex].length === 0
|
||||
) {
|
||||
// We do not add the node if there is no input data and the node that should be connected
|
||||
// is a child of the parent node. Because else it would run a node even though it should be
|
||||
// specifically not run, as it did not receive any data.
|
||||
@@ -418,30 +501,32 @@ export class WorkflowExecute {
|
||||
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
||||
// Add empty item if the node does not have any input connections
|
||||
addEmptyItem = true;
|
||||
} else {
|
||||
if (this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[nodeToAdd].main[0], runIndex)) {
|
||||
// Add empty item also if the input data is empty
|
||||
addEmptyItem = true;
|
||||
}
|
||||
} else if (
|
||||
this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[nodeToAdd].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
// Add empty item also if the input data is empty
|
||||
addEmptyItem = true;
|
||||
}
|
||||
|
||||
if (addEmptyItem === true) {
|
||||
if (addEmptyItem) {
|
||||
// Add only node if it does not have any inputs because else it will
|
||||
// be added by its input node later anyway.
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(
|
||||
{
|
||||
node: workflow.getNode(nodeToAdd) as INode,
|
||||
data: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
],
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.getNode(nodeToAdd) as INode,
|
||||
data: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,9 +546,11 @@ export class WorkflowExecute {
|
||||
connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
|
||||
}
|
||||
|
||||
if (stillDataMissing === true) {
|
||||
if (stillDataMissing) {
|
||||
// Additional data is needed to run node so add it to waiting
|
||||
if (!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)) {
|
||||
if (
|
||||
!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
}
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
@@ -480,7 +567,6 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given execution data.
|
||||
*
|
||||
@@ -488,14 +574,17 @@ export class WorkflowExecute {
|
||||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
Logger.verbose('Workflow execution started', { workflowId: workflow.id });
|
||||
|
||||
const startedAt = new Date();
|
||||
|
||||
const workflowIssues = workflow.checkReadyForExecution();
|
||||
if (workflowIssues !== null) {
|
||||
throw new Error('The workflow has issues and can for that reason not be executed. Please fix them first.');
|
||||
throw new Error(
|
||||
'The workflow has issues and can for that reason not be executed. Please fix them first.',
|
||||
);
|
||||
}
|
||||
|
||||
// Variables which hold temporary data for each node-execution
|
||||
@@ -521,7 +610,7 @@ export class WorkflowExecute {
|
||||
let currentExecutionTry = '';
|
||||
let lastExecutionTry = '';
|
||||
|
||||
return new PCancelable((resolve, reject, onCancel) => {
|
||||
return new PCancelable(async (resolve, reject, onCancel) => {
|
||||
let gotCancel = false;
|
||||
|
||||
onCancel.shouldReject = false;
|
||||
@@ -533,7 +622,6 @@ export class WorkflowExecute {
|
||||
try {
|
||||
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||
} catch (error) {
|
||||
|
||||
// Set the error that it can be saved correctly
|
||||
executionError = {
|
||||
...error,
|
||||
@@ -542,16 +630,17 @@ export class WorkflowExecute {
|
||||
};
|
||||
|
||||
// Set the incoming data of the node that it can be saved correctly
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0] as IExecuteData;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0];
|
||||
this.runExecutionData.resultData = {
|
||||
runData: {
|
||||
[executionData.node.name]: [
|
||||
{
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
data: ({
|
||||
'main': executionData.data.main,
|
||||
} as ITaskDataConnections),
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
data: {
|
||||
main: executionData.data.main,
|
||||
} as ITaskDataConnections,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -562,24 +651,31 @@ export class WorkflowExecute {
|
||||
throw error;
|
||||
}
|
||||
|
||||
executionLoop:
|
||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||
|
||||
if (this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp) {
|
||||
executionLoop: while (
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.length !== 0
|
||||
) {
|
||||
if (
|
||||
this.additionalData.executionTimeoutTimestamp !== undefined &&
|
||||
Date.now() >= this.additionalData.executionTimeoutTimestamp
|
||||
) {
|
||||
gotCancel = true;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
nodeSuccessData = null;
|
||||
executionError = undefined;
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionData =
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionNode = executionData.node;
|
||||
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
|
||||
// Get the index of the current run
|
||||
@@ -594,7 +690,10 @@ export class WorkflowExecute {
|
||||
throw new Error('Did stop execution because execution seems to be in endless loop.');
|
||||
}
|
||||
|
||||
if (this.runExecutionData.startData!.runNodeFilter !== undefined && this.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
|
||||
// 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.
|
||||
@@ -608,17 +707,24 @@ export class WorkflowExecute {
|
||||
let inputConnections: IConnection[][];
|
||||
let connectionIndex: number;
|
||||
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name]['main'];
|
||||
// eslint-disable-next-line prefer-const
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name].main;
|
||||
|
||||
for (connectionIndex = 0; connectionIndex < inputConnections.length; connectionIndex++) {
|
||||
if (workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0) {
|
||||
for (
|
||||
connectionIndex = 0;
|
||||
connectionIndex < inputConnections.length;
|
||||
connectionIndex++
|
||||
) {
|
||||
if (
|
||||
workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0
|
||||
) {
|
||||
// If there is no valid incoming node (if all are disabled)
|
||||
// then ignore that it has inputs and simply execute it as it is without
|
||||
// any data
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!executionData.data!.hasOwnProperty('main')) {
|
||||
if (!executionData.data.hasOwnProperty('main')) {
|
||||
// ExecutionData does not even have the connection set up so can
|
||||
// not have that data, so add it again to be executed later
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
@@ -629,7 +735,10 @@ export class WorkflowExecute {
|
||||
// Check if it has the data for all the inputs
|
||||
// The most nodes just have one but merge node for example has two and data
|
||||
// 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
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
lastExecutionTry = currentExecutionTry;
|
||||
@@ -653,22 +762,25 @@ export class WorkflowExecute {
|
||||
let waitBetweenTries = 0;
|
||||
if (executionData.node.retryOnFail === true) {
|
||||
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
||||
waitBetweenTries = Math.min(5000, Math.max(0, executionData.node.waitBetweenTries || 1000));
|
||||
waitBetweenTries = Math.min(
|
||||
5000,
|
||||
Math.max(0, executionData.node.waitBetweenTries || 1000),
|
||||
);
|
||||
}
|
||||
|
||||
for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) {
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
try {
|
||||
|
||||
if (tryIndex !== 0) {
|
||||
// Reset executionError from previous error try
|
||||
executionError = undefined;
|
||||
if (waitBetweenTries !== 0) {
|
||||
// TODO: Improve that in the future and check if other nodes can
|
||||
// be executed in the meantime
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined);
|
||||
@@ -677,9 +789,23 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, { node: executionNode.name, workflowId: workflow.id });
|
||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
nodeSuccessData = await workflow.runNode(
|
||||
executionData.node,
|
||||
executionData.data,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.additionalData,
|
||||
NodeExecuteFunctions,
|
||||
this.mode,
|
||||
);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
|
||||
if (nodeSuccessData === undefined) {
|
||||
// Node did not get executed
|
||||
@@ -699,7 +825,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!!) {
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!) {
|
||||
// If null gets returned it means that the node did succeed
|
||||
// but did not have any data. So the branch should end
|
||||
// (meaning the nodes afterwards should not be processed)
|
||||
@@ -708,7 +834,6 @@ export class WorkflowExecute {
|
||||
|
||||
break;
|
||||
} catch (error) {
|
||||
|
||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
|
||||
executionError = {
|
||||
@@ -717,7 +842,10 @@ export class WorkflowExecute {
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,7 +857,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
taskData = {
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
};
|
||||
|
||||
if (executionError !== undefined) {
|
||||
@@ -741,7 +869,7 @@ export class WorkflowExecute {
|
||||
// Simply get the input data of the node if it has any and pass it through
|
||||
// to the next node
|
||||
if (executionData.data.main[0] !== null) {
|
||||
nodeSuccessData = [executionData.data.main[0] as INodeExecutionData[]];
|
||||
nodeSuccessData = [executionData.data.main[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -751,30 +879,46 @@ export class WorkflowExecute {
|
||||
// Add the execution data again so that it can get restarted
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Node executed successfully. So add data and go on.
|
||||
taskData.data = ({
|
||||
'main': nodeSuccessData,
|
||||
} as ITaskDataConnections);
|
||||
taskData.data = {
|
||||
main: nodeSuccessData,
|
||||
} as ITaskDataConnections;
|
||||
|
||||
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||
|
||||
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode && this.runExecutionData.startData.destinationNode === executionNode.name) {
|
||||
if (
|
||||
this.runExecutionData.startData &&
|
||||
this.runExecutionData.startData.destinationNode &&
|
||||
this.runExecutionData.startData.destinationNode === executionNode.name
|
||||
) {
|
||||
// Before stopping, make sure we are executing hooks so
|
||||
// That frontend is notified for example for manual executions.
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// If destination node is defined and got executed stop execution
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.runExecutionData.waitTill!!) {
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
if (this.runExecutionData.waitTill!) {
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// Add the node back to the stack that the workflow can start to execute again from that node
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
@@ -786,24 +930,46 @@ export class WorkflowExecute {
|
||||
// be executed next
|
||||
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
||||
if (workflow.connectionsBySourceNode[executionNode.name].hasOwnProperty('main')) {
|
||||
let outputIndex: string, connectionData: IConnection;
|
||||
let outputIndex: string;
|
||||
let connectionData: IConnection;
|
||||
// Iterate over all the outputs
|
||||
|
||||
// Add the nodes to be executed
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name]['main']) {
|
||||
if (!workflow.connectionsBySourceNode[executionNode.name]['main'].hasOwnProperty(outputIndex)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name].main) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[executionNode.name].main.hasOwnProperty(
|
||||
outputIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over all the different connections of this output
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name]['main'][outputIndex]) {
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name].main[
|
||||
outputIndex
|
||||
]) {
|
||||
if (!workflow.nodes.hasOwnProperty(connectionData.node)) {
|
||||
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}"`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (nodeSuccessData![outputIndex] && (nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)) {
|
||||
if (
|
||||
nodeSuccessData![outputIndex] &&
|
||||
(nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)
|
||||
) {
|
||||
// Add the node only if it did execute or if connected to second "optional" input
|
||||
this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
||||
this.addNodeToBeExecuted(
|
||||
workflow,
|
||||
connectionData,
|
||||
parseInt(outputIndex, 10),
|
||||
executionNode.name,
|
||||
nodeSuccessData!,
|
||||
runIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -814,58 +980,79 @@ export class WorkflowExecute {
|
||||
// Execute hooks now to make sure that all hooks are executed properly
|
||||
// Await is needed to make sure that we don't fall into concurrency problems
|
||||
// When saving node execution data
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})()
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(startedAt, workflow, new WorkflowOperationError('Workflow has been canceled or timed out!'));
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(
|
||||
startedAt,
|
||||
workflow,
|
||||
new WorkflowOperationError('Workflow has been canceled or timed out!'),
|
||||
);
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
.catch(async (error) => {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
fullRunData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
fullRunData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
// 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]).catch(error => {
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
}
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
(error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
},
|
||||
);
|
||||
|
||||
return fullRunData;
|
||||
});
|
||||
|
||||
return fullRunData;
|
||||
});
|
||||
|
||||
return returnPromise.then(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
|
||||
async processSuccessExecution(
|
||||
startedAt: Date,
|
||||
workflow: Workflow,
|
||||
executionError?: ExecutionError,
|
||||
// @ts-ignore
|
||||
): PCancelable<IRun> {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
if (executionError !== undefined) {
|
||||
Logger.verbose(`Workflow execution finished with error`, { error: executionError, workflowId: workflow.id });
|
||||
Logger.verbose(`Workflow execution finished with error`, {
|
||||
error: executionError,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.data.resultData.error = {
|
||||
...executionError,
|
||||
message: executionError.message,
|
||||
stack: executionError.stack,
|
||||
} as ExecutionError;
|
||||
} else if (this.runExecutionData.waitTill!!) {
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, { workflowId: workflow.id });
|
||||
} else if (this.runExecutionData.waitTill!) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, {
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.waitTill = this.runExecutionData.waitTill;
|
||||
} else {
|
||||
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
||||
@@ -874,6 +1061,7 @@ export class WorkflowExecute {
|
||||
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
@@ -894,5 +1082,4 @@ export class WorkflowExecute {
|
||||
|
||||
return fullRunData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user