From 39e2d35a710307b9e4d170c3a71eeccc34a08738 Mon Sep 17 00:00:00 2001 From: Marc Littlemore Date: Fri, 4 Apr 2025 14:15:10 +0100 Subject: [PATCH] fix(core): Return correct trigger count for nodes with multiple webhooks (#14300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danny Martini Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ --- packages/cli/src/active-workflow-manager.ts | 15 ++++- .../active-workflow-manager.test.ts | 60 ++++++++++++++++++- .../test/integration/shared/utils/index.ts | 13 +++- .../nodes-base/test/nodes/TriggerHelpers.ts | 8 --- 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/active-workflow-manager.ts b/packages/cli/src/active-workflow-manager.ts index 305ad170c7..83854a85e1 100644 --- a/packages/cli/src/active-workflow-manager.ts +++ b/packages/cli/src/active-workflow-manager.ts @@ -673,10 +673,23 @@ export class ActiveWorkflowManager { const triggerFilter = (nodeType: INodeType) => !!nodeType.trigger && !nodeType.description.name.includes('manualTrigger'); + // Retrieve unique webhooks as some nodes have multiple webhooks + const workflowWebhooks = WebhookHelpers.getWorkflowWebhooks( + workflow, + additionalData, + undefined, + true, + ); + + const uniqueWebhooks = workflowWebhooks.reduce>((acc, webhook) => { + acc.add(webhook.node); + return acc; + }, new Set()); + return ( workflow.queryNodes(triggerFilter).length + workflow.getPollNodes().length + - WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true).length + uniqueWebhooks.size ); } diff --git a/packages/cli/test/integration/active-workflow-manager.test.ts b/packages/cli/test/integration/active-workflow-manager.test.ts index c9df802937..ae1b1d70b4 100644 --- a/packages/cli/test/integration/active-workflow-manager.test.ts +++ b/packages/cli/test/integration/active-workflow-manager.test.ts @@ -1,12 +1,20 @@ import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import { Logger } from 'n8n-core'; +import { FormTrigger } from 'n8n-nodes-base/nodes/Form/FormTrigger.node'; +import { ScheduleTrigger } from 'n8n-nodes-base/nodes/Schedule/ScheduleTrigger.node'; import { NodeApiError, Workflow } from 'n8n-workflow'; -import type { IWebhookData, IWorkflowBase, WorkflowActivateMode } from 'n8n-workflow'; +import type { + IWebhookData, + IWorkflowBase, + WorkflowActivateMode, + INodeTypeData, +} from 'n8n-workflow'; import { ActiveExecutions } from '@/active-executions'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import type { WebhookEntity } from '@/databases/entities/webhook-entity'; +import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { ExecutionService } from '@/executions/execution.service'; import { ExternalHooks } from '@/external-hooks'; import { NodeTypes } from '@/node-types'; @@ -43,7 +51,18 @@ beforeAll(async () => { activeWorkflowManager = Container.get(ActiveWorkflowManager); - await utils.initNodeTypes(); + const nodes: INodeTypeData = { + 'n8n-nodes-base.scheduleTrigger': { + type: new ScheduleTrigger(), + sourcePath: '', + }, + 'n8n-nodes-base.formTrigger': { + type: new FormTrigger(), + sourcePath: '', + }, + }; + + await utils.initNodeTypes(nodes); const owner = await createOwner(); createActiveWorkflow = async () => await createWorkflow({ active: true }, owner); @@ -136,6 +155,43 @@ describe('add()', () => { }, ); }); + + test('should count workflow triggers correctly when node has multiple webhooks', async () => { + const workflowRepositoryInstance = Container.get(WorkflowRepository); + const updateWorkflowTriggerCountSpy = jest.spyOn( + workflowRepositoryInstance, + 'updateWorkflowTriggerCount', + ); + await activeWorkflowManager.init(); + + // Mock all of the webhooks + const mockWebhooks: IWebhookData[] = [ + mock({ node: 'Form Trigger', httpMethod: 'GET', path: '/webhook-path' }), + mock({ node: 'Form Trigger', httpMethod: 'POST', path: '/webhook-path' }), + ]; + webhookService.getNodeWebhooks.mockReturnValue(mockWebhooks); + webhookService.createWebhook.mockReturnValue( + mock({ webhookPath: '/webhook-path' }), + ); + + // Create a workflow which has a form trigger + const dbWorkflow = await createWorkflow({ + nodes: [ + { + id: 'uuid-1', + parameters: { path: 'test-webhook-path', options: {} }, + name: 'Form Trigger', + type: 'n8n-nodes-base.formTrigger', + typeVersion: 1, + position: [500, 300], + }, + ], + }); + + await activeWorkflowManager.add(dbWorkflow.id, 'activate'); + + expect(updateWorkflowTriggerCountSpy).toHaveBeenCalledWith(dbWorkflow.id, 1); + }); }); describe('removeAll()', () => { diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index f43b102bd3..7c9eebf657 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -9,6 +9,7 @@ import { import { Ftp } from 'n8n-nodes-base/credentials/Ftp.credentials'; import { GithubApi } from 'n8n-nodes-base/credentials/GithubApi.credentials'; import { Cron } from 'n8n-nodes-base/nodes/Cron/Cron.node'; +import { FormTrigger } from 'n8n-nodes-base/nodes/Form/FormTrigger.node'; import { ScheduleTrigger } from 'n8n-nodes-base/nodes/Schedule/ScheduleTrigger.node'; import { Set } from 'n8n-nodes-base/nodes/Set/Set.node'; import { Start } from 'n8n-nodes-base/nodes/Start/Start.node'; @@ -67,9 +68,8 @@ export async function initCredentialsTypes(): Promise { /** * Initialize node types. */ -export async function initNodeTypes() { - ScheduleTrigger.prototype.trigger = async () => ({}); - const nodes: INodeTypeData = { +export async function initNodeTypes(customNodes?: INodeTypeData) { + const defaultNodes: INodeTypeData = { 'n8n-nodes-base.start': { type: new Start(), sourcePath: '', @@ -86,7 +86,14 @@ export async function initNodeTypes() { type: new ScheduleTrigger(), sourcePath: '', }, + 'n8n-nodes-base.formTrigger': { + type: new FormTrigger(), + sourcePath: '', + }, }; + + ScheduleTrigger.prototype.trigger = async () => ({}); + const nodes = customNodes ?? defaultNodes; const loader = mock(); loader.getNode.mockImplementation((nodeType) => { const node = nodes[`n8n-nodes-base.${nodeType}`]; diff --git a/packages/nodes-base/test/nodes/TriggerHelpers.ts b/packages/nodes-base/test/nodes/TriggerHelpers.ts index 32cfabf686..a0a15942f7 100644 --- a/packages/nodes-base/test/nodes/TriggerHelpers.ts +++ b/packages/nodes-base/test/nodes/TriggerHelpers.ts @@ -47,14 +47,6 @@ function getNodeVersion(Trigger: new () => VersionedNodeType, version?: number) return instance.nodeVersions[version ?? instance.currentVersion]; } -export async function testVersionedTriggerNode( - Trigger: new () => VersionedNodeType, - version?: number, - options: TestTriggerNodeOptions = {}, -) { - return await testTriggerNode(getNodeVersion(Trigger, version), options); -} - export async function testTriggerNode( Trigger: (new () => INodeType) | INodeType, options: TestTriggerNodeOptions = {},