diff --git a/packages/core/src/execution-engine/__tests__/workflow-execute.test.ts b/packages/core/src/execution-engine/__tests__/workflow-execute.test.ts index 75a1cc55d0..9a40520502 100644 --- a/packages/core/src/execution-engine/__tests__/workflow-execute.test.ts +++ b/packages/core/src/execution-engine/__tests__/workflow-execute.test.ts @@ -540,6 +540,55 @@ describe('WorkflowExecute', () => { ); }); + // DR ►► DR + // ┌───────┐ ┌───────────┐ ┌─────────┐ + // │trigger├───►destination├───►dirtyNode│ + // └───────┘ └───────────┘ └─────────┘ + test('passes pruned dirty nodes to `cleanRunData`', async () => { + // ARRANGE + const waitPromise = createDeferredPromise(); + const nodeExecutionOrder: string[] = []; + const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder); + const workflowExecute = new WorkflowExecute(additionalData, 'manual'); + + const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' }); + const destination = createNodeData({ name: 'destination' }); + const dirtyNode = createNodeData({ name: 'dirtyNode' }); + const workflow = new DirectedGraph() + .addNodes(trigger, destination, dirtyNode) + .addConnections({ from: trigger, to: destination }, { from: destination, to: dirtyNode }) + .toWorkflow({ name: '', active: false, nodeTypes }); + + const pinData: IPinData = {}; + const runData: IRunData = {}; + const dirtyNodeNames: string[] = [trigger.name, dirtyNode.name]; + + jest.spyOn(workflowExecute, 'processRunExecutionData').mockImplementationOnce(jest.fn()); + const cleanRunDataSpy = jest.spyOn(partialExecutionUtils, 'cleanRunData'); + + // ACT + await workflowExecute.runPartialWorkflow2( + workflow, + runData, + pinData, + dirtyNodeNames, + destination.name, + ); + + // ASSERT + const subgraph = new DirectedGraph() + .addNodes(trigger, destination) + .addConnections({ from: trigger, to: destination }); + expect(cleanRunDataSpy).toHaveBeenCalledTimes(2); + expect(cleanRunDataSpy).toHaveBeenNthCalledWith( + 1, + runData, + subgraph, + // first call with the dirty nodes, which are an empty set in this case + new Set([trigger]), + ); + }); + // ►► // ┌──────┐ // │orphan│ diff --git a/packages/core/src/execution-engine/partial-execution-utils/__tests__/directed-graph.test.ts b/packages/core/src/execution-engine/partial-execution-utils/__tests__/directed-graph.test.ts index a825c144a9..7b4e090761 100644 --- a/packages/core/src/execution-engine/partial-execution-utils/__tests__/directed-graph.test.ts +++ b/packages/core/src/execution-engine/partial-execution-utils/__tests__/directed-graph.test.ts @@ -492,4 +492,35 @@ describe('DirectedGraph', () => { expect(graph.hasNode(node.name + 'foo')).toBe(false); }); }); + + describe('getNodesByNames', () => { + test('returns empty Set when no names are provided', () => { + // ARRANGE + const node1 = createNodeData({ name: 'Node1' }); + const node2 = createNodeData({ name: 'Node2' }); + const graph = new DirectedGraph().addNodes(node1, node2); + + // ACT + const result = graph.getNodesByNames([]); + + // ASSERT + expect(result.size).toBe(0); + expect(result).toEqual(new Set()); + }); + + test('returns Set with only nodes that exist in the graph', () => { + // ARRANGE + const node1 = createNodeData({ name: 'Node1' }); + const node2 = createNodeData({ name: 'Node2' }); + const node3 = createNodeData({ name: 'Node3' }); + const graph = new DirectedGraph().addNodes(node1, node2, node3); + + // ACT + const result = graph.getNodesByNames(['Node1', 'Node3', 'Node4']); + + // ASSERT + expect(result.size).toBe(2); + expect(result).toEqual(new Set([node1, node3])); + }); + }); }); diff --git a/packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts b/packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts index 63d95367ab..de4d55a6e7 100644 --- a/packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts +++ b/packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts @@ -50,6 +50,25 @@ export class DirectedGraph { return new Map(this.nodes.entries()); } + /** + * Returns a set of nodes whose names match the provided array of names. + * + * Only nodes that exist in the graph will be included in the result. + */ + getNodesByNames(names: string[]) { + const nodes: Set = new Set(); + + for (const name of names) { + const node = this.nodes.get(name); + + if (node) { + nodes.add(node); + } + } + + return nodes; + } + getConnections(filter: { to?: INode } = {}) { const filteredCopy: GraphConnection[] = []; diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index d8894259dc..1d947390e6 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -404,7 +404,7 @@ export class WorkflowExecute { const filteredNodes = graph.getNodes(); // 3. Find the Start Nodes - const dirtyNodes = new Set(workflow.getNodes(dirtyNodeNames)); + const dirtyNodes = graph.getNodesByNames(dirtyNodeNames); runData = cleanRunData(runData, graph, dirtyNodes); let startNodes = findStartNodes({ graph, trigger, destination, runData, pinData });