mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +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│
|
// │orphan│
|
||||||
|
|||||||
@@ -492,4 +492,35 @@ describe('DirectedGraph', () => {
|
|||||||
expect(graph.hasNode(node.name + 'foo')).toBe(false);
|
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());
|
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 } = {}) {
|
getConnections(filter: { to?: INode } = {}) {
|
||||||
const filteredCopy: GraphConnection[] = [];
|
const filteredCopy: GraphConnection[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ export class WorkflowExecute {
|
|||||||
const filteredNodes = graph.getNodes();
|
const filteredNodes = graph.getNodes();
|
||||||
|
|
||||||
// 3. Find the Start Nodes
|
// 3. Find the Start Nodes
|
||||||
const dirtyNodes = new Set(workflow.getNodes(dirtyNodeNames));
|
const dirtyNodes = graph.getNodesByNames(dirtyNodeNames);
|
||||||
runData = cleanRunData(runData, graph, dirtyNodes);
|
runData = cleanRunData(runData, graph, dirtyNodes);
|
||||||
let startNodes = findStartNodes({ graph, trigger, destination, runData, pinData });
|
let startNodes = findStartNodes({ graph, trigger, destination, runData, pinData });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user