mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(core): Don't fail partial execution when an unrelated node is dirty (#13925)
This commit is contained in:
@@ -540,6 +540,55 @@ describe('WorkflowExecute', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// DR ►► DR
|
||||
// ┌───────┐ ┌───────────┐ ┌─────────┐
|
||||
// │trigger├───►destination├───►dirtyNode│
|
||||
// └───────┘ └───────────┘ └─────────┘
|
||||
test('passes pruned dirty nodes to `cleanRunData`', async () => {
|
||||
// ARRANGE
|
||||
const waitPromise = createDeferredPromise<IRun>();
|
||||
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│
|
||||
|
||||
@@ -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]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<INode> = 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[] = [];
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user