From 98b821bbd89a069acc228730f2dd1eafd7b0fdd4 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:04:21 +0300 Subject: [PATCH] feat: Don't allow multiple active workflows with same form path (#16722) --- ...wActivationConflictingWebhookModal.test.ts | 31 ++++++- ...kflowActivationConflictingWebhookModal.vue | 13 ++- .../src/components/WorkflowActivator.vue | 2 +- .../composables/useWorkflowHelpers.test.ts | 86 +++++++++++++++++++ .../src/composables/useWorkflowHelpers.ts | 38 ++++++-- .../frontend/editor-ui/src/stores/ui.store.ts | 1 + 6 files changed, 160 insertions(+), 11 deletions(-) diff --git a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts index a0bc26342a..4eb726e1b6 100644 --- a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts +++ b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.test.ts @@ -4,6 +4,7 @@ import WorkflowActivationConflictingWebhookModal from '@/components/WorkflowActi import { 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'; vi.mock('@/stores/ui.store', () => { return { @@ -37,12 +38,13 @@ describe('WorkflowActivationConflictingWebhookModal', () => { createTestingPinia(); }); - it('should render modal', async () => { + it('should render webhook conflict modal', async () => { const props = { modalName: WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY, data: { triggerName: 'Trigger in this workflow', workflowName: 'Test Workflow', + triggerType: WEBHOOK_NODE_TYPE, workflowId: '123', webhookPath: 'webhook-path', method: 'GET', @@ -62,4 +64,31 @@ describe('WorkflowActivationConflictingWebhookModal', () => { 'http://webhook-base/webhook-path', ); }); + + it('should render form conflict modal', async () => { + const props = { + modalName: WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY, + data: { + triggerName: 'Trigger in this workflow', + workflowName: 'Test Form', + triggerType: FORM_TRIGGER_NODE_TYPE, + workflowId: '123', + webhookPath: 'form-path', + method: 'GET', + node: 'Node in workflow', + }, + }; + + const wrapper = renderComponent({ props }); + await waitFor(() => { + expect(wrapper.queryByTestId('conflicting-webhook-callout')).toBeInTheDocument(); + }); + + 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-path')).toHaveTextContent( + 'http://webhook-base/form-path', + ); + }); }); diff --git a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue index 987d369e76..a6cda1d9f4 100644 --- a/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue +++ b/packages/frontend/editor-ui/src/components/WorkflowActivationConflictingWebhookModal.vue @@ -6,6 +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'; const modalBus = createEventBus(); const uiStore = useUIStore(); @@ -14,6 +15,7 @@ const rootStore = useRootStore(); const props = defineProps<{ data: { workflowName: string; + triggerType: string; workflowId: string; webhookPath: string; node: string; @@ -26,6 +28,11 @@ const webhookUrl = computed(() => { return rootStore.webhookUrl; }); +const webhookType = computed(() => { + if (data.triggerType === FORM_TRIGGER_NODE_TYPE) return 'form'; + return 'webhook'; +}); + const workflowUrl = computed(() => { return rootStore.urlBaseEditor + 'workflow/' + data.workflowId; }); @@ -39,14 +46,14 @@ const onClick = async () => {