| | import type { IExecutionResponse } from '@n8n/db'; |
| | import { Service } from '@n8n/di'; |
| | import type express from 'express'; |
| | import type { IRunData } from 'n8n-workflow'; |
| | import { |
| | FORM_NODE_TYPE, |
| | WAIT_NODE_TYPE, |
| | WAITING_FORMS_EXECUTION_STATUS, |
| | Workflow, |
| | } from 'n8n-workflow'; |
| |
|
| | import { ConflictError } from '@/errors/response-errors/conflict.error'; |
| | import { NotFoundError } from '@/errors/response-errors/not-found.error'; |
| | import { WaitingWebhooks } from '@/webhooks/waiting-webhooks'; |
| |
|
| | import type { IWebhookResponseCallbackData, WaitingWebhookRequest } from './webhook.types'; |
| |
|
| | @Service() |
| | export class WaitingForms extends WaitingWebhooks { |
| | protected override includeForms = true; |
| |
|
| | protected override logReceivedWebhook(method: string, executionId: string) { |
| | this.logger.debug(`Received waiting-form "${method}" for execution "${executionId}"`); |
| | } |
| |
|
| | protected disableNode(execution: IExecutionResponse, method?: string) { |
| | if (method === 'POST') { |
| | execution.data.executionData!.nodeExecutionStack[0].node.disabled = true; |
| | } |
| | } |
| |
|
| | private getWorkflow(execution: IExecutionResponse) { |
| | const { workflowData } = execution; |
| | return new Workflow({ |
| | id: workflowData.id, |
| | name: workflowData.name, |
| | nodes: workflowData.nodes, |
| | connections: workflowData.connections, |
| | active: workflowData.active, |
| | nodeTypes: this.nodeTypes, |
| | staticData: workflowData.staticData, |
| | settings: workflowData.settings, |
| | }); |
| | } |
| |
|
| | findCompletionPage(workflow: Workflow, runData: IRunData, lastNodeExecuted: string) { |
| | const parentNodes = workflow.getParentNodes(lastNodeExecuted); |
| | const lastNode = workflow.nodes[lastNodeExecuted]; |
| |
|
| | if ( |
| | !lastNode.disabled && |
| | lastNode.type === FORM_NODE_TYPE && |
| | lastNode.parameters.operation === 'completion' |
| | ) { |
| | return lastNodeExecuted; |
| | } else { |
| | return parentNodes.reverse().find((nodeName) => { |
| | const node = workflow.nodes[nodeName]; |
| | return ( |
| | !node.disabled && |
| | node.type === FORM_NODE_TYPE && |
| | node.parameters.operation === 'completion' && |
| | runData[nodeName] |
| | ); |
| | }); |
| | } |
| | } |
| |
|
| | async executeWebhook( |
| | req: WaitingWebhookRequest, |
| | res: express.Response, |
| | ): Promise<IWebhookResponseCallbackData> { |
| | const { path: executionId, suffix } = req.params; |
| |
|
| | this.logReceivedWebhook(req.method, executionId); |
| |
|
| | |
| | req.params = {} as WaitingWebhookRequest['params']; |
| |
|
| | const execution = await this.getExecution(executionId); |
| |
|
| | if (suffix === WAITING_FORMS_EXECUTION_STATUS) { |
| | let status: string = execution?.status ?? 'null'; |
| | const { node } = execution?.data.executionData?.nodeExecutionStack[0] ?? {}; |
| |
|
| | if (node && status === 'waiting') { |
| | if (node.type === FORM_NODE_TYPE) { |
| | status = 'form-waiting'; |
| | } |
| | if (node.type === WAIT_NODE_TYPE && node.parameters.resume === 'form') { |
| | status = 'form-waiting'; |
| | } |
| | } |
| | res.send(status); |
| | return { noWebhookResponse: true }; |
| | } |
| |
|
| | if (!execution) { |
| | throw new NotFoundError(`The execution "${executionId}" does not exist.`); |
| | } |
| |
|
| | if (execution.data.resultData.error) { |
| | const message = `The execution "${executionId}" has finished with error.`; |
| | this.logger.debug(message, { error: execution.data.resultData.error }); |
| | throw new ConflictError(message); |
| | } |
| |
|
| | if (execution.status === 'running') { |
| | return { noWebhookResponse: true }; |
| | } |
| |
|
| | let lastNodeExecuted = execution.data.resultData.lastNodeExecuted as string; |
| |
|
| | if (execution.finished) { |
| | |
| | |
| | const workflow = this.getWorkflow(execution); |
| |
|
| | const completionPage = this.findCompletionPage( |
| | workflow, |
| | execution.data.resultData.runData, |
| | lastNodeExecuted, |
| | ); |
| |
|
| | if (!completionPage) { |
| | res.render('form-trigger-completion', { |
| | title: 'Form Submitted', |
| | message: 'Your response has been recorded', |
| | formTitle: 'Form Submitted', |
| | }); |
| |
|
| | return { |
| | noWebhookResponse: true, |
| | }; |
| | } else { |
| | lastNodeExecuted = completionPage; |
| | } |
| | } |
| |
|
| | return await this.getWebhookExecutionData({ |
| | execution, |
| | req, |
| | res, |
| | lastNodeExecuted, |
| | executionId, |
| | suffix, |
| | }); |
| | } |
| | } |
| |
|