fix(core): Manual execution defaults to Manual trigger (#15052)

Co-authored-by: Danny Martini <danny@n8n.io>
This commit is contained in:
Raúl Gómez Morales
2025-05-05 08:25:45 +02:00
committed by GitHub
parent 40d0702ed3
commit c1760631cf
2 changed files with 58 additions and 38 deletions

View File

@@ -12,6 +12,7 @@ import type {
IExecuteData, IExecuteData,
IWaitingForExecution, IWaitingForExecution,
IWaitingForExecutionSource, IWaitingForExecutionSource,
INodeExecutionData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import type PCancelable from 'p-cancelable'; import type PCancelable from 'p-cancelable';
@@ -24,64 +25,73 @@ describe('ManualExecutionService', () => {
describe('getExecutionStartNode', () => { describe('getExecutionStartNode', () => {
it('Should return undefined', () => { it('Should return undefined', () => {
const data = { const data = mock<IWorkflowExecutionDataProcess>();
pinData: {}, const workflow = mock<Workflow>({
startNodes: [], getTriggerNodes() {
} as unknown as IWorkflowExecutionDataProcess; return [];
const workflow = {
getNode(nodeName: string) {
return {
name: nodeName,
};
}, },
} as unknown as Workflow; });
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow); const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode).toBeUndefined(); expect(executionStartNode).toBeUndefined();
}); });
it('Should return startNode', () => { it('Should return startNode', () => {
const data = { const data = mock<IWorkflowExecutionDataProcess>({
pinData: { pinData: {
node1: {}, node1: [mock<INodeExecutionData>()],
node2: {}, node2: [mock<INodeExecutionData>()],
}, },
startNodes: [{ name: 'node2' }], startNodes: [{ name: 'node2' }],
} as unknown as IWorkflowExecutionDataProcess; });
const workflow = { const workflow = mock<Workflow>({
getNode(nodeName: string) { getNode(nodeName: string) {
if (nodeName === 'node2') { if (nodeName === 'node2') {
return { return mock<INode>({ name: 'node2' });
name: 'node2',
};
} }
return undefined; return null;
}, },
} as unknown as Workflow;
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode).toEqual({
name: 'node2',
}); });
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode?.name).toEqual('node2');
}); });
it('Should return triggerToStartFrom trigger node', () => { it('Should return triggerToStartFrom trigger node', () => {
const data = { const data = mock<IWorkflowExecutionDataProcess>({
pinData: { pinData: {
node1: {}, node1: [mock<INodeExecutionData>()],
node2: {}, node2: [mock<INodeExecutionData>()],
}, },
triggerToStartFrom: { name: 'node3' }, triggerToStartFrom: { name: 'node3' },
} as unknown as IWorkflowExecutionDataProcess;
const workflow = {
getNode(nodeName: string) {
return {
name: nodeName,
};
},
} as unknown as Workflow;
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode).toEqual({
name: 'node3',
}); });
const workflow = mock<Workflow>({
getNode(nodeName: string) {
return mock<INode>({ name: nodeName });
},
});
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode?.name).toEqual('node3');
});
it('should default to The manual trigger', () => {
const data = mock<IWorkflowExecutionDataProcess>();
const manualTrigger = mock<INode>({
type: 'n8n-nodes-base.manualTrigger',
name: 'When clicking Test workflow',
});
const workflow = mock<Workflow>({
getTriggerNodes() {
return [
mock<INode>({
type: 'n8n-nodes-base.scheduleTrigger',
name: 'Wed 12:00',
}),
manualTrigger,
];
},
});
const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow);
expect(executionStartNode?.name).toBe(manualTrigger.name);
}); });
}); });
@@ -230,6 +240,7 @@ describe('ManualExecutionService', () => {
const workflow = mock<Workflow>({ const workflow = mock<Workflow>({
getNode: jest.fn().mockReturnValue(null), getNode: jest.fn().mockReturnValue(null),
getTriggerNodes: jest.fn().mockReturnValue([]),
}); });
const additionalData = mock<IWorkflowExecuteAdditionalData>(); const additionalData = mock<IWorkflowExecuteAdditionalData>();

View File

@@ -9,6 +9,7 @@ import {
isTool, isTool,
rewireGraph, rewireGraph,
} from 'n8n-core'; } from 'n8n-core';
import { MANUAL_TRIGGER_NODE_TYPE } from 'n8n-workflow';
import type { import type {
IPinData, IPinData,
IRun, IRun,
@@ -39,7 +40,15 @@ export class ManualExecutionService {
startNode = workflow.getNode(data.startNodes[0].name) ?? undefined; startNode = workflow.getNode(data.startNodes[0].name) ?? undefined;
} }
return startNode; if (startNode) {
return startNode;
}
const manualTrigger = workflow
.getTriggerNodes()
.find((node) => node.type === MANUAL_TRIGGER_NODE_TYPE);
return manualTrigger;
} }
// eslint-disable-next-line @typescript-eslint/promise-function-async // eslint-disable-next-line @typescript-eslint/promise-function-async