Skip to content

Commit ddfe594

Browse files
authored
fix(core): Prefer triggers with run data during partial executions (#14691)
1 parent 048df28 commit ddfe594

File tree

3 files changed

+37
-8
lines changed

3 files changed

+37
-8
lines changed

packages/core/src/execution-engine/partial-execution-utils/__tests__/find-trigger-for-partial-execution.test.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { mock } from 'jest-mock-extended';
2-
import type { IConnections, INode, INodeType, INodeTypes, IPinData } from 'n8n-workflow';
2+
import type { IConnections, INode, INodeType, INodeTypes, IPinData, IRunData } from 'n8n-workflow';
33
import { Workflow } from 'n8n-workflow';
44

5-
import { toIConnections } from './helpers';
5+
import { createNodeData, toIConnections, toITaskData } from './helpers';
6+
import { DirectedGraph } from '../directed-graph';
67
import { findTriggerForPartialExecution } from '../find-trigger-for-partial-execution';
78

89
describe('findTriggerForPartialExecution', () => {
@@ -188,7 +189,7 @@ describe('findTriggerForPartialExecution', () => {
188189
'$description',
189190
({ nodes, connections, destinationNodeName, expectedTrigger, pinData }) => {
190191
const workflow = createMockWorkflow(nodes, toIConnections(connections), pinData);
191-
expect(findTriggerForPartialExecution(workflow, destinationNodeName)).toBe(
192+
expect(findTriggerForPartialExecution(workflow, destinationNodeName, {})).toBe(
192193
expectedTrigger,
193194
);
194195
},
@@ -199,19 +200,39 @@ describe('findTriggerForPartialExecution', () => {
199200
describe('Error and Edge Case Handling', () => {
200201
it('should handle non-existent destination node gracefully', () => {
201202
const workflow = createMockWorkflow([], {});
202-
expect(findTriggerForPartialExecution(workflow, 'NonExistentNode')).toBeUndefined();
203+
expect(findTriggerForPartialExecution(workflow, 'NonExistentNode', {})).toBeUndefined();
203204
});
204205

205206
it('should handle empty workflow', () => {
206207
const workflow = createMockWorkflow([], {});
207-
expect(findTriggerForPartialExecution(workflow, '')).toBeUndefined();
208+
expect(findTriggerForPartialExecution(workflow, '', {})).toBeUndefined();
208209
});
209210

210211
it('should handle workflow with no connections', () => {
211212
const workflow = createMockWorkflow([manualTriggerNode], {});
212-
expect(findTriggerForPartialExecution(workflow, manualTriggerNode.name)).toBe(
213+
expect(findTriggerForPartialExecution(workflow, manualTriggerNode.name, {})).toBe(
213214
manualTriggerNode,
214215
);
215216
});
217+
218+
it('should prefer triggers that have run data', () => {
219+
// ARRANGE
220+
const trigger1 = createNodeData({ name: 'trigger1', type: 'n8n-nodes-base.manualTrigger' });
221+
const trigger2 = createNodeData({ name: 'trigger2', type: 'n8n-nodes-base.manualTrigger' });
222+
const node = createNodeData({ name: 'node' });
223+
const workflow = new DirectedGraph()
224+
.addNodes(trigger1, trigger2, node)
225+
.addConnections({ from: trigger1, to: node }, { from: trigger2, to: node })
226+
.toWorkflow({ name: '', active: false, nodeTypes });
227+
const runData: IRunData = {
228+
[trigger1.name]: [toITaskData([{ data: { nodeName: 'trigger1' } }])],
229+
};
230+
231+
// ACT
232+
const chosenTrigger = findTriggerForPartialExecution(workflow, node.name, runData);
233+
234+
// ASSERT
235+
expect(chosenTrigger).toBe(trigger1);
236+
});
216237
});
217238
});

packages/core/src/execution-engine/partial-execution-utils/find-trigger-for-partial-execution.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as assert from 'assert/strict';
2-
import type { INode, INodeType, Workflow } from 'n8n-workflow';
2+
import type { INode, INodeType, IRunData, Workflow } from 'n8n-workflow';
33

44
const isTriggerNode = (nodeType: INodeType) => nodeType.description.group.includes('trigger');
55

@@ -29,6 +29,7 @@ function findAllParentTriggers(workflow: Workflow, destinationNodeName: string)
2929
export function findTriggerForPartialExecution(
3030
workflow: Workflow,
3131
destinationNodeName: string,
32+
runData: IRunData,
3233
): INode | undefined {
3334
// First, check if the destination node itself is a trigger
3435
const destinationNode = workflow.getNode(destinationNodeName);
@@ -48,6 +49,13 @@ export function findTriggerForPartialExecution(
4849
(trigger) => !trigger.disabled,
4950
);
5051

52+
// prefer triggers that have run data
53+
for (const trigger of parentTriggers) {
54+
if (runData[trigger.name]) {
55+
return trigger;
56+
}
57+
}
58+
5159
// Prioritize webhook triggers with pinned-data
5260
const pinnedTriggers = parentTriggers
5361
// TODO: add the other filters here from `findAllPinnedActivators`, see

packages/core/src/execution-engine/workflow-execute.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export class WorkflowExecute {
394394
}
395395

396396
// 1. Find the Trigger
397-
const trigger = findTriggerForPartialExecution(workflow, destinationNodeName);
397+
const trigger = findTriggerForPartialExecution(workflow, destinationNodeName, runData);
398398
if (trigger === undefined) {
399399
throw new UserError('Connect a trigger to run this node');
400400
}

0 commit comments

Comments
 (0)