fix(core): Use destination node to select the correct pinned trigger to start from (#15633)

Co-authored-by: Danny Martini <danny@n8n.io>
This commit is contained in:
Elias Meire
2025-05-23 16:29:53 +02:00
committed by GitHub
parent 5636fb62ca
commit dc0802bbd1
2 changed files with 78 additions and 3 deletions

View File

@@ -1,6 +1,13 @@
import type { User } from '@n8n/db';
import { mock } from 'jest-mock-extended';
import type { INode, IWorkflowBase, INodeType, IWorkflowExecuteAdditionalData } from 'n8n-workflow';
import {
NodeConnectionTypes,
type IConnections,
type INode,
type INodeType,
type IWorkflowBase,
type IWorkflowExecuteAdditionalData,
} from 'n8n-workflow';
import type { NodeTypes } from '@/node-types';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
@@ -52,6 +59,15 @@ const hackerNewsNode: INode = {
position: [0, 0],
};
const secondHackerNewsNode: INode = {
name: 'Hacker News 2',
type: 'n8n-nodes-base.hackerNews',
id: '55d63bca-bb6c-4568-948f-8ed9aacb1fe3',
parameters: {},
typeVersion: 1,
position: [0, 0],
};
describe('WorkflowExecutionService', () => {
const nodeTypes = mock<NodeTypes>();
const workflowRunner = mock<WorkflowRunner>();
@@ -417,6 +433,23 @@ describe('WorkflowExecutionService', () => {
expect(node).toEqual(webhookNode);
});
it('should favor webhook node connected to the destination node', () => {
workflow.nodes.push(webhookNode, secondWebhookNode, hackerNewsNode, secondHackerNewsNode);
workflow.connections = {
...createMainConnection(webhookNode.name, hackerNewsNode.name),
...createMainConnection(secondWebhookNode.name, secondHackerNewsNode.name),
};
const node = workflowExecutionService.selectPinnedActivatorStarter(
workflow,
[],
{ ...pinData, [secondWebhookNode.name]: [{ json: { key: 'value' } }] },
secondHackerNewsNode.name,
);
expect(node).toEqual(secondWebhookNode);
});
describe('offloading manual executions to workers', () => {
test('when receiving no `runData`, should set `runData` to undefined in `executionData`', async () => {
const originalEnv = process.env;
@@ -458,3 +491,19 @@ describe('WorkflowExecutionService', () => {
});
});
});
function createMainConnection(targetNode: string, sourceNode: string): IConnections {
return {
[sourceNode]: {
[NodeConnectionTypes.Main]: [
[
{
node: targetNode,
type: 'main',
index: 0,
},
],
],
},
};
}

View File

@@ -117,6 +117,7 @@ export class WorkflowExecutionService {
workflowData,
startNodes?.map((nodeData) => nodeData.name),
pinData,
destinationNode,
);
// TODO: Reverse the order of events, first find out if the execution is
@@ -378,7 +379,12 @@ export class WorkflowExecutionService {
* prioritizing `n8n-nodes-base.webhook` over other activators. If the executed node
* has no upstream nodes and is itself is a pinned activator, select it.
*/
selectPinnedActivatorStarter(workflow: IWorkflowBase, startNodes?: string[], pinData?: IPinData) {
selectPinnedActivatorStarter(
workflow: IWorkflowBase,
startNodes?: string[],
pinData?: IPinData,
destinationNode?: string,
) {
if (!pinData || !startNodes) return null;
const allPinnedActivators = this.findAllPinnedActivators(workflow, pinData);
@@ -389,7 +395,27 @@ export class WorkflowExecutionService {
// full manual execution
if (startNodes?.length === 0) return firstPinnedActivator ?? null;
if (startNodes?.length === 0) {
// If there is a destination node, find the pinned activator that is a parent of the destination node
if (destinationNode) {
const destinationParents = new Set(
new Workflow({
nodes: workflow.nodes,
connections: workflow.connections,
active: workflow.active,
nodeTypes: this.nodeTypes,
}).getParentNodes(destinationNode),
);
const activator = allPinnedActivators.find((a) => destinationParents.has(a.name));
if (activator) {
return activator;
}
}
return firstPinnedActivator ?? null;
}
// partial manual execution