Skip to content

Commit

Permalink
fix(n8n Form Node): Support expressions in completion page (#11781)
Browse files Browse the repository at this point in the history
Co-authored-by: Shireen Missi <shireen@n8n.io>
  • Loading branch information
michael-radency and ShireenMissi authored Nov 20, 2024
1 parent 43aa389 commit 1099167
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 22 deletions.
48 changes: 29 additions & 19 deletions packages/nodes-base/nodes/Form/Form.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down Expand Up @@ -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 }}`,
Expand All @@ -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') {
Expand Down Expand Up @@ -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()];
Expand Down
7 changes: 7 additions & 0 deletions packages/nodes-base/nodes/Form/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
19 changes: 16 additions & 3 deletions packages/nodes-base/nodes/Form/test/Form.node.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -7,14 +8,16 @@ import type {
IWebhookFunctions,
NodeTypeAndVersion,
} from 'n8n-workflow';
import type { Response, Request } from 'express';

import { Form } from '../Form.node';

describe('Form Node', () => {
let form: Form;
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
let mockWebhookFunctions: MockProxy<IWebhookFunctions>;

const formCompletionNodeName = 'Form Completion';
const testExecutionId = 'test_execution_id';
beforeEach(() => {
form = new Form();
mockExecuteFunctions = mock<IExecuteFunctions>();
Expand Down Expand Up @@ -68,7 +71,12 @@ describe('Form Node', () => {
]);
mockExecuteFunctions.getChildNodes.mockReturnValue([]);
mockExecuteFunctions.getInputData.mockReturnValue(inputData);
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>());
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ name: formCompletionNodeName }));
mockExecuteFunctions.getExecutionId.mockReturnValue(testExecutionId);

mockExecuteFunctions.getWorkflowStaticData.mockReturnValue({
[`${testExecutionId}-${formCompletionNodeName}`]: { redirectUrl: 'test' },
});

const result = await form.execute(mockExecuteFunctions);

Expand Down Expand Up @@ -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<INode>());
mockWebhookFunctions.getNode.mockReturnValue(mock<INode>({ name: formCompletionNodeName }));
mockWebhookFunctions.getExecutionId.mockReturnValue(testExecutionId);
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
[`${testExecutionId}-${formCompletionNodeName}`]: { redirectUrl: '' },
});

const result = await form.webhook(mockWebhookFunctions);

Expand Down

0 comments on commit 1099167

Please sign in to comment.