From c1760631cf86942a8da19d0bf647d2eb70fc0477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20G=C3=B3mez=20Morales?= Date: Mon, 5 May 2025 08:25:45 +0200 Subject: [PATCH] fix(core): Manual execution defaults to Manual trigger (#15052) Co-authored-by: Danny Martini --- .../manual-execution.service.test.ts | 85 +++++++++++-------- packages/cli/src/manual-execution.service.ts | 11 ++- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/packages/cli/src/__tests__/manual-execution.service.test.ts b/packages/cli/src/__tests__/manual-execution.service.test.ts index e9b74172e3..4731b14eef 100644 --- a/packages/cli/src/__tests__/manual-execution.service.test.ts +++ b/packages/cli/src/__tests__/manual-execution.service.test.ts @@ -12,6 +12,7 @@ import type { IExecuteData, IWaitingForExecution, IWaitingForExecutionSource, + INodeExecutionData, } from 'n8n-workflow'; import type PCancelable from 'p-cancelable'; @@ -24,64 +25,73 @@ describe('ManualExecutionService', () => { describe('getExecutionStartNode', () => { it('Should return undefined', () => { - const data = { - pinData: {}, - startNodes: [], - } as unknown as IWorkflowExecutionDataProcess; - const workflow = { - getNode(nodeName: string) { - return { - name: nodeName, - }; + const data = mock(); + const workflow = mock({ + getTriggerNodes() { + return []; }, - } as unknown as Workflow; + }); const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow); expect(executionStartNode).toBeUndefined(); }); it('Should return startNode', () => { - const data = { + const data = mock({ pinData: { - node1: {}, - node2: {}, + node1: [mock()], + node2: [mock()], }, startNodes: [{ name: 'node2' }], - } as unknown as IWorkflowExecutionDataProcess; - const workflow = { + }); + const workflow = mock({ getNode(nodeName: string) { if (nodeName === 'node2') { - return { - name: 'node2', - }; + return mock({ 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', () => { - const data = { + const data = mock({ pinData: { - node1: {}, - node2: {}, + node1: [mock()], + node2: [mock()], }, 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({ + getNode(nodeName: string) { + return mock({ name: nodeName }); + }, + }); + const executionStartNode = manualExecutionService.getExecutionStartNode(data, workflow); + expect(executionStartNode?.name).toEqual('node3'); + }); + + it('should default to The manual trigger', () => { + const data = mock(); + const manualTrigger = mock({ + type: 'n8n-nodes-base.manualTrigger', + name: 'When clicking ‘Test workflow’', + }); + + const workflow = mock({ + getTriggerNodes() { + return [ + mock({ + 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({ getNode: jest.fn().mockReturnValue(null), + getTriggerNodes: jest.fn().mockReturnValue([]), }); const additionalData = mock(); diff --git a/packages/cli/src/manual-execution.service.ts b/packages/cli/src/manual-execution.service.ts index e48c520a60..98b0b113a6 100644 --- a/packages/cli/src/manual-execution.service.ts +++ b/packages/cli/src/manual-execution.service.ts @@ -9,6 +9,7 @@ import { isTool, rewireGraph, } from 'n8n-core'; +import { MANUAL_TRIGGER_NODE_TYPE } from 'n8n-workflow'; import type { IPinData, IRun, @@ -39,7 +40,15 @@ export class ManualExecutionService { 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