fix(Wait Node): Fix for hasNextPage in waiting forms (#12636)

This commit is contained in:
Michael Kret 2025-01-20 11:41:50 +02:00 committed by GitHub
parent 847aa1295d
commit 652b8d170b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 9 deletions

View file

@ -79,18 +79,24 @@ export abstract class NodeExecutionContext implements Omit<FunctionsBase, 'getCr
return this.workflow.getStaticData(type, this.node);
}
getChildNodes(nodeName: string) {
getChildNodes(nodeName: string, options?: { includeNodeParameters?: boolean }) {
const output: NodeTypeAndVersion[] = [];
const nodeNames = this.workflow.getChildNodes(nodeName);
for (const n of nodeNames) {
const node = this.workflow.nodes[n];
output.push({
const entry: NodeTypeAndVersion = {
name: node.name,
type: node.type,
typeVersion: node.typeVersion,
disabled: node.disabled ?? false,
});
};
if (options?.includeNodeParameters) {
entry.parameters = node.parameters;
}
output.push(entry);
}
return output;
}

View file

@ -5,9 +5,16 @@ import type {
INode,
IWebhookFunctions,
MultiPartFormData,
NodeTypeAndVersion,
} from 'n8n-workflow';
import { formWebhook, prepareFormData, prepareFormReturnItem, resolveRawData } from '../utils';
import {
formWebhook,
prepareFormData,
prepareFormReturnItem,
resolveRawData,
isFormConnected,
} from '../utils';
describe('FormTrigger, formWebhook', () => {
beforeEach(() => {
@ -721,3 +728,36 @@ describe('resolveRawData', () => {
);
});
});
describe('FormTrigger, isFormConnected', () => {
it('should return false if Wait node is connected but resume parameter is not form', async () => {
const result = isFormConnected([
mock<NodeTypeAndVersion>({
type: 'n8n-nodes-base.wait',
parameters: {
resume: 'timeInterval',
},
}),
]);
expect(result).toBe(false);
});
it('should return true if Wait node is connected and resume parameter is form', async () => {
const result = isFormConnected([
mock<NodeTypeAndVersion>({
type: 'n8n-nodes-base.wait',
parameters: {
resume: 'form',
},
}),
]);
expect(result).toBe(true);
});
it('should return true if Form node is connected', async () => {
const result = isFormConnected([
mock<NodeTypeAndVersion>({
type: 'n8n-nodes-base.form',
}),
]);
expect(result).toBe(true);
});
});

View file

@ -326,6 +326,13 @@ export function renderForm({
res.render('form-trigger', data);
}
export const isFormConnected = (nodes: NodeTypeAndVersion[]) => {
return nodes.some(
(n) =>
n.type === FORM_NODE_TYPE || (n.type === WAIT_NODE_TYPE && n.parameters?.resume === 'form'),
);
};
export async function formWebhook(
context: IWebhookFunctions,
authProperty = FORM_TRIGGER_AUTHENTICATION_PROPERTY,
@ -403,10 +410,10 @@ export async function formWebhook(
}
if (!redirectUrl && node.type !== FORM_TRIGGER_NODE_TYPE) {
const connectedNodes = context.getChildNodes(context.getNode().name);
const hasNextPage = connectedNodes.some(
(n) => n.type === FORM_NODE_TYPE || n.type === WAIT_NODE_TYPE,
);
const connectedNodes = context.getChildNodes(context.getNode().name, {
includeNodeParameters: true,
});
const hasNextPage = isFormConnected(connectedNodes);
if (hasNextPage) {
redirectUrl = context.evaluateExpression('{{ $execution.resumeFormUrl }}') as string;

View file

@ -852,6 +852,7 @@ export type NodeTypeAndVersion = {
type: string;
typeVersion: number;
disabled: boolean;
parameters?: INodeParameters;
};
export interface FunctionsBase {
@ -869,7 +870,10 @@ export interface FunctionsBase {
getRestApiUrl(): string;
getInstanceBaseUrl(): string;
getInstanceId(): string;
getChildNodes(nodeName: string): NodeTypeAndVersion[];
getChildNodes(
nodeName: string,
options?: { includeNodeParameters?: boolean },
): NodeTypeAndVersion[];
getParentNodes(nodeName: string): NodeTypeAndVersion[];
getKnownNodeTypes(): IDataObject;
getMode?: () => WorkflowExecuteMode;