From 847a5d822f77a3362f382c17070d49ccc6e3d999 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:46:17 +0300 Subject: [PATCH] fix: Extend deduplication check to all webhook-based triggers and chat trigger (#18044) --- ...wActivationConflictingWebhookModal.test.ts | 73 ++++++++++++++++++- ...kflowActivationConflictingWebhookModal.vue | 44 ++++++++--- .../composables/useWorkflowHelpers.test.ts | 72 +++++++++++++++++- .../src/composables/useWorkflowHelpers.ts | 12 ++- 4 files changed, 185 insertions(+), 16 deletions(-) diff --git a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts index 4eb726e1b6..45c84c66ac 100644 --- a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts +++ b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts @@ -1,10 +1,13 @@ import { createTestingPinia } from '@pinia/testing'; import { createComponentRenderer } from '@/__tests__/render'; import WorkflowActivationConflictingWebhookModal from '@/components/WorkflowActivationConflictingWebhookModal.vue'; -import { WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY } from '@/constants'; +import { + SLACK_TRIGGER_NODE_TYPE, + WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY, +} from '@/constants'; import { waitFor } from '@testing-library/vue'; -import { FORM_TRIGGER_NODE_TYPE, WEBHOOK_NODE_TYPE } from 'n8n-workflow'; +import { CHAT_TRIGGER_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, WEBHOOK_NODE_TYPE } from 'n8n-workflow'; vi.mock('@/stores/ui.store', () => { return { @@ -60,6 +63,9 @@ describe('WorkflowActivationConflictingWebhookModal', () => { expect(wrapper.getByTestId('conflicting-webhook-callout')).toHaveTextContent( "A webhook trigger 'Node in workflow' in the workflow 'Test Workflow' uses a conflicting URL path, so this workflow cannot be activated", ); + expect(wrapper.getByTestId('conflicting-webhook-suggestion')).toHaveTextContent( + 'and activate this one, or adjust the following URL path in either workflow:', + ); expect(wrapper.getByTestId('conflicting-webhook-path')).toHaveTextContent( 'http://webhook-base/webhook-path', ); @@ -87,8 +93,71 @@ describe('WorkflowActivationConflictingWebhookModal', () => { expect(wrapper.getByTestId('conflicting-webhook-callout')).toHaveTextContent( "A form trigger 'Node in workflow' in the workflow 'Test Form' uses a conflicting URL path, so this workflow cannot be activated", ); + expect(wrapper.getByTestId('conflicting-webhook-suggestion')).toHaveTextContent( + 'and activate this one, or adjust the following URL path in either workflow:', + ); expect(wrapper.getByTestId('conflicting-webhook-path')).toHaveTextContent( 'http://webhook-base/form-path', ); }); + + it('should render chat conflict modal', async () => { + const props = { + modalName: WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY, + data: { + triggerName: 'Chat in this workflow', + workflowName: 'Test Chat', + triggerType: CHAT_TRIGGER_NODE_TYPE, + workflowId: '123', + webhookPath: '123/chat', + method: 'POST', + node: 'Chat trigger', + }, + }; + + const wrapper = renderComponent({ props }); + await waitFor(() => { + expect(wrapper.queryByTestId('conflicting-webhook-callout')).toBeInTheDocument(); + }); + + expect(wrapper.getByTestId('conflicting-webhook-callout')).toHaveTextContent( + "A chat trigger 'Chat trigger' in the workflow 'Test Chat' uses a conflicting URL path, so this workflow cannot be activated", + ); + expect(wrapper.getByTestId('conflicting-webhook-suggestion')).toHaveTextContent( + 'and activate this one, or insert a new Chat Trigger node in either workflow:', + ); + expect(wrapper.getByTestId('conflicting-webhook-path')).toHaveTextContent( + 'http://webhook-base/123/chat', + ); + }); + + it('should render trigger conflict modal', async () => { + const props = { + modalName: WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY, + data: { + triggerName: 'Slack in this workflow', + workflowName: 'Test Trigger', + triggerType: SLACK_TRIGGER_NODE_TYPE, + workflowId: '123', + webhookPath: '123/webhook', + method: 'POST', + node: 'Slack trigger', + }, + }; + + const wrapper = renderComponent({ props }); + await waitFor(() => { + expect(wrapper.queryByTestId('conflicting-webhook-callout')).toBeInTheDocument(); + }); + + expect(wrapper.getByTestId('conflicting-webhook-callout')).toHaveTextContent( + "A trigger 'Slack trigger' in the workflow 'Test Trigger' uses a conflicting URL path, so this workflow cannot be activated", + ); + expect(wrapper.getByTestId('conflicting-webhook-suggestion')).toHaveTextContent( + 'and activate this one, or insert a new trigger node of the same type in either workflow:', + ); + expect(wrapper.getByTestId('conflicting-webhook-path')).toHaveTextContent( + 'http://webhook-base/123/webhook', + ); + }); }); diff --git a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue index a6cda1d9f4..f3735b478e 100644 --- a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue +++ b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue @@ -6,7 +6,7 @@ import { useUIStore } from '@/stores/ui.store'; import { useRootStore } from '@n8n/stores/useRootStore'; import { computed } from 'vue'; -import { FORM_TRIGGER_NODE_TYPE } from 'n8n-workflow'; +import { CHAT_TRIGGER_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, WEBHOOK_NODE_TYPE } from 'n8n-workflow'; const modalBus = createEventBus(); const uiStore = useUIStore(); @@ -28,9 +28,33 @@ const webhookUrl = computed(() => { return rootStore.webhookUrl; }); -const webhookType = computed(() => { - if (data.triggerType === FORM_TRIGGER_NODE_TYPE) return 'form'; - return 'webhook'; +const webhookTypeUi = computed((): { title: string; callout: string; suggestion: string } => { + const suggestionBase = 'and activate this one, or '; + + if (data.triggerType === FORM_TRIGGER_NODE_TYPE) + return { + title: 'Form', + callout: 'form trigger', + suggestion: suggestionBase + 'adjust the following URL path in either workflow:', + }; + if (data.triggerType === CHAT_TRIGGER_NODE_TYPE) + return { + title: 'Chat', + callout: 'chat trigger', + suggestion: suggestionBase + 'insert a new Chat Trigger node in either workflow:', + }; + if (data.triggerType === WEBHOOK_NODE_TYPE) + return { + title: 'Webhook', + callout: 'webhook trigger', + suggestion: suggestionBase + 'adjust the following URL path in either workflow:', + }; + + return { + title: 'Trigger', + callout: 'trigger', + suggestion: suggestionBase + 'insert a new trigger node of the same type in either workflow:', + }; }); const workflowUrl = computed(() => { @@ -46,21 +70,21 @@ const onClick = async () => {