diff --git a/packages/core/src/execution-engine/partial-execution-utils/__tests__/clean-run-data.test.ts b/packages/core/src/execution-engine/partial-execution-utils/__tests__/clean-run-data.test.ts index 7de648e6fd8c5..77611703f0ddc 100644 --- a/packages/core/src/execution-engine/partial-execution-utils/__tests__/clean-run-data.test.ts +++ b/packages/core/src/execution-engine/partial-execution-utils/__tests__/clean-run-data.test.ts @@ -1,4 +1,15 @@ -import type { IRunData } from 'n8n-workflow'; +// NOTE: Diagrams in this file have been created with https://asciiflow.com/#/ +// If you update the tests, please update the diagrams as well. +// If you add a test, please create a new diagram. +// +// Map +// 0 means the output has no run data +// 1 means the output has run data +// ►► denotes the node that the user wants to execute to +// XX denotes that the node is disabled +// PD denotes that the node has pinned data + +import { NodeConnectionType, type IRunData } from 'n8n-workflow'; import { createNodeData, toITaskData } from './helpers'; import { cleanRunData } from '../clean-run-data'; @@ -111,4 +122,73 @@ describe('cleanRunData', () => { [node1.name]: [toITaskData([{ data: { value: 1 } }])], }); }); + + // ┌─────┐ ┌────────┐ + // │node1├─────►rootNode│ + // └─────┘ └───▲────┘ + // │ + // ┌───┴───┐ + // │subNode│ + // └───────┘ + test('removes run data of sub nodes when the start node is a root node', () => { + // ARRANGE + const node1 = createNodeData({ name: 'Node1' }); + const rootNode = createNodeData({ name: 'Root Node' }); + const subNode = createNodeData({ name: 'Sub Node' }); + const graph = new DirectedGraph() + .addNodes(node1, rootNode, subNode) + .addConnections( + { from: node1, to: rootNode }, + { from: subNode, to: rootNode, type: NodeConnectionType.AiLanguageModel }, + ); + const runData: IRunData = { + [node1.name]: [toITaskData([{ data: { value: 1 } }])], + [rootNode.name]: [toITaskData([{ data: { value: 2 } }])], + [subNode.name]: [toITaskData([{ data: { value: 3 } }])], + }; + + // ACT + const newRunData = cleanRunData(runData, graph, new Set([rootNode])); + + // ASSERT + expect(newRunData).toEqual({ + [node1.name]: [toITaskData([{ data: { value: 1 } }])], + }); + }); + + // ┌─────┐ ┌─────┐ ┌────────┐ + // │node1├───►node2├────►rootNode│ + // └─────┘ └─────┘ └───▲────┘ + // │ + // ┌───┴───┐ + // │subNode│ + // └───────┘ + test('removes run data of sub nodes for root nodes downstream of the start node', () => { + // ARRANGE + const node1 = createNodeData({ name: 'Node1' }); + const node2 = createNodeData({ name: 'Node2' }); + const rootNode = createNodeData({ name: 'Root Node' }); + const subNode = createNodeData({ name: 'Sub Node' }); + const graph = new DirectedGraph() + .addNodes(node1, node2, rootNode, subNode) + .addConnections( + { from: node1, to: node2 }, + { from: node2, to: rootNode }, + { from: subNode, to: rootNode, type: NodeConnectionType.AiLanguageModel }, + ); + const runData: IRunData = { + [node1.name]: [toITaskData([{ data: { value: 1 } }])], + [node2.name]: [toITaskData([{ data: { value: 1 } }])], + [rootNode.name]: [toITaskData([{ data: { value: 2 } }])], + [subNode.name]: [toITaskData([{ data: { value: 3 } }])], + }; + + // ACT + const newRunData = cleanRunData(runData, graph, new Set([node2])); + + // ASSERT + expect(newRunData).toEqual({ + [node1.name]: [toITaskData([{ data: { value: 1 } }])], + }); + }); }); diff --git a/packages/core/src/execution-engine/partial-execution-utils/clean-run-data.ts b/packages/core/src/execution-engine/partial-execution-utils/clean-run-data.ts index 78d08acd9b53d..a6cf7f0b27273 100644 --- a/packages/core/src/execution-engine/partial-execution-utils/clean-run-data.ts +++ b/packages/core/src/execution-engine/partial-execution-utils/clean-run-data.ts @@ -1,4 +1,4 @@ -import type { INode, IRunData } from 'n8n-workflow'; +import { NodeConnectionType, type INode, type IRunData } from 'n8n-workflow'; import type { DirectedGraph } from './directed-graph'; @@ -16,10 +16,22 @@ export function cleanRunData( for (const startNode of startNodes) { delete newRunData[startNode.name]; + const children = graph.getChildren(startNode); + for (const node of [startNode, ...children]) { + delete newRunData[node.name]; + + // Delete runData for subNodes + const subNodeConnections = graph.getParentConnections(node); + for (const subNodeConnection of subNodeConnections) { + // Sub nodes never use the Main connection type, so this filters our + // the connection that goes upstream of the startNode. + if (subNodeConnection.type === NodeConnectionType.Main) { + continue; + } - for (const child of children) { - delete newRunData[child.name]; + delete newRunData[subNodeConnection.from.name]; + } } }