From 10991675fe2e6913e8f03d565b670257941f18e5 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:52:11 +0200 Subject: [PATCH] fix(n8n Form Node): Support expressions in completion page (#11781) Co-authored-by: Shireen Missi --- packages/nodes-base/nodes/Form/Form.node.ts | 48 +++++++++++-------- packages/nodes-base/nodes/Form/interfaces.ts | 7 +++ .../nodes/Form/test/Form.node.test.ts | 19 ++++++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/packages/nodes-base/nodes/Form/Form.node.ts b/packages/nodes-base/nodes/Form/Form.node.ts index a064ce016a4c3..1aa55daf89165 100644 --- a/packages/nodes-base/nodes/Form/Form.node.ts +++ b/packages/nodes-base/nodes/Form/Form.node.ts @@ -20,6 +20,7 @@ import { import { formDescription, formFields, formTitle } from '../Form/common.descriptions'; import { prepareFormReturnItem, renderForm, resolveRawData } from '../Form/utils'; +import { type CompletionPageConfig } from './interfaces'; const pageProperties = updateDisplayOptions( { @@ -267,22 +268,17 @@ export class Form extends Node { const method = context.getRequestObject().method; if (operation === 'completion') { - const respondWith = context.getNodeParameter('respondWith', '') as string; - - if (respondWith === 'redirect') { - const redirectUrl = context.getNodeParameter('redirectUrl', '') as string; - res.redirect(redirectUrl); - return { - noWebhookResponse: true, - }; + const staticData = context.getWorkflowStaticData('node'); + const id = `${context.getExecutionId()}-${context.getNode().name}`; + const config = staticData?.[id] as CompletionPageConfig; + delete staticData[id]; + + if (config.redirectUrl) { + res.redirect(config.redirectUrl); + return { noWebhookResponse: true }; } - const completionTitle = context.getNodeParameter('completionTitle', '') as string; - const completionMessage = context.getNodeParameter('completionMessage', '') as string; - const options = context.getNodeParameter('options', {}) as { - formTitle: string; - }; - let title = options.formTitle; + let title = config.pageTitle; if (!title) { title = context.evaluateExpression( `{{ $('${trigger?.name}').params.formTitle }}`, @@ -293,15 +289,13 @@ export class Form extends Node { ) as boolean; res.render('form-trigger-completion', { - title: completionTitle, - message: completionMessage, + title: config.completionTitle, + message: config.completionMessage, formTitle: title, appendAttribution, }); - return { - noWebhookResponse: true, - }; + return { noWebhookResponse: true }; } if (method === 'GET') { @@ -415,6 +409,22 @@ export class Form extends Node { if (operation !== 'completion') { const waitTill = new Date(WAIT_TIME_UNLIMITED); await context.putExecutionToWait(waitTill); + } else { + const staticData = context.getWorkflowStaticData('node'); + const completionTitle = context.getNodeParameter('completionTitle', 0, '') as string; + const completionMessage = context.getNodeParameter('completionMessage', 0, '') as string; + const redirectUrl = context.getNodeParameter('redirectUrl', 0, '') as string; + const options = context.getNodeParameter('options', 0, {}) as { formTitle: string }; + const id = `${context.getExecutionId()}-${context.getNode().name}`; + + const config: CompletionPageConfig = { + completionTitle, + completionMessage, + redirectUrl, + pageTitle: options.formTitle, + }; + + staticData[id] = config; } return [context.getInputData()]; diff --git a/packages/nodes-base/nodes/Form/interfaces.ts b/packages/nodes-base/nodes/Form/interfaces.ts index 26b5cf567adfd..ce2196949a437 100644 --- a/packages/nodes-base/nodes/Form/interfaces.ts +++ b/packages/nodes-base/nodes/Form/interfaces.ts @@ -31,4 +31,11 @@ export type FormTriggerData = { buttonLabel?: string; }; +export type CompletionPageConfig = { + pageTitle?: string; + completionMessage?: string; + completionTitle?: string; + redirectUrl?: string; +}; + export const FORM_TRIGGER_AUTHENTICATION_PROPERTY = 'authentication'; diff --git a/packages/nodes-base/nodes/Form/test/Form.node.test.ts b/packages/nodes-base/nodes/Form/test/Form.node.test.ts index 616e3cf8dbd62..ecd24d8ca54b3 100644 --- a/packages/nodes-base/nodes/Form/test/Form.node.test.ts +++ b/packages/nodes-base/nodes/Form/test/Form.node.test.ts @@ -1,3 +1,4 @@ +import type { Response, Request } from 'express'; import type { MockProxy } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended'; import type { @@ -7,7 +8,7 @@ import type { IWebhookFunctions, NodeTypeAndVersion, } from 'n8n-workflow'; -import type { Response, Request } from 'express'; + import { Form } from '../Form.node'; describe('Form Node', () => { @@ -15,6 +16,8 @@ describe('Form Node', () => { let mockExecuteFunctions: MockProxy; let mockWebhookFunctions: MockProxy; + const formCompletionNodeName = 'Form Completion'; + const testExecutionId = 'test_execution_id'; beforeEach(() => { form = new Form(); mockExecuteFunctions = mock(); @@ -68,7 +71,12 @@ describe('Form Node', () => { ]); mockExecuteFunctions.getChildNodes.mockReturnValue([]); mockExecuteFunctions.getInputData.mockReturnValue(inputData); - mockExecuteFunctions.getNode.mockReturnValue(mock()); + mockExecuteFunctions.getNode.mockReturnValue(mock({ name: formCompletionNodeName })); + mockExecuteFunctions.getExecutionId.mockReturnValue(testExecutionId); + + mockExecuteFunctions.getWorkflowStaticData.mockReturnValue({ + [`${testExecutionId}-${formCompletionNodeName}`]: { redirectUrl: 'test' }, + }); const result = await form.execute(mockExecuteFunctions); @@ -172,11 +180,16 @@ describe('Form Node', () => { const mockResponseObject = { render: jest.fn(), + redirect: jest.fn(), }; mockWebhookFunctions.getResponseObject.mockReturnValue( mockResponseObject as unknown as Response, ); - mockWebhookFunctions.getNode.mockReturnValue(mock()); + mockWebhookFunctions.getNode.mockReturnValue(mock({ name: formCompletionNodeName })); + mockWebhookFunctions.getExecutionId.mockReturnValue(testExecutionId); + mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({ + [`${testExecutionId}-${formCompletionNodeName}`]: { redirectUrl: '' }, + }); const result = await form.webhook(mockWebhookFunctions);