diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 30f76cf72a..1b203c7c4e 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -338,12 +338,6 @@ export class Start extends BaseCommand { const editorUrl = GenericHelpers.getBaseUrl(); this.log(`\nEditor is now accessible via:\n${editorUrl}`); - const saveManualExecutions = config.getEnv('executions.saveDataManualExecutions'); - - if (saveManualExecutions) { - this.log('\nManual executions will be visible only for the owner'); - } - // Allow to open n8n editor by pressing "o" if (Boolean(process.stdout.isTTY) && process.stdin.setRawMode) { process.stdin.setRawMode(true); diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index 597680a3e7..6fe4838115 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -143,24 +143,8 @@ export class WorkflowExecute { return this.processRunExecutionData(workflow); } - forceInputNodeExecution(workflow: Workflow, node: INode): boolean { - const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); - - // Check if the incoming nodes should be forced to execute - let forceInputNodeExecution = nodeType.description.forceInputNodeExecution; - if (forceInputNodeExecution !== undefined) { - if (typeof forceInputNodeExecution === 'string') { - forceInputNodeExecution = !!workflow.expression.getSimpleParameterValue( - node, - forceInputNodeExecution, - this.mode, - this.additionalData.timezone, - { $version: node.typeVersion }, - ); - } - return forceInputNodeExecution; - } - return false; + forceInputNodeExecution(workflow: Workflow): boolean { + return workflow.settings.executionOrder !== 'v1'; } /** @@ -379,6 +363,7 @@ export class WorkflowExecute { runIndex: number, ): void { let stillDataMissing = false; + const enqueueFn = workflow.settings.executionOrder === 'v1' ? 'unshift' : 'push'; let waitingNodeIndex: number | undefined; // Check if node has multiple inputs as then we have to wait for all input data @@ -510,7 +495,7 @@ export class WorkflowExecute { ]; } - this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionStackItem); + this.runExecutionData.executionData!.nodeExecutionStack[enqueueFn](executionStackItem); // Remove the data from waiting delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][ @@ -554,8 +539,7 @@ export class WorkflowExecute { // are already on the list to be processed. // If that is not the case add it. - const node = workflow.getNode(connectionData.node); - const forceInputNodeExecution = this.forceInputNodeExecution(workflow, node!); + const forceInputNodeExecution = this.forceInputNodeExecution(workflow); for ( let inputIndex = 0; @@ -680,7 +664,7 @@ export class WorkflowExecute { 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.unshift({ + this.runExecutionData.executionData!.nodeExecutionStack[enqueueFn]({ node: workflow.getNode(nodeToAdd) as INode, data: { main: [ @@ -744,7 +728,7 @@ export class WorkflowExecute { }; } else { // All data is there so add it directly to stack - this.runExecutionData.executionData!.nodeExecutionStack.unshift({ + this.runExecutionData.executionData!.nodeExecutionStack[enqueueFn]({ node: workflow.nodes[connectionData.node], data: { main: connectionDataArray, @@ -774,6 +758,7 @@ export class WorkflowExecute { Logger.verbose('Workflow execution started', { workflowId: workflow.id }); const startedAt = new Date(); + const forceInputNodeExecution = this.forceInputNodeExecution(workflow); this.status = 'running'; @@ -937,8 +922,6 @@ export class WorkflowExecute { continue; } - const node = workflow.getNode(executionNode.name); - // Check if all the data which is needed to run the node is available if (workflow.connectionsByDestinationNode.hasOwnProperty(executionNode.name)) { // Check if the node has incoming connections @@ -971,7 +954,7 @@ export class WorkflowExecute { continue executionLoop; } - if (this.forceInputNodeExecution(workflow, node!)) { + if (forceInputNodeExecution) { // 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. @@ -1295,53 +1278,60 @@ export class WorkflowExecute { ); } - const connectionDestinationNode = workflow.getNode(connectionData.node); - const forceInputNodeExecution = this.forceInputNodeExecution( - workflow, - connectionDestinationNode!, - ); - if ( nodeSuccessData![outputIndex] && (nodeSuccessData![outputIndex].length !== 0 || (connectionData.index > 0 && forceInputNodeExecution)) ) { // Add the node only if it did execute or if connected to second "optional" input - const nodeToAdd = workflow.getNode(connectionData.node); - nodesToAdd.push({ - position: nodeToAdd?.position || [0, 0], - connection: connectionData, - outputIndex: parseInt(outputIndex, 10), - }); + if (workflow.settings.executionOrder === 'v1') { + const nodeToAdd = workflow.getNode(connectionData.node); + nodesToAdd.push({ + position: nodeToAdd?.position || [0, 0], + connection: connectionData, + outputIndex: parseInt(outputIndex, 10), + }); + } else { + this.addNodeToBeExecuted( + workflow, + connectionData, + parseInt(outputIndex, 10), + executionNode.name, + nodeSuccessData!, + runIndex, + ); + } } } } - // Always execute the node that is more to the top-left first - nodesToAdd.sort((a, b) => { - if (a.position[1] < b.position[1]) { - return 1; - } - if (a.position[1] > b.position[1]) { - return -1; - } + if (workflow.settings.executionOrder === 'v1') { + // Always execute the node that is more to the top-left first + nodesToAdd.sort((a, b) => { + if (a.position[1] < b.position[1]) { + return 1; + } + if (a.position[1] > b.position[1]) { + return -1; + } - if (a.position[0] > b.position[0]) { - return -1; + if (a.position[0] > b.position[0]) { + return -1; + } + + return 0; + }); + + for (const nodeData of nodesToAdd) { + this.addNodeToBeExecuted( + workflow, + nodeData.connection, + nodeData.outputIndex, + executionNode.name, + nodeSuccessData!, + runIndex, + ); } - - return 0; - }); - - for (const nodeData of nodesToAdd) { - this.addNodeToBeExecuted( - workflow, - nodeData.connection, - nodeData.outputIndex, - executionNode.name, - nodeSuccessData!, - runIndex, - ); } } } @@ -1382,7 +1372,10 @@ export class WorkflowExecute { ); // Check if the node is only allowed execute if all inputs received data - let requiredInputs = nodeType.description.requiredInputs; + let requiredInputs = + workflow.settings.executionOrder === 'v1' + ? nodeType.description.requiredInputs + : undefined; if (requiredInputs !== undefined) { if (typeof requiredInputs === 'string') { requiredInputs = workflow.expression.getSimpleParameterValue( diff --git a/packages/core/test/WorkflowExecute.test.ts b/packages/core/test/WorkflowExecute.test.ts index fca310f6c0..668e8e77dc 100644 --- a/packages/core/test/WorkflowExecute.test.ts +++ b/packages/core/test/WorkflowExecute.test.ts @@ -4,15 +4,15 @@ import { WorkflowExecute } from '@/WorkflowExecute'; import * as Helpers from './helpers'; import { initLogger } from './helpers/utils'; -import { predefinedWorkflowExecuteTests } from './helpers/constants'; +import { legacyWorkflowExecuteTests, v1WorkflowExecuteTests } from './helpers/constants'; describe('WorkflowExecute', () => { beforeAll(() => { initLogger(); }); - describe('run', () => { - const tests: WorkflowTestData[] = predefinedWorkflowExecuteTests; + describe('v0 execution order', () => { + const tests: WorkflowTestData[] = legacyWorkflowExecuteTests; const executionMode = 'manual'; const nodeTypes = Helpers.NodeTypes(); @@ -25,6 +25,9 @@ describe('WorkflowExecute', () => { connections: testData.input.workflowData.connections, active: false, nodeTypes, + settings: { + executionOrder: 'v0', + }, }); const waitPromise = await createDeferredPromise(); @@ -71,6 +74,70 @@ describe('WorkflowExecute', () => { } }); + describe('v1 execution order', () => { + const tests: WorkflowTestData[] = v1WorkflowExecuteTests; + + const executionMode = 'manual'; + const nodeTypes = Helpers.NodeTypes(); + + for (const testData of tests) { + test(testData.description, async () => { + const workflowInstance = new Workflow({ + id: 'test', + nodes: testData.input.workflowData.nodes, + connections: testData.input.workflowData.connections, + active: false, + nodeTypes, + settings: { + executionOrder: 'v1', + }, + }); + + const waitPromise = await createDeferredPromise(); + const nodeExecutionOrder: string[] = []; + const additionalData = Helpers.WorkflowExecuteAdditionalData( + waitPromise, + nodeExecutionOrder, + ); + + const workflowExecute = new WorkflowExecute(additionalData, executionMode); + + const executionData = await workflowExecute.run(workflowInstance); + + const result = await waitPromise.promise(); + + // Check if the data from WorkflowExecute is identical to data received + // by the webhooks + expect(executionData).toEqual(result); + + // Check if the output data of the nodes is correct + for (const nodeName of Object.keys(testData.output.nodeData)) { + if (result.data.resultData.runData[nodeName] === undefined) { + throw new Error(`Data for node "${nodeName}" is missing!`); + } + + const resultData = result.data.resultData.runData[nodeName].map((nodeData) => { + if (nodeData.data === undefined) { + return null; + } + return nodeData.data.main[0]!.map((entry) => entry.json); + }); + + // expect(resultData).toEqual(testData.output.nodeData[nodeName]); + expect(resultData).toEqual(testData.output.nodeData[nodeName]); + } + + // Check if the nodes did execute in the correct order + expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder); + + // Check if other data has correct value + expect(result.finished).toEqual(true); + expect(result.data.executionData!.contextData).toEqual({}); + expect(result.data.executionData!.nodeExecutionStack).toEqual([]); + }); + } + }); + //run tests on json files from specified directory, default 'workflows' //workflows must have pinned data that would be used to test output after execution describe('run test workflows', () => { @@ -87,6 +154,7 @@ describe('WorkflowExecute', () => { connections: testData.input.workflowData.connections, active: false, nodeTypes, + settings: testData.input.workflowData.settings, }); const waitPromise = await createDeferredPromise(); diff --git a/packages/core/test/helpers/constants.ts b/packages/core/test/helpers/constants.ts index 4d74e7108a..92dcd50cb4 100644 --- a/packages/core/test/helpers/constants.ts +++ b/packages/core/test/helpers/constants.ts @@ -354,7 +354,6 @@ export const predefinedNodesTypes: INodeTypeData = { icon: 'fa:clone', group: ['transform'], version: [1, 2], - forceInputNodeExecution: '={{ $version === 1 }}', requiredInputs: '={{ $version === 2 ? 1 : undefined }}', description: 'Merges data of multiple streams once data of both is available', defaults: { @@ -745,10 +744,10 @@ export const predefinedNodesTypes: INodeTypeData = { }, }; -export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ +export const legacyWorkflowExecuteTests: WorkflowTestData[] = [ { description: - 'should run complicated multi node workflow where multiple Merge-Node have missing data and complex dependency structure (Old Merge-Node behavior - forceInputNodeExecution=true)', + 'should run complicated multi node workflow where multiple Merge-Node have missing data and complex dependency structure', input: { workflowData: { nodes: [ @@ -800,7 +799,7 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ name: 'IF3', type: 'n8n-nodes-base.if', typeVersion: 1, - position: [620, 400], + position: [620, 1100], }, { parameters: {}, @@ -1100,11 +1099,11 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ nodeExecutionOrder: [ 'Start', 'Set1', - 'IF3', - 'IF4', 'IF1', 'IF2', + 'IF3', 'Merge2', + 'IF4', 'Merge1', 'Merge4', 'Merge3', @@ -1154,588 +1153,6 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ }, }, }, - { - description: - 'should run keep on executing even if data from input 1 is missing (New Merge-Node behavior - No force execution)', - input: { - workflowData: { - nodes: [ - { - parameters: {}, - id: '9c0cb647-5d60-40dc-b791-4946ee260a5d', - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [180, 240], - }, - { - parameters: { - values: { - string: [ - { - name: 'test', - value: 'a', - }, - ], - }, - options: {}, - }, - id: '2bed3b26-0907-465b-a416-9dc993c2e302', - name: 'Set', - type: 'n8n-nodes-base.set', - typeVersion: 1, - position: [400, 240], - }, - { - parameters: { - conditions: { - string: [ - { - value1: '={{ $json["test"] }}', - value2: 'b', - }, - ], - }, - }, - id: 'eca22a12-fb0c-4a4f-ab97-74544c178714', - name: 'IF', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [620, 240], - }, - { - parameters: {}, - id: '8d63caea-8d89-450e-87ae-6097b9821a70', - name: 'NoOp', - type: 'n8n-nodes-base.noOp', - typeVersion: 1, - position: [860, 160], - }, - { - parameters: {}, - id: 'bd0e79e4-7b7a-4016-ace3-6f54f46b41c3', - name: 'NoOp1', - type: 'n8n-nodes-base.noOp', - typeVersion: 1, - position: [860, 300], - }, - { - parameters: {}, - id: '975966f6-8e59-41d8-a69e-7223476a7c50', - name: 'Merge', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [1140, 220], - }, - ], - connections: { - Start: { - main: [ - [ - { - node: 'Set', - type: 'main', - index: 0, - }, - ], - ], - }, - Set: { - main: [ - [ - { - node: 'IF', - type: 'main', - index: 0, - }, - ], - ], - }, - IF: { - main: [ - [ - { - node: 'NoOp', - type: 'main', - index: 0, - }, - ], - [ - { - node: 'NoOp1', - type: 'main', - index: 0, - }, - ], - ], - }, - NoOp: { - main: [ - [ - { - node: 'Merge', - type: 'main', - index: 0, - }, - ], - ], - }, - NoOp1: { - main: [ - [ - { - node: 'Merge', - type: 'main', - index: 1, - }, - ], - ], - }, - }, - }, - }, - output: { - nodeExecutionOrder: ['Start', 'Set', 'IF', 'NoOp1', 'Merge'], - nodeData: { - Merge: [ - [ - { - test: 'a', - }, - ], - ], - }, - }, - }, - { - description: - 'should run complicated multi node workflow where multiple Merge-Node have missing data and complex dependency structure (New Merge-Node behavior - forceInputNodeExecution=false)', - input: { - workflowData: { - nodes: [ - { - parameters: { - conditions: { - string: [ - { - value1: '={{ $json["test"] }}', - value2: 'b', - }, - ], - }, - }, - id: '21593a8c-07c1-435b-93a6-75317ee3bf67', - name: 'IF4', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [880, 1240], - }, - { - parameters: {}, - id: 'a9af6b9f-011c-4b34-a367-0cfa5ad4c865', - name: 'NoOp2', - type: 'n8n-nodes-base.noOp', - typeVersion: 1, - position: [1320, 1060], - }, - { - parameters: {}, - id: '429d1a51-65f0-4701-af76-b73611774952', - name: 'Merge3', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [1100, 1060], - }, - { - parameters: { - conditions: { - string: [ - { - value1: '={{ $json["test"] }}', - value2: 'b', - }, - ], - }, - }, - id: 'ed08db0f-f747-4f87-af62-051fc53f955c', - name: 'IF3', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [620, 1060], - }, - { - parameters: {}, - id: 'e80d2aac-cbd4-4e7c-9817-83db52a617d4', - name: 'Merge2', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [940, 900], - }, - { - parameters: { - conditions: { - string: [ - { - value1: '={{ $json["test"] }}', - value2: 'a', - }, - ], - }, - }, - id: '766dad6b-4326-41b5-a02a-0b3b7d879eb4', - name: 'IF2', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [620, 900], - }, - { - parameters: {}, - id: '0c0cd5bb-eb44-48fe-b66a-54a3c541ea57', - name: 'Merge7', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [2180, 1180], - }, - { - parameters: {}, - id: '863a00e5-7be4-43f3-97da-07cf552d7c0e', - name: 'Merge6', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [1840, 1200], - }, - { - parameters: {}, - id: '8855d0ca-1deb-4ad8-958b-2379d3a87160', - name: 'Merge5', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [1600, 1040], - }, - { - parameters: {}, - id: 'ea37e388-c77a-4a2f-a527-4585f24371d5', - name: 'Merge4', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [1180, 880], - }, - { - parameters: {}, - id: 'e3c814e9-9a92-4e12-96d5-85634fe76dc9', - name: 'Merge1', - type: 'n8n-nodes-base.merge', - typeVersion: 2, - position: [940, 720], - }, - { - parameters: { - conditions: { - string: [ - { - value1: '={{ $json["test"] }}', - value2: 'b', - }, - ], - }, - }, - id: 'a21a3932-8a3f-464f-8393-309d3233433a', - name: 'IF1', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [620, 720], - }, - { - parameters: { - values: { - string: [ - { - name: 'test', - value: 'a', - }, - ], - }, - options: {}, - }, - id: '12d33a38-baeb-41de-aea0-d8a7477f5aa6', - name: 'Set1', - type: 'n8n-nodes-base.set', - typeVersion: 1, - position: [400, 720], - }, - { - parameters: {}, - id: '41589b0b-0521-41ae-b0c6-80a016af803e', - name: 'Start', - type: 'n8n-nodes-base.start', - typeVersion: 1, - position: [160, 240], - }, - ], - connections: { - IF4: { - main: [ - [ - { - node: 'Merge3', - type: 'main', - index: 1, - }, - ], - [ - { - node: 'Merge6', - type: 'main', - index: 1, - }, - ], - ], - }, - NoOp2: { - main: [ - [ - { - node: 'Merge5', - type: 'main', - index: 1, - }, - ], - ], - }, - Merge3: { - main: [ - [ - { - node: 'NoOp2', - type: 'main', - index: 0, - }, - ], - ], - }, - IF3: { - main: [ - [ - { - node: 'Merge3', - type: 'main', - index: 0, - }, - ], - [ - { - node: 'IF4', - type: 'main', - index: 0, - }, - ], - ], - }, - Merge2: { - main: [ - [ - { - node: 'Merge4', - type: 'main', - index: 1, - }, - ], - ], - }, - IF2: { - main: [ - [ - { - node: 'Merge2', - type: 'main', - index: 0, - }, - ], - [ - { - node: 'Merge2', - type: 'main', - index: 1, - }, - ], - ], - }, - Merge6: { - main: [ - [ - { - node: 'Merge7', - type: 'main', - index: 1, - }, - ], - ], - }, - Merge5: { - main: [ - [ - { - node: 'Merge6', - type: 'main', - index: 0, - }, - ], - ], - }, - Merge4: { - main: [ - [ - { - node: 'Merge5', - type: 'main', - index: 0, - }, - ], - ], - }, - Merge1: { - main: [ - [ - { - node: 'Merge4', - type: 'main', - index: 0, - }, - ], - ], - }, - IF1: { - main: [ - [ - { - node: 'Merge1', - type: 'main', - index: 0, - }, - ], - [ - { - node: 'Merge1', - type: 'main', - index: 1, - }, - ], - ], - }, - Set1: { - main: [ - [ - { - node: 'IF1', - type: 'main', - index: 0, - }, - { - node: 'IF2', - type: 'main', - index: 0, - }, - { - node: 'IF3', - type: 'main', - index: 0, - }, - ], - ], - }, - Start: { - main: [ - [ - { - node: 'Set1', - type: 'main', - index: 0, - }, - ], - ], - }, - }, - }, - }, - output: { - nodeExecutionOrder: [ - 'Start', - 'Set1', - 'IF1', - 'IF2', - 'IF3', - 'IF4', - 'Merge1', - 'Merge2', - 'Merge4', - 'Merge5', - 'Merge6', - 'Merge7', - ], - nodeData: { - Merge1: [ - [ - { - test: 'a', - }, - ], - ], - Merge2: [ - [ - { - test: 'a', - }, - ], - ], - Merge4: [ - [ - { - test: 'a', - }, - { - test: 'a', - }, - ], - ], - Merge5: [ - [ - { - test: 'a', - }, - { - test: 'a', - }, - ], - ], - Merge6: [ - [ - { - test: 'a', - }, - { - test: 'a', - }, - { - test: 'a', - }, - ], - ], - Merge7: [ - [ - { - test: 'a', - }, - { - test: 'a', - }, - { - test: 'a', - }, - ], - ], - }, - }, - }, { description: 'should simply execute the next multi-input-node (totally ignoring the runIndex)', input: { @@ -1956,18 +1373,18 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ nodeExecutionOrder: [ 'Start', 'Set1', - 'IF1', - 'Set1', - 'IF1', - 'Set1', - 'IF1', 'Set', + 'IF1', 'IF', - 'Merge1', + 'Set1', 'Set', + 'Merge1', + 'IF1', 'IF', - 'Merge1', + 'Set1', 'Set', + 'Merge1', + 'IF1', 'IF', 'Merge', 'Merge2', @@ -2148,7 +1565,7 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ }, }, { - description: 'should run node twice when it has two input connections', + description: 'should run node twice when it has two input connections3', input: { // Leave the workflowData in regular JSON to be able to easily // copy it from/in the UI @@ -2241,12 +1658,12 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ Set2: [ [ { - value1: 1, value2: 2, }, ], [ { + value1: 1, value2: 2, }, ], @@ -2501,10 +1918,10 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ nodeExecutionOrder: [ 'Start', 'Set1', - 'Set3', - 'Set4', 'Set2', + 'Set3', 'Merge1', + 'Set4', 'Merge2', 'Merge3', 'Merge4', @@ -3406,7 +2823,7 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ }, }, { - description: 'should execute nodes in the correct order, the most top-left one first', + description: 'should execute nodes in the correct order, breath-first & order of connection', input: { workflowData: { nodes: [ @@ -3594,7 +3011,975 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ id: 'cf72f99c-612f-4b76-bc8e-d77612e4faa9', name: 'Merge', type: 'n8n-nodes-base.merge', - typeVersion: 2.1, + typeVersion: 2, + position: [1300, 220], + }, + { + parameters: { + options: {}, + }, + id: 'bfe1dfca-a060-4c37-94d0-058739e7cfca', + name: 'Wait14', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1520, 220], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + conditions: { + number: [ + { + value1: '={{ $itemIndex }}', + operation: 'equal', + value2: 1, + }, + ], + }, + }, + id: 'bf7d7e54-db5f-4f20-bf3e-b07224096872', + name: 'IF', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [1780, -220], + }, + { + parameters: { + options: {}, + }, + id: 'd340f2ad-3a6a-4412-bd15-9a7dde1fcb8c', + name: 'Wait15', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [2020, -300], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: '913a3c9c-1704-433d-9790-21ad0922e5e1', + name: 'Wait16', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [2020, -140], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + conditions: { + number: [ + { + value1: '={{ $itemIndex }}', + operation: 'equal', + value2: 1, + }, + ], + }, + }, + id: 'df1fba53-92af-4351-b471-114dda12bef9', + name: 'IF1', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [1780, 120], + }, + { + parameters: { + options: {}, + }, + id: '8b3c7e63-8cd8-469d-b6d4-bf5c1953af11', + name: 'Wait17', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [2020, 200], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'e74c4b7c-fc76-4e48-9a0e-3195b19ce1a0', + name: 'Wait18', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [2020, 40], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'Set', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait: { + main: [ + [ + { + node: 'Wait2', + type: 'main', + index: 0, + }, + ], + ], + }, + Set: { + main: [ + [ + { + node: 'Wait', + type: 'main', + index: 0, + }, + { + node: 'Wait1', + type: 'main', + index: 0, + }, + { + node: 'Wait6', + type: 'main', + index: 0, + }, + { + node: 'Wait7', + type: 'main', + index: 0, + }, + { + node: 'Wait8', + type: 'main', + index: 0, + }, + { + node: 'Wait9', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait1: { + main: [ + [ + { + node: 'Wait4', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait2: { + main: [ + [ + { + node: 'Wait3', + type: 'main', + index: 0, + }, + { + node: 'Merge', + type: 'main', + index: 1, + }, + ], + ], + }, + Wait3: { + main: [ + [ + { + node: 'Wait10', + type: 'main', + index: 0, + }, + { + node: 'Wait11', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait4: { + main: [ + [ + { + node: 'Wait5', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait7: { + main: [ + [ + { + node: 'Merge', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait9: { + main: [ + [ + { + node: 'Wait13', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait10: { + main: [ + [ + { + node: 'Wait12', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge: { + main: [ + [ + { + node: 'Wait14', + type: 'main', + index: 0, + }, + ], + ], + }, + Wait14: { + main: [ + [ + { + node: 'IF', + type: 'main', + index: 0, + }, + { + node: 'IF1', + type: 'main', + index: 0, + }, + ], + ], + }, + IF: { + main: [ + [ + { + node: 'Wait15', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'Wait16', + type: 'main', + index: 0, + }, + ], + ], + }, + IF1: { + main: [ + [ + { + node: 'Wait17', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'Wait18', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: [ + 'Start', + 'Set', + 'Wait', + 'Wait1', + 'Wait6', + 'Wait7', + 'Wait8', + 'Wait9', + 'Wait2', + 'Wait4', + 'Wait13', + 'Wait3', + 'Merge', + 'Wait5', + 'Wait10', + 'Wait11', + 'Wait14', + 'Wait12', + 'IF', + 'IF1', + 'Wait15', + 'Wait16', + 'Wait17', + 'Wait18', + ], + nodeData: {}, + }, + }, +]; + +export const v1WorkflowExecuteTests: WorkflowTestData[] = [ + { + description: 'should run node twice when it has two input connections', + input: { + // Leave the workflowData in regular JSON to be able to easily + // copy it from/in the UI + workflowData: { + nodes: [ + { + id: 'uuid-1', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 300], + }, + { + id: 'uuid-2', + parameters: { + values: { + number: [ + { + name: 'value1', + value: 1, + }, + ], + }, + }, + name: 'Set1', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [300, 250], + }, + { + id: 'uuid-3', + parameters: { + values: { + number: [ + { + name: 'value2', + value: 2, + }, + ], + }, + }, + name: 'Set2', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [500, 400], + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'Set1', + type: 'main', + index: 0, + }, + { + node: 'Set2', + type: 'main', + index: 0, + }, + ], + ], + }, + Set1: { + main: [ + [ + { + node: 'Set2', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: ['Start', 'Set1', 'Set2', 'Set2'], + nodeData: { + Set1: [ + [ + { + value1: 1, + }, + ], + ], + Set2: [ + [ + { + value1: 1, + value2: 2, + }, + ], + [ + { + value2: 2, + }, + ], + ], + }, + }, + }, + { + description: 'should run complicated multi node workflow', + input: { + // Leave the workflowData in regular JSON to be able to easily + // copy it from/in the UI + workflowData: { + nodes: [ + { + id: 'uuid-1', + parameters: { + mode: 'passThrough', + }, + name: 'Merge4', + type: 'n8n-nodes-base.merge', + typeVersion: 1, + position: [1150, 500], + }, + { + id: 'uuid-2', + parameters: { + values: { + number: [ + { + name: 'value2', + value: 2, + }, + ], + }, + }, + name: 'Set2', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [290, 400], + }, + { + id: 'uuid-3', + parameters: { + values: { + number: [ + { + name: 'value4', + value: 4, + }, + ], + }, + }, + name: 'Set4', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [850, 200], + }, + { + id: 'uuid-4', + parameters: { + values: { + number: [ + { + name: 'value3', + value: 3, + }, + ], + }, + }, + name: 'Set3', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [650, 200], + }, + { + id: 'uuid-5', + parameters: { + mode: 'passThrough', + }, + name: 'Merge4', + type: 'n8n-nodes-base.merge', + typeVersion: 1, + position: [1150, 500], + }, + { + id: 'uuid-6', + parameters: {}, + name: 'Merge3', + type: 'n8n-nodes-base.merge', + typeVersion: 1, + position: [1000, 400], + }, + { + id: 'uuid-7', + parameters: { + mode: 'passThrough', + output: 'input2', + }, + name: 'Merge2', + type: 'n8n-nodes-base.merge', + typeVersion: 1, + position: [700, 400], + }, + { + id: 'uuid-8', + parameters: {}, + name: 'Merge1', + type: 'n8n-nodes-base.merge', + typeVersion: 1, + position: [500, 300], + }, + { + id: 'uuid-9', + parameters: { + values: { + number: [ + { + name: 'value1', + value: 1, + }, + ], + }, + }, + name: 'Set1', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [300, 200], + }, + { + id: 'uuid-10', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 300], + }, + ], + connections: { + Set2: { + main: [ + [ + { + node: 'Merge1', + type: 'main', + index: 1, + }, + { + node: 'Merge2', + type: 'main', + index: 1, + }, + ], + ], + }, + Set4: { + main: [ + [ + { + node: 'Merge3', + type: 'main', + index: 0, + }, + ], + ], + }, + Set3: { + main: [ + [ + { + node: 'Set4', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge3: { + main: [ + [ + { + node: 'Merge4', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge2: { + main: [ + [ + { + node: 'Merge3', + type: 'main', + index: 1, + }, + ], + ], + }, + Merge1: { + main: [ + [ + { + node: 'Merge2', + type: 'main', + index: 0, + }, + ], + ], + }, + Set1: { + main: [ + [ + { + node: 'Merge1', + type: 'main', + index: 0, + }, + { + node: 'Set3', + type: 'main', + index: 0, + }, + ], + ], + }, + Start: { + main: [ + [ + { + node: 'Set1', + type: 'main', + index: 0, + }, + { + node: 'Set2', + type: 'main', + index: 0, + }, + { + node: 'Merge4', + type: 'main', + index: 1, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: [ + 'Start', + 'Set1', + 'Set3', + 'Set4', + 'Set2', + 'Merge1', + 'Merge2', + 'Merge3', + 'Merge4', + ], + nodeData: { + Set1: [ + [ + { + value1: 1, + }, + ], + ], + Set2: [ + [ + { + value2: 2, + }, + ], + ], + Set3: [ + [ + { + value1: 1, + value3: 3, + }, + ], + ], + Set4: [ + [ + { + value1: 1, + value3: 3, + value4: 4, + }, + ], + ], + Merge1: [ + [ + { + value1: 1, + }, + { + value2: 2, + }, + ], + ], + Merge2: [ + [ + { + value2: 2, + }, + ], + ], + Merge3: [ + [ + { + value1: 1, + value3: 3, + value4: 4, + }, + { + value2: 2, + }, + ], + ], + Merge4: [ + [ + { + value1: 1, + value3: 3, + value4: 4, + }, + { + value2: 2, + }, + ], + ], + }, + }, + }, + { + description: + 'should execute nodes in the correct order, depth-first & the most top-left one first', + input: { + workflowData: { + nodes: [ + { + parameters: {}, + id: '3e4ab8bb-2e22-45d9-9287-0265f2ee9c4b', + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [300, 620], + }, + { + parameters: { + options: {}, + }, + id: '444650ce-464a-4630-9e24-109056105167', + name: 'Wait', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [740, 420], + webhookId: '1f4118f8-591a-48fe-a68d-6fec3c99b7a8', + }, + { + parameters: { + values: { + number: [ + { + name: 'wait', + }, + ], + }, + options: {}, + }, + id: '7a74a097-6563-4f1e-a327-97e5a43b8acb', + name: 'Set', + type: 'n8n-nodes-base.set', + typeVersion: 2, + position: [480, 620], + }, + { + parameters: { + options: {}, + }, + id: '9039eebf-6c11-4ce0-b8ad-0812774019d4', + name: 'Wait1', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [740, 800], + webhookId: '35ceb27a-3fb1-47a9-8678-2df16dcecbcb', + }, + { + parameters: { + options: {}, + }, + id: '7f130b16-8fac-4d93-a0ef-56dfe575f952', + name: 'Wait2', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [940, 420], + webhookId: 'cc8e2fd2-afc8-4a17-afda-fda943f4bd83', + }, + { + parameters: { + options: {}, + }, + id: '063e2097-b27a-4775-923c-5b839c434640', + name: 'Wait3', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1300, 420], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'ec908b56-8829-4566-a0b7-ced4bd16c550', + name: 'Wait4', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [940, 800], + webhookId: 'cc8e2fd2-afc8-4a17-afda-fda943f4bd83', + }, + { + parameters: { + options: {}, + }, + id: 'a7d279bd-7241-4744-8ef6-41468131dfa7', + name: 'Wait5', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1140, 800], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'f620aff1-7d9c-453f-a2c1-6e3b9a1664d3', + name: 'Wait6', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [760, 200], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: '1d9bac9b-8197-4ad9-9189-f947068f1a46', + name: 'Wait7', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1060, 200], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: '9ad0cc8c-4922-440e-913c-39c8570ddcbc', + name: 'Wait8', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [740, 600], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'af0ca700-b6ed-40c1-8c62-bbadb6fd81f7', + name: 'Wait9', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1040, 580], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'f2553f9f-670f-4b54-8b89-84dd5a27a244', + name: 'Wait10', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1660, 340], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: '0f4475cb-87db-4ed7-a7a0-8a67043c320b', + name: 'Wait11', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1660, 540], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: '815f7b2a-1789-48a3-be61-931e643e6d89', + name: 'Wait12', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1920, 340], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: { + options: {}, + }, + id: 'be1e11af-b8e4-40cb-af36-03613e384b5e', + name: 'Wait13', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [1240, 580], + webhookId: '35400ecf-3e53-4b2d-9fd7-2663bbfd830f', + }, + { + parameters: {}, + id: 'cf72f99c-612f-4b76-bc8e-d77612e4faa9', + name: 'Merge', + type: 'n8n-nodes-base.merge', + typeVersion: 2, position: [1300, 220], }, { @@ -3931,4 +4316,935 @@ export const predefinedWorkflowExecuteTests: WorkflowTestData[] = [ nodeData: {}, }, }, + { + description: 'should simply execute the next multi-input-node (totally ignoring the runIndex)', + input: { + workflowData: { + nodes: [ + { + parameters: { + values: { + number: [ + { + name: 'counter', + value: '={{ ($input.first().json.counter || 0) + 1 }}', + }, + ], + }, + options: {}, + }, + id: '18191406-b56b-4388-9d4b-ff5b22fdc02c', + name: 'Set', + type: 'n8n-nodes-base.set', + typeVersion: 2, + position: [640, 660], + }, + { + parameters: { + conditions: { + number: [ + { + value1: '={{ $json.counter }}', + value2: 3, + }, + ], + }, + }, + id: '0c6f239b-f9f5-4a20-b554-c69e7bc692b1', + name: 'IF', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [900, 660], + }, + { + parameters: {}, + id: '463194c3-4fcb-4da4-bba0-bc58462ac59a', + name: 'Merge', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1180, 760], + }, + { + parameters: { + values: { + number: [ + { + name: 'counter', + value: '={{ ($input.first().json.counter || 0) + 1 }}', + }, + ], + }, + options: {}, + }, + id: '8b5177c1-34ab-468f-8cb1-ff1d253562dc', + name: 'Set1', + type: 'n8n-nodes-base.set', + typeVersion: 2, + position: [640, 320], + }, + { + parameters: { + conditions: { + number: [ + { + value1: '={{ $json.counter }}', + value2: 3, + }, + ], + }, + }, + id: '455663ab-bc3b-4674-9769-7428c85918c3', + name: 'IF1', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [860, 320], + }, + { + parameters: {}, + id: 'ffc0d327-5cbc-4cf3-8fb0-77c087b391c1', + name: 'Merge1', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1180, 420], + }, + { + parameters: {}, + id: '9a5b13a4-eba1-4a18-a4c6-36bedb07d975', + name: 'Merge2', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1500, 600], + }, + { + parameters: {}, + id: '89a78e50-2ec6-48bf-be5f-3838600cd08a', + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [-20, 700], + }, + ], + connections: { + Set: { + main: [ + [ + { + node: 'IF', + type: 'main', + index: 0, + }, + ], + ], + }, + IF: { + main: [ + [ + { + node: 'Set', + type: 'main', + index: 0, + }, + { + node: 'Merge1', + type: 'main', + index: 1, + }, + ], + [ + { + node: 'Merge', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge: { + main: [ + [ + { + node: 'Merge2', + type: 'main', + index: 1, + }, + ], + ], + }, + Set1: { + main: [ + [ + { + node: 'IF1', + type: 'main', + index: 0, + }, + ], + ], + }, + IF1: { + main: [ + [ + { + node: 'Set1', + type: 'main', + index: 0, + }, + { + node: 'Merge1', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge1: { + main: [ + [ + { + node: 'Merge2', + type: 'main', + index: 0, + }, + ], + ], + }, + Start: { + main: [ + [ + { + node: 'Merge', + type: 'main', + index: 1, + }, + { + node: 'Set1', + type: 'main', + index: 0, + }, + { + node: 'Set', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: [ + 'Start', + 'Set1', + 'IF1', + 'Set1', + 'IF1', + 'Set1', + 'IF1', + 'Set', + 'IF', + 'Merge1', + 'Set', + 'IF', + 'Merge1', + 'Set', + 'IF', + 'Merge', + 'Merge2', + 'Merge2', + ], + nodeData: { + Start: [[{}]], + Set1: [ + [ + { + counter: 1, + }, + ], + [ + { + counter: 2, + }, + ], + [ + { + counter: 3, + }, + ], + ], + Set: [ + [ + { + counter: 1, + }, + ], + [ + { + counter: 2, + }, + ], + [ + { + counter: 3, + }, + ], + ], + IF1: [ + [ + { + counter: 1, + }, + ], + [ + { + counter: 2, + }, + ], + [], + ], + IF: [ + [ + { + counter: 1, + }, + ], + [ + { + counter: 2, + }, + ], + [], + ], + Merge1: [ + [ + { + counter: 1, + }, + { + counter: 1, + }, + ], + [ + { + counter: 2, + }, + { + counter: 2, + }, + ], + ], + Merge: [ + [ + { + counter: 3, + }, + {}, + ], + ], + Merge2: [ + [ + { + counter: 1, + }, + { + counter: 1, + }, + { + counter: 3, + }, + {}, + ], + [ + { + counter: 2, + }, + { + counter: 2, + }, + ], + ], + }, + }, + }, + { + description: 'should run keep on executing even if data from input 1 is missing', + input: { + workflowData: { + nodes: [ + { + parameters: {}, + id: '9c0cb647-5d60-40dc-b791-4946ee260a5d', + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [180, 240], + }, + { + parameters: { + values: { + string: [ + { + name: 'test', + value: 'a', + }, + ], + }, + options: {}, + }, + id: '2bed3b26-0907-465b-a416-9dc993c2e302', + name: 'Set', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [400, 240], + }, + { + parameters: { + conditions: { + string: [ + { + value1: '={{ $json["test"] }}', + value2: 'b', + }, + ], + }, + }, + id: 'eca22a12-fb0c-4a4f-ab97-74544c178714', + name: 'IF', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [620, 240], + }, + { + parameters: {}, + id: '8d63caea-8d89-450e-87ae-6097b9821a70', + name: 'NoOp', + type: 'n8n-nodes-base.noOp', + typeVersion: 1, + position: [860, 160], + }, + { + parameters: {}, + id: 'bd0e79e4-7b7a-4016-ace3-6f54f46b41c3', + name: 'NoOp1', + type: 'n8n-nodes-base.noOp', + typeVersion: 1, + position: [860, 300], + }, + { + parameters: {}, + id: '975966f6-8e59-41d8-a69e-7223476a7c50', + name: 'Merge', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1140, 220], + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'Set', + type: 'main', + index: 0, + }, + ], + ], + }, + Set: { + main: [ + [ + { + node: 'IF', + type: 'main', + index: 0, + }, + ], + ], + }, + IF: { + main: [ + [ + { + node: 'NoOp', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'NoOp1', + type: 'main', + index: 0, + }, + ], + ], + }, + NoOp: { + main: [ + [ + { + node: 'Merge', + type: 'main', + index: 0, + }, + ], + ], + }, + NoOp1: { + main: [ + [ + { + node: 'Merge', + type: 'main', + index: 1, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: ['Start', 'Set', 'IF', 'NoOp1', 'Merge'], + nodeData: { + Merge: [ + [ + { + test: 'a', + }, + ], + ], + }, + }, + }, + { + description: + 'should run complicated multi node workflow where multiple Merge-Node have missing data and complex dependency structure', + input: { + workflowData: { + nodes: [ + { + parameters: { + conditions: { + string: [ + { + value1: '={{ $json["test"] }}', + value2: 'b', + }, + ], + }, + }, + id: '21593a8c-07c1-435b-93a6-75317ee3bf67', + name: 'IF4', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [880, 1240], + }, + { + parameters: {}, + id: 'a9af6b9f-011c-4b34-a367-0cfa5ad4c865', + name: 'NoOp2', + type: 'n8n-nodes-base.noOp', + typeVersion: 1, + position: [1320, 1060], + }, + { + parameters: {}, + id: '429d1a51-65f0-4701-af76-b73611774952', + name: 'Merge3', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1100, 1060], + }, + { + parameters: { + conditions: { + string: [ + { + value1: '={{ $json["test"] }}', + value2: 'b', + }, + ], + }, + }, + id: 'ed08db0f-f747-4f87-af62-051fc53f955c', + name: 'IF3', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [620, 1060], + }, + { + parameters: {}, + id: 'e80d2aac-cbd4-4e7c-9817-83db52a617d4', + name: 'Merge2', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [940, 900], + }, + { + parameters: { + conditions: { + string: [ + { + value1: '={{ $json["test"] }}', + value2: 'a', + }, + ], + }, + }, + id: '766dad6b-4326-41b5-a02a-0b3b7d879eb4', + name: 'IF2', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [620, 900], + }, + { + parameters: {}, + id: '0c0cd5bb-eb44-48fe-b66a-54a3c541ea57', + name: 'Merge7', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [2180, 1180], + }, + { + parameters: {}, + id: '863a00e5-7be4-43f3-97da-07cf552d7c0e', + name: 'Merge6', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1840, 1200], + }, + { + parameters: {}, + id: '8855d0ca-1deb-4ad8-958b-2379d3a87160', + name: 'Merge5', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1600, 1040], + }, + { + parameters: {}, + id: 'ea37e388-c77a-4a2f-a527-4585f24371d5', + name: 'Merge4', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [1180, 880], + }, + { + parameters: {}, + id: 'e3c814e9-9a92-4e12-96d5-85634fe76dc9', + name: 'Merge1', + type: 'n8n-nodes-base.merge', + typeVersion: 2, + position: [940, 720], + }, + { + parameters: { + conditions: { + string: [ + { + value1: '={{ $json["test"] }}', + value2: 'b', + }, + ], + }, + }, + id: 'a21a3932-8a3f-464f-8393-309d3233433a', + name: 'IF1', + type: 'n8n-nodes-base.if', + typeVersion: 1, + position: [620, 720], + }, + { + parameters: { + values: { + string: [ + { + name: 'test', + value: 'a', + }, + ], + }, + options: {}, + }, + id: '12d33a38-baeb-41de-aea0-d8a7477f5aa6', + name: 'Set1', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [400, 720], + }, + { + parameters: {}, + id: '41589b0b-0521-41ae-b0c6-80a016af803e', + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [160, 240], + }, + ], + connections: { + IF4: { + main: [ + [ + { + node: 'Merge3', + type: 'main', + index: 1, + }, + ], + [ + { + node: 'Merge6', + type: 'main', + index: 1, + }, + ], + ], + }, + NoOp2: { + main: [ + [ + { + node: 'Merge5', + type: 'main', + index: 1, + }, + ], + ], + }, + Merge3: { + main: [ + [ + { + node: 'NoOp2', + type: 'main', + index: 0, + }, + ], + ], + }, + IF3: { + main: [ + [ + { + node: 'Merge3', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'IF4', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge2: { + main: [ + [ + { + node: 'Merge4', + type: 'main', + index: 1, + }, + ], + ], + }, + IF2: { + main: [ + [ + { + node: 'Merge2', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'Merge2', + type: 'main', + index: 1, + }, + ], + ], + }, + Merge6: { + main: [ + [ + { + node: 'Merge7', + type: 'main', + index: 1, + }, + ], + ], + }, + Merge5: { + main: [ + [ + { + node: 'Merge6', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge4: { + main: [ + [ + { + node: 'Merge5', + type: 'main', + index: 0, + }, + ], + ], + }, + Merge1: { + main: [ + [ + { + node: 'Merge4', + type: 'main', + index: 0, + }, + ], + ], + }, + IF1: { + main: [ + [ + { + node: 'Merge1', + type: 'main', + index: 0, + }, + ], + [ + { + node: 'Merge1', + type: 'main', + index: 1, + }, + ], + ], + }, + Set1: { + main: [ + [ + { + node: 'IF1', + type: 'main', + index: 0, + }, + { + node: 'IF2', + type: 'main', + index: 0, + }, + { + node: 'IF3', + type: 'main', + index: 0, + }, + ], + ], + }, + Start: { + main: [ + [ + { + node: 'Set1', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: [ + 'Start', + 'Set1', + 'IF1', + 'IF2', + 'IF3', + 'IF4', + 'Merge1', + 'Merge2', + 'Merge4', + 'Merge5', + 'Merge6', + 'Merge7', + ], + nodeData: { + Merge1: [ + [ + { + test: 'a', + }, + ], + ], + Merge2: [ + [ + { + test: 'a', + }, + ], + ], + Merge4: [ + [ + { + test: 'a', + }, + { + test: 'a', + }, + ], + ], + Merge5: [ + [ + { + test: 'a', + }, + { + test: 'a', + }, + ], + ], + Merge6: [ + [ + { + test: 'a', + }, + { + test: 'a', + }, + { + test: 'a', + }, + ], + ], + Merge7: [ + [ + { + test: 'a', + }, + { + test: 'a', + }, + { + test: 'a', + }, + ], + ], + }, + }, + }, ]; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index aaa6ae8d4b..e0f6c6981f 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -702,6 +702,7 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow { maxExecutionTimeout?: number; callerIds?: string; callerPolicy?: WorkflowSettings.CallerPolicy; + executionOrder: NonNullable; } export interface ITimeoutHMS { diff --git a/packages/editor-ui/src/api/workflows.ts b/packages/editor-ui/src/api/workflows.ts index 24f047aeb7..2c2cbc6b54 100644 --- a/packages/editor-ui/src/api/workflows.ts +++ b/packages/editor-ui/src/api/workflows.ts @@ -7,6 +7,7 @@ export async function getNewWorkflow(context: IRestApiContext, name?: string) { return { name: response.name, onboardingFlowEnabled: response.onboardingFlowEnabled === true, + settings: response.defaultSettings, }; } diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 4f27094fa1..0370c9bfcb 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -13,6 +13,31 @@ >