From 09d3f870833206ca5fea1a62ad98d32d99b3fc7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 28 Apr 2023 16:58:15 +0200 Subject: [PATCH 01/16] fix(core): Use AbortController to Notify nodes to abort execution --- packages/cli/src/ActiveExecutions.ts | 14 ++++++++++++++ packages/cli/src/Interfaces.ts | 1 + .../cli/src/WorkflowExecuteAdditionalData.ts | 1 + packages/cli/src/WorkflowRunner.ts | 18 ++++++++++++++++-- packages/cli/src/WorkflowRunnerProcess.ts | 17 ++++++++++++++--- packages/cli/src/commands/worker.ts | 1 + packages/core/src/NodeExecuteFunctions.ts | 17 +++++++++++++++++ packages/core/src/WorkflowExecute.ts | 2 ++ packages/workflow/src/Interfaces.ts | 3 +++ packages/workflow/src/RoutingNode.ts | 3 +++ packages/workflow/src/Workflow.ts | 4 ++++ 11 files changed, 76 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 9d606aab7d57d..bc0343d06e246 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -119,6 +119,17 @@ export class ActiveExecutions { this.activeExecutions[executionId].responsePromise = responsePromise; } + attachAbortController(executionId: string, abortController: AbortController): void { + const execution = this.activeExecutions[executionId]; + if (execution === undefined) { + throw new Error( + `No active execution with id "${executionId}" got found to attach to workflowExecution to!`, + ); + } + + execution.abortController = abortController; + } + resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void { if (this.activeExecutions[executionId] === undefined) { return; @@ -175,6 +186,9 @@ export class ActiveExecutions { }, 1); } } else { + // Notify nodes to abort all operations + this.activeExecutions[executionId].abortController?.abort(); + // Workflow is running in current process this.activeExecutions[executionId].workflowExecution!.cancel(); } diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 1caadbee578e9..d16c40183e469 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -209,6 +209,7 @@ export interface IExecutingWorkflowData { postExecutePromises: Array>; responsePromise?: IDeferredPromise; workflowExecution?: PCancelable; + abortController?: AbortController; status: ExecutionStatus; } diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 556b92cd237d5..f6cff22a2cbff 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -878,6 +878,7 @@ async function executeWorkflow( const runExecutionData = runData.executionData as IRunExecutionData; + // TODO: setup abortController // Execute the workflow const workflowExecute = new WorkflowExecute( additionalDataIntegrated, diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 995033805eaeb..d33e98b6d8eab 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -347,6 +347,7 @@ export class WorkflowRunner { sessionId: data.sessionId, }); + const abortController = new AbortController(); if (data.executionData !== undefined) { this.logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, { executionId, @@ -355,6 +356,7 @@ export class WorkflowRunner { additionalData, data.executionMode, data.executionData, + abortController, ); workflowExecution = workflowExecute.processRunExecutionData(workflow); } else if ( @@ -370,7 +372,12 @@ export class WorkflowRunner { const startNode = WorkflowHelpers.getExecutionStartNode(data, workflow); // Can execute without webhook so go on - const workflowExecute = new WorkflowExecute(additionalData, data.executionMode); + const workflowExecute = new WorkflowExecute( + additionalData, + data.executionMode, + undefined, + abortController, + ); workflowExecution = workflowExecute.run( workflow, startNode, @@ -380,7 +387,12 @@ export class WorkflowRunner { } else { this.logger.debug(`Execution ID ${executionId} is a partial execution.`, { executionId }); // Execute only the nodes between start and destination nodes - const workflowExecute = new WorkflowExecute(additionalData, data.executionMode); + const workflowExecute = new WorkflowExecute( + additionalData, + data.executionMode, + undefined, + abortController, + ); workflowExecution = workflowExecute.runPartialWorkflow( workflow, data.runData, @@ -390,6 +402,7 @@ export class WorkflowRunner { ); } + this.activeExecutions.attachAbortController(executionId, abortController); this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); if (workflowTimeout > 0) { @@ -640,6 +653,7 @@ export class WorkflowRunner { // So we're just preventing crashes here. }); + // TODO: setup AbortController this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); return executionId; } diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 18992db4ac7f8..e568b1c9fe91a 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -269,12 +269,13 @@ class WorkflowRunnerProcess { return returnData!.data!.main; }; - + const abortController = new AbortController(); if (this.data.executionData !== undefined) { this.workflowExecute = new WorkflowExecute( additionalData, this.data.executionMode, this.data.executionData, + abortController, ); return this.workflowExecute.processRunExecutionData(this.workflow); } @@ -288,7 +289,12 @@ class WorkflowRunnerProcess { const startNode = WorkflowHelpers.getExecutionStartNode(this.data, this.workflow); // Can execute without webhook so go on - this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode); + this.workflowExecute = new WorkflowExecute( + additionalData, + this.data.executionMode, + undefined, + abortController, + ); return this.workflowExecute.run( this.workflow, startNode, @@ -297,7 +303,12 @@ class WorkflowRunnerProcess { ); } // Execute only the nodes between start and destination nodes - this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode); + this.workflowExecute = new WorkflowExecute( + additionalData, + this.data.executionMode, + undefined, + abortController, + ); return this.workflowExecute.runPartialWorkflow( this.workflow, this.data.runData, diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index cc18c1cda33a2..f53bb5aa03330 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -220,6 +220,7 @@ export class Worker extends BaseCommand { this.logger.debug(`Queued worker execution status for ${executionId} is "${status}"`); }; + // TODO: setup AbortController let workflowExecute: WorkflowExecute; let workflowRun: PCancelable; if (fullExecutionData.data !== undefined) { diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index c1ae3ea95b285..08017cb224ad0 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2508,6 +2508,19 @@ const getCommonWorkflowFunctions = ( prepareOutputData: async (outputData) => [outputData], }); +const executionCancellationFunctions = ( + abortController?: AbortController, +): Pick => ({ + onExecutionCancellation: (cleanup, reject) => { + const handler = async () => { + abortController?.signal?.removeEventListener('abort', handler); + await cleanup(); + reject?.(new Error('Execution cancelled')); + }; + abortController?.signal?.addEventListener('abort', handler); + }, +}); + const getRequestHelperFunctions = ( workflow: Workflow, node: INode, @@ -3087,10 +3100,12 @@ export function getExecuteFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, + abortController?: AbortController, ): IExecuteFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node) => { return { ...getCommonWorkflowFunctions(workflow, node, additionalData), + ...executionCancellationFunctions(abortController), getMode: () => mode, getCredentials: async (type, itemIndex) => getCredentials( @@ -3512,10 +3527,12 @@ export function getExecuteSingleFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, + abortController?: AbortController, ): IExecuteSingleFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => { return { ...getCommonWorkflowFunctions(workflow, node, additionalData), + ...executionCancellationFunctions(abortController), continueOnFail: () => continueOnFail(node), evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => { evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex; diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index b0490cd63f5db..5fd75a7431271 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -56,6 +56,7 @@ export class WorkflowExecute { additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData, + private abortController?: AbortController, ) { this.additionalData = additionalData; this.mode = mode; @@ -1052,6 +1053,7 @@ export class WorkflowExecute { this.additionalData, NodeExecuteFunctions, this.mode, + this.abortController, ); nodeSuccessData = runNodeData.data; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 06b634083496a..1ea0322553498 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -422,6 +422,7 @@ export interface IGetExecuteFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, + abortController?: AbortController, ): IExecuteFunctions; } @@ -437,6 +438,7 @@ export interface IGetExecuteSingleFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, + abortController?: AbortController, ): IExecuteSingleFunctions; } @@ -776,6 +778,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { getExecuteData(): IExecuteData; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getInputSourceData(inputIndex?: number, inputName?: string): ISourceData; + onExecutionCancellation(cleanup: () => Promise, reject: (reason: Error) => void): void; }; // TODO: Create later own type only for Config-Nodes diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index f8394943f293d..b25f36c76e42e 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -79,6 +79,7 @@ export class RoutingNode { executeData: IExecuteData, nodeExecuteFunctions: INodeExecuteFunctions, credentialsDecrypted?: ICredentialsDecrypted, + abortController?: AbortController, ): Promise { const items = inputData.main[0] as INodeExecutionData[]; const returnData: INodeExecutionData[] = []; @@ -99,6 +100,7 @@ export class RoutingNode { this.additionalData, executeData, this.mode, + abortController, ); let credentials: ICredentialDataDecryptedObject | undefined; @@ -136,6 +138,7 @@ export class RoutingNode { this.additionalData, executeData, this.mode, + abortController, ); const requestData: DeclarativeRestApiSettings.ResultOptions = { options: { diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 53eade2492af5..abd47215a38eb 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -1216,6 +1216,7 @@ export class Workflow { additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode, + abortController?: AbortController, ): Promise { const { node } = executionData; let inputData = executionData.data; @@ -1303,6 +1304,7 @@ export class Workflow { additionalData, executionData, mode, + abortController, ); const data = nodeType instanceof Node @@ -1385,6 +1387,8 @@ export class Workflow { nodeType, executionData, nodeExecuteFunctions, + undefined, + abortController, ), }; } From d276b4c1c0c69f13f4c4e5b9f7b310da581ff107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 10 Nov 2023 16:59:18 +0100 Subject: [PATCH 02/16] allow cancelling execution with wait nodes (with timeout less than 65 seconds) --- packages/nodes-base/nodes/Wait/Wait.node.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Wait/Wait.node.ts b/packages/nodes-base/nodes/Wait/Wait.node.ts index 85604f5b8c02f..609d902779902 100644 --- a/packages/nodes-base/nodes/Wait/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait/Wait.node.ts @@ -347,10 +347,9 @@ export class Wait extends Webhook { if (waitValue < 65000) { // If wait time is shorter than 65 seconds leave execution active because // we just check the database every 60 seconds. - return new Promise((resolve, _reject) => { - setTimeout(() => { - resolve([context.getInputData()]); - }, waitValue); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => resolve([context.getInputData()]), waitValue); + context.onExecutionCancellation(async () => clearTimeout(timer), reject); }); } From bd28ab2b47bf6dda7e768366e80b6458af972ef4 Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Mon, 13 Nov 2023 14:55:36 +0100 Subject: [PATCH 03/16] Expose `getExecutionCancelSignal` getter to get AbortSignal Signed-off-by: Oleg Ivaniv --- packages/core/src/NodeExecuteFunctions.ts | 5 ++++- packages/workflow/src/Interfaces.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 08017cb224ad0..a4fd3c156a19f 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2510,7 +2510,10 @@ const getCommonWorkflowFunctions = ( const executionCancellationFunctions = ( abortController?: AbortController, -): Pick => ({ +): Pick => ({ + getExecutionCancelSignal: () => { + return abortController?.signal; + }, onExecutionCancellation: (cleanup, reject) => { const handler = async () => { abortController?.signal?.removeEventListener('abort', handler); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 1ea0322553498..2596c0fa6dd28 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -778,6 +778,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { getExecuteData(): IExecuteData; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getInputSourceData(inputIndex?: number, inputName?: string): ISourceData; + getExecutionCancelSignal(): AbortSignal | undefined; onExecutionCancellation(cleanup: () => Promise, reject: (reason: Error) => void): void; }; From df6ae1e4786f210d82eafd5621ed538554f04711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 14 Nov 2023 19:41:07 +0100 Subject: [PATCH 04/16] move executionCanceled flag to additionalData --- packages/core/src/WorkflowExecute.ts | 12 ++++++------ packages/workflow/src/Interfaces.ts | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index 5fd75a7431271..87dc4296cc438 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -831,11 +831,11 @@ export class WorkflowExecute { let closeFunction: Promise | undefined; return new PCancelable(async (resolve, reject, onCancel) => { - let gotCancel = false; + this.additionalData.executionCanceled = false; onCancel.shouldReject = false; onCancel(() => { - gotCancel = true; + this.additionalData.executionCanceled = true; }); const returnPromise = (async () => { @@ -882,10 +882,10 @@ export class WorkflowExecute { this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp ) { - gotCancel = true; + this.additionalData.executionCanceled = true; } - if (gotCancel) { + if (this.additionalData.executionCanceled) { return; } @@ -1015,7 +1015,7 @@ export class WorkflowExecute { } for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) { - if (gotCancel) { + if (this.additionalData.executionCanceled) { return; } try { @@ -1646,7 +1646,7 @@ export class WorkflowExecute { return; })() .then(async () => { - if (gotCancel && executionError === undefined) { + if (this.additionalData.executionCanceled && executionError === undefined) { return this.processSuccessExecution( startedAt, workflow, diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 2596c0fa6dd28..c75dbad4dce3b 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1892,6 +1892,7 @@ export interface IWorkflowExecuteAdditionalData { }, ) => Promise; executionId?: string; + executionCanceled?: boolean; restartExecutionId?: string; hooks?: WorkflowHooks; httpResponse?: express.Response; From 029c951e48d1a8afe661ebfcd3c0d7aca5973ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 14 Nov 2023 19:41:40 +0100 Subject: [PATCH 05/16] backport over pushMessage fixes --- .../editor-ui/src/mixins/pushConnection.ts | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/editor-ui/src/mixins/pushConnection.ts b/packages/editor-ui/src/mixins/pushConnection.ts index eda1d1d810a03..c937f5cb77330 100644 --- a/packages/editor-ui/src/mixins/pushConnection.ts +++ b/packages/editor-ui/src/mixins/pushConnection.ts @@ -19,6 +19,7 @@ import type { IWorkflowBase, SubworkflowOperationError, IExecuteContextData, + NodeOperationError, } from 'n8n-workflow'; import { TelemetryHelpers } from 'n8n-workflow'; @@ -387,21 +388,55 @@ export const pushConnection = defineComponent({ type: 'error', duration: 0, }); - } else { + } else if ( + runDataExecuted.data.resultData.error?.name === 'NodeOperationError' && + (runDataExecuted.data.resultData.error as NodeOperationError).functionality === + 'configuration-node' + ) { + // If the error is a configuration error of the node itself doesn't get executed so we can't use lastNodeExecuted for the title let title: string; - if (runDataExecuted.data.resultData.lastNodeExecuted) { - title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`; + const nodeError = runDataExecuted.data.resultData.error as NodeOperationError; + if (nodeError.node.name) { + title = `Error in sub-node ‘${nodeError.node.name}‘`; } else { title = 'Problem executing workflow'; } this.showMessage({ title, - message: runDataExecutedErrorMessage, + message: + (nodeError?.description ?? runDataExecutedErrorMessage) + + this.$locale.baseText('pushConnection.executionError.openNode', { + interpolate: { + node: nodeError.node.name, + }, + }), type: 'error', duration: 0, dangerouslyUseHTMLString: true, }); + } else { + let title: string; + const isManualExecutionCancelled = + runDataExecutedErrorMessage === 'AbortError' || + (runDataExecuted.mode === 'manual' && runDataExecuted.status === 'canceled'); + + // Do not show the error message if the workflow got canceled manually + if (!isManualExecutionCancelled) { + if (runDataExecuted.data.resultData.lastNodeExecuted) { + title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`; + } else { + title = 'Problem executing workflow'; + } + + this.showMessage({ + title, + message: runDataExecutedErrorMessage, + type: 'error', + duration: 0, + dangerouslyUseHTMLString: true, + }); + } } } else { // Workflow did execute without a problem From 43c954dff8d95c789eb9c19ad476a8be09de77ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 16 Nov 2023 18:24:27 +0100 Subject: [PATCH 06/16] Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning --- packages/cli/src/ActiveExecutions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index bc0343d06e246..0f0807226df85 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ - +import { setMaxListeners } from 'events'; import { Container, Service } from 'typedi'; import type { IDeferredPromise, @@ -127,6 +127,9 @@ export class ActiveExecutions { ); } + // Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning + setMaxListeners(Infinity, abortController.signal); + execution.abortController = abortController; } From ee8669cf95538d4fdbbccd31e436c5223f7a04aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 22 Nov 2023 18:16:50 +0100 Subject: [PATCH 07/16] hook into the existing cancelling code --- packages/cli/src/ActiveExecutions.ts | 23 ++----------- packages/cli/src/CredentialsHelper.ts | 1 + packages/cli/src/Interfaces.ts | 1 - .../cli/src/WorkflowExecuteAdditionalData.ts | 1 - packages/cli/src/WorkflowRunner.ts | 18 ++-------- packages/cli/src/WorkflowRunnerProcess.ts | 16 ++------- packages/cli/src/commands/worker.ts | 1 - .../services/dynamicNodeParameters.service.ts | 1 + packages/core/src/NodeExecuteFunctions.ts | 18 +++++----- packages/core/src/WorkflowExecute.ts | 34 ++++++++----------- packages/workflow/src/Interfaces.ts | 4 +-- packages/workflow/src/RoutingNode.ts | 6 ++-- packages/workflow/src/Workflow.ts | 7 ++-- 13 files changed, 38 insertions(+), 93 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 0f0807226df85..563f5fba1d249 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ -import { setMaxListeners } from 'events'; import { Container, Service } from 'typedi'; import type { IDeferredPromise, @@ -119,20 +117,6 @@ export class ActiveExecutions { this.activeExecutions[executionId].responsePromise = responsePromise; } - attachAbortController(executionId: string, abortController: AbortController): void { - const execution = this.activeExecutions[executionId]; - if (execution === undefined) { - throw new Error( - `No active execution with id "${executionId}" got found to attach to workflowExecution to!`, - ); - } - - // Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning - setMaxListeners(Infinity, abortController.signal); - - execution.abortController = abortController; - } - resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void { if (this.activeExecutions[executionId] === undefined) { return; @@ -189,9 +173,6 @@ export class ActiveExecutions { }, 1); } } else { - // Notify nodes to abort all operations - this.activeExecutions[executionId].abortController?.abort(); - // Workflow is running in current process this.activeExecutions[executionId].workflowExecution!.cancel(); } @@ -231,10 +212,10 @@ export class ActiveExecutions { data = this.activeExecutions[id]; returnData.push({ id, - retryOf: data.executionData.retryOf as string | undefined, + retryOf: data.executionData.retryOf, startedAt: data.startedAt, mode: data.executionData.executionMode, - workflowId: data.executionData.workflowData.id! as string, + workflowId: data.executionData.workflowData.id!, status: data.status, }); } diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index 9144c9fb2de5a..eb7524da31037 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -701,6 +701,7 @@ export class CredentialsHelper extends ICredentialsHelper { nodeTypeCopy, { node, data: {}, source: null }, NodeExecuteFunctions, + new AbortController().signal, credentialsDecrypted, ); } catch (error) { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index d16c40183e469..1caadbee578e9 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -209,7 +209,6 @@ export interface IExecutingWorkflowData { postExecutePromises: Array>; responsePromise?: IDeferredPromise; workflowExecution?: PCancelable; - abortController?: AbortController; status: ExecutionStatus; } diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index f6cff22a2cbff..556b92cd237d5 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -878,7 +878,6 @@ async function executeWorkflow( const runExecutionData = runData.executionData as IRunExecutionData; - // TODO: setup abortController // Execute the workflow const workflowExecute = new WorkflowExecute( additionalDataIntegrated, diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index d33e98b6d8eab..995033805eaeb 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -347,7 +347,6 @@ export class WorkflowRunner { sessionId: data.sessionId, }); - const abortController = new AbortController(); if (data.executionData !== undefined) { this.logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, { executionId, @@ -356,7 +355,6 @@ export class WorkflowRunner { additionalData, data.executionMode, data.executionData, - abortController, ); workflowExecution = workflowExecute.processRunExecutionData(workflow); } else if ( @@ -372,12 +370,7 @@ export class WorkflowRunner { const startNode = WorkflowHelpers.getExecutionStartNode(data, workflow); // Can execute without webhook so go on - const workflowExecute = new WorkflowExecute( - additionalData, - data.executionMode, - undefined, - abortController, - ); + const workflowExecute = new WorkflowExecute(additionalData, data.executionMode); workflowExecution = workflowExecute.run( workflow, startNode, @@ -387,12 +380,7 @@ export class WorkflowRunner { } else { this.logger.debug(`Execution ID ${executionId} is a partial execution.`, { executionId }); // Execute only the nodes between start and destination nodes - const workflowExecute = new WorkflowExecute( - additionalData, - data.executionMode, - undefined, - abortController, - ); + const workflowExecute = new WorkflowExecute(additionalData, data.executionMode); workflowExecution = workflowExecute.runPartialWorkflow( workflow, data.runData, @@ -402,7 +390,6 @@ export class WorkflowRunner { ); } - this.activeExecutions.attachAbortController(executionId, abortController); this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); if (workflowTimeout > 0) { @@ -653,7 +640,6 @@ export class WorkflowRunner { // So we're just preventing crashes here. }); - // TODO: setup AbortController this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); return executionId; } diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index e568b1c9fe91a..a54e5a0d4fd8a 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -269,13 +269,11 @@ class WorkflowRunnerProcess { return returnData!.data!.main; }; - const abortController = new AbortController(); if (this.data.executionData !== undefined) { this.workflowExecute = new WorkflowExecute( additionalData, this.data.executionMode, this.data.executionData, - abortController, ); return this.workflowExecute.processRunExecutionData(this.workflow); } @@ -289,12 +287,7 @@ class WorkflowRunnerProcess { const startNode = WorkflowHelpers.getExecutionStartNode(this.data, this.workflow); // Can execute without webhook so go on - this.workflowExecute = new WorkflowExecute( - additionalData, - this.data.executionMode, - undefined, - abortController, - ); + this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode); return this.workflowExecute.run( this.workflow, startNode, @@ -303,12 +296,7 @@ class WorkflowRunnerProcess { ); } // Execute only the nodes between start and destination nodes - this.workflowExecute = new WorkflowExecute( - additionalData, - this.data.executionMode, - undefined, - abortController, - ); + this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode); return this.workflowExecute.runPartialWorkflow( this.workflow, this.data.runData, diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index f53bb5aa03330..cc18c1cda33a2 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -220,7 +220,6 @@ export class Worker extends BaseCommand { this.logger.debug(`Queued worker execution status for ${executionId} is "${status}"`); }; - // TODO: setup AbortController let workflowExecute: WorkflowExecute; let workflowRun: PCancelable; if (fullExecutionData.data !== undefined) { diff --git a/packages/cli/src/services/dynamicNodeParameters.service.ts b/packages/cli/src/services/dynamicNodeParameters.service.ts index d3ac8c0d6f189..ab42eab0065c6 100644 --- a/packages/cli/src/services/dynamicNodeParameters.service.ts +++ b/packages/cli/src/services/dynamicNodeParameters.service.ts @@ -107,6 +107,7 @@ export class DynamicNodeParametersService { tempNode, { node, source: null, data: {} }, NodeExecuteFunctions, + new AbortController().signal, ); if (optionsData?.length === 0) { diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index a4fd3c156a19f..d197da2503b5c 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2509,18 +2509,16 @@ const getCommonWorkflowFunctions = ( }); const executionCancellationFunctions = ( - abortController?: AbortController, + abortSignal: AbortSignal, ): Pick => ({ - getExecutionCancelSignal: () => { - return abortController?.signal; - }, + getExecutionCancelSignal: () => abortSignal, onExecutionCancellation: (cleanup, reject) => { const handler = async () => { - abortController?.signal?.removeEventListener('abort', handler); + abortSignal.removeEventListener('abort', handler); await cleanup(); reject?.(new Error('Execution cancelled')); }; - abortController?.signal?.addEventListener('abort', handler); + abortSignal.addEventListener('abort', handler); }, }); @@ -3103,12 +3101,12 @@ export function getExecuteFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortController?: AbortController, + abortSignal: AbortSignal, ): IExecuteFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node) => { return { ...getCommonWorkflowFunctions(workflow, node, additionalData), - ...executionCancellationFunctions(abortController), + ...executionCancellationFunctions(abortSignal), getMode: () => mode, getCredentials: async (type, itemIndex) => getCredentials( @@ -3530,12 +3528,12 @@ export function getExecuteSingleFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortController?: AbortController, + abortSignal: AbortSignal, ): IExecuteSingleFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => { return { ...getCommonWorkflowFunctions(workflow, node, additionalData), - ...executionCancellationFunctions(abortController), + ...executionCancellationFunctions(abortSignal), continueOnFail: () => continueOnFail(node), evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => { evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex; diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index 87dc4296cc438..6a70cd6183e2d 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/prefer-optional-chain */ - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ - /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ +import { setMaxListeners } from 'events'; import PCancelable from 'p-cancelable'; import type { @@ -44,24 +43,14 @@ import get from 'lodash/get'; import * as NodeExecuteFunctions from './NodeExecuteFunctions'; export class WorkflowExecute { - runExecutionData: IRunExecutionData; - - private additionalData: IWorkflowExecuteAdditionalData; - - private mode: WorkflowExecuteMode; + private status: ExecutionStatus = 'new'; - private status: ExecutionStatus; + private readonly abortController = new AbortController(); constructor( - additionalData: IWorkflowExecuteAdditionalData, - mode: WorkflowExecuteMode, - runExecutionData?: IRunExecutionData, - private abortController?: AbortController, - ) { - this.additionalData = additionalData; - this.mode = mode; - this.status = 'new'; - this.runExecutionData = runExecutionData || { + private readonly additionalData: IWorkflowExecuteAdditionalData, + private readonly mode: WorkflowExecuteMode, + private runExecutionData: IRunExecutionData = { startData: {}, resultData: { runData: {}, @@ -74,8 +63,8 @@ export class WorkflowExecute { waitingExecution: {}, waitingExecutionSource: {}, }, - }; - } + }, + ) {} /** * Executes the given workflow. @@ -833,8 +822,12 @@ export class WorkflowExecute { return new PCancelable(async (resolve, reject, onCancel) => { this.additionalData.executionCanceled = false; + // Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning + setMaxListeners(Infinity, this.abortController.signal); + onCancel.shouldReject = false; onCancel(() => { + this.abortController.abort(); this.additionalData.executionCanceled = true; }); @@ -1053,7 +1046,7 @@ export class WorkflowExecute { this.additionalData, NodeExecuteFunctions, this.mode, - this.abortController, + this.abortController.signal, ); nodeSuccessData = runNodeData.data; @@ -1091,6 +1084,7 @@ export class WorkflowExecute { this.additionalData, executionData, this.mode, + this.abortController.signal, ); const dataProxy = executeFunctions.getWorkflowDataProxy(0); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index c75dbad4dce3b..bcb1b6db115b0 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -422,7 +422,7 @@ export interface IGetExecuteFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortController?: AbortController, + abortSignal: AbortSignal, ): IExecuteFunctions; } @@ -438,7 +438,7 @@ export interface IGetExecuteSingleFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortController?: AbortController, + abortSignal: AbortSignal, ): IExecuteSingleFunctions; } diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index b25f36c76e42e..eb5c52b2e0010 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -78,8 +78,8 @@ export class RoutingNode { nodeType: INodeType, executeData: IExecuteData, nodeExecuteFunctions: INodeExecuteFunctions, + abortSignal: AbortSignal, credentialsDecrypted?: ICredentialsDecrypted, - abortController?: AbortController, ): Promise { const items = inputData.main[0] as INodeExecutionData[]; const returnData: INodeExecutionData[] = []; @@ -100,7 +100,7 @@ export class RoutingNode { this.additionalData, executeData, this.mode, - abortController, + abortSignal, ); let credentials: ICredentialDataDecryptedObject | undefined; @@ -138,7 +138,7 @@ export class RoutingNode { this.additionalData, executeData, this.mode, - abortController, + abortSignal, ); const requestData: DeclarativeRestApiSettings.ResultOptions = { options: { diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index abd47215a38eb..3916bbe0eba72 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -1216,7 +1216,7 @@ export class Workflow { additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode, - abortController?: AbortController, + abortSignal: AbortSignal, ): Promise { const { node } = executionData; let inputData = executionData.data; @@ -1304,7 +1304,7 @@ export class Workflow { additionalData, executionData, mode, - abortController, + abortSignal, ); const data = nodeType instanceof Node @@ -1387,8 +1387,7 @@ export class Workflow { nodeType, executionData, nodeExecuteFunctions, - undefined, - abortController, + abortSignal, ), }; } From 7ee3a306db9244d8d3046c7d33caf61a6e711226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 22 Nov 2023 18:29:36 +0100 Subject: [PATCH 08/16] remove unnecessary changes --- packages/cli/src/ActiveExecutions.ts | 6 ++++-- packages/cli/src/WorkflowRunnerProcess.ts | 1 + packages/core/src/NodeExecuteFunctions.ts | 14 ++++++-------- packages/core/src/WorkflowExecute.ts | 12 ++++++------ packages/nodes-base/nodes/Wait/Wait.node.ts | 4 ++-- packages/workflow/src/Interfaces.ts | 4 +--- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 563f5fba1d249..9d606aab7d57d 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ + import { Container, Service } from 'typedi'; import type { IDeferredPromise, @@ -212,10 +214,10 @@ export class ActiveExecutions { data = this.activeExecutions[id]; returnData.push({ id, - retryOf: data.executionData.retryOf, + retryOf: data.executionData.retryOf as string | undefined, startedAt: data.startedAt, mode: data.executionData.executionMode, - workflowId: data.executionData.workflowData.id!, + workflowId: data.executionData.workflowData.id! as string, status: data.status, }); } diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index a54e5a0d4fd8a..18992db4ac7f8 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -269,6 +269,7 @@ class WorkflowRunnerProcess { return returnData!.data!.main; }; + if (this.data.executionData !== undefined) { this.workflowExecute = new WorkflowExecute( additionalData, diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index d197da2503b5c..da7594a5cd39f 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2510,15 +2510,13 @@ const getCommonWorkflowFunctions = ( const executionCancellationFunctions = ( abortSignal: AbortSignal, -): Pick => ({ - getExecutionCancelSignal: () => abortSignal, - onExecutionCancellation: (cleanup, reject) => { - const handler = async () => { - abortSignal.removeEventListener('abort', handler); - await cleanup(); - reject?.(new Error('Execution cancelled')); +): Pick => ({ + onExecutionCancellation: (handler) => { + const fn = () => { + abortSignal.removeEventListener('abort', fn); + handler(); }; - abortSignal.addEventListener('abort', handler); + abortSignal.addEventListener('abort', fn); }, }); diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index 6a70cd6183e2d..42d0c8208c230 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -820,7 +820,7 @@ export class WorkflowExecute { let closeFunction: Promise | undefined; return new PCancelable(async (resolve, reject, onCancel) => { - this.additionalData.executionCanceled = false; + let gotCancel = false; // Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning setMaxListeners(Infinity, this.abortController.signal); @@ -828,7 +828,7 @@ export class WorkflowExecute { onCancel.shouldReject = false; onCancel(() => { this.abortController.abort(); - this.additionalData.executionCanceled = true; + gotCancel = true; }); const returnPromise = (async () => { @@ -875,10 +875,10 @@ export class WorkflowExecute { this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp ) { - this.additionalData.executionCanceled = true; + gotCancel = true; } - if (this.additionalData.executionCanceled) { + if (gotCancel) { return; } @@ -1008,7 +1008,7 @@ export class WorkflowExecute { } for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) { - if (this.additionalData.executionCanceled) { + if (gotCancel) { return; } try { @@ -1640,7 +1640,7 @@ export class WorkflowExecute { return; })() .then(async () => { - if (this.additionalData.executionCanceled && executionError === undefined) { + if (gotCancel && executionError === undefined) { return this.processSuccessExecution( startedAt, workflow, diff --git a/packages/nodes-base/nodes/Wait/Wait.node.ts b/packages/nodes-base/nodes/Wait/Wait.node.ts index 609d902779902..bb007f744e433 100644 --- a/packages/nodes-base/nodes/Wait/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait/Wait.node.ts @@ -347,9 +347,9 @@ export class Wait extends Webhook { if (waitValue < 65000) { // If wait time is shorter than 65 seconds leave execution active because // we just check the database every 60 seconds. - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const timer = setTimeout(() => resolve([context.getInputData()]), waitValue); - context.onExecutionCancellation(async () => clearTimeout(timer), reject); + context.onExecutionCancellation(() => clearTimeout(timer)); }); } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index bcb1b6db115b0..735730f7a1580 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -778,8 +778,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { getExecuteData(): IExecuteData; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getInputSourceData(inputIndex?: number, inputName?: string): ISourceData; - getExecutionCancelSignal(): AbortSignal | undefined; - onExecutionCancellation(cleanup: () => Promise, reject: (reason: Error) => void): void; + onExecutionCancellation(handler: () => unknown): void; }; // TODO: Create later own type only for Config-Nodes @@ -1892,7 +1891,6 @@ export interface IWorkflowExecuteAdditionalData { }, ) => Promise; executionId?: string; - executionCanceled?: boolean; restartExecutionId?: string; hooks?: WorkflowHooks; httpResponse?: express.Response; From 31a296fc2ad5ae2fbbfb22122b18f0a019458e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 22 Nov 2023 19:12:59 +0100 Subject: [PATCH 09/16] handle cancellation properly --- packages/cli/src/ActiveExecutions.ts | 1 + .../repositories/execution.repository.ts | 2 +- packages/core/src/WorkflowExecute.ts | 8 +++- .../editor-ui/src/mixins/pushConnection.ts | 43 ++----------------- 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 9d606aab7d57d..3835c02bd2960 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -177,6 +177,7 @@ export class ActiveExecutions { } else { // Workflow is running in current process this.activeExecutions[executionId].workflowExecution!.cancel(); + setTimeout(() => this.remove(executionId), 10); } return this.getPostExecutePromise(executionId); diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index df1bb093bc8f3..8b6de66b861d7 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -223,7 +223,7 @@ export class ExecutionRepository extends Repository { async markAsCrashed(executionIds: string[]) { await this.update( - { id: In(executionIds) }, + { id: In(executionIds), status: In(['new', 'running']) }, { status: 'crashed', stoppedAt: new Date(), diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index 42d0c8208c230..ea67ffb169cff 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -827,8 +827,14 @@ export class WorkflowExecute { onCancel.shouldReject = false; onCancel(() => { - this.abortController.abort(); gotCancel = true; + this.abortController.abort(); + void this.processSuccessExecution( + startedAt, + workflow, + new WorkflowOperationError('Workflow has been canceled!'), + closeFunction, + ); }); const returnPromise = (async () => { diff --git a/packages/editor-ui/src/mixins/pushConnection.ts b/packages/editor-ui/src/mixins/pushConnection.ts index c937f5cb77330..eda1d1d810a03 100644 --- a/packages/editor-ui/src/mixins/pushConnection.ts +++ b/packages/editor-ui/src/mixins/pushConnection.ts @@ -19,7 +19,6 @@ import type { IWorkflowBase, SubworkflowOperationError, IExecuteContextData, - NodeOperationError, } from 'n8n-workflow'; import { TelemetryHelpers } from 'n8n-workflow'; @@ -388,55 +387,21 @@ export const pushConnection = defineComponent({ type: 'error', duration: 0, }); - } else if ( - runDataExecuted.data.resultData.error?.name === 'NodeOperationError' && - (runDataExecuted.data.resultData.error as NodeOperationError).functionality === - 'configuration-node' - ) { - // If the error is a configuration error of the node itself doesn't get executed so we can't use lastNodeExecuted for the title + } else { let title: string; - const nodeError = runDataExecuted.data.resultData.error as NodeOperationError; - if (nodeError.node.name) { - title = `Error in sub-node ‘${nodeError.node.name}‘`; + if (runDataExecuted.data.resultData.lastNodeExecuted) { + title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`; } else { title = 'Problem executing workflow'; } this.showMessage({ title, - message: - (nodeError?.description ?? runDataExecutedErrorMessage) + - this.$locale.baseText('pushConnection.executionError.openNode', { - interpolate: { - node: nodeError.node.name, - }, - }), + message: runDataExecutedErrorMessage, type: 'error', duration: 0, dangerouslyUseHTMLString: true, }); - } else { - let title: string; - const isManualExecutionCancelled = - runDataExecutedErrorMessage === 'AbortError' || - (runDataExecuted.mode === 'manual' && runDataExecuted.status === 'canceled'); - - // Do not show the error message if the workflow got canceled manually - if (!isManualExecutionCancelled) { - if (runDataExecuted.data.resultData.lastNodeExecuted) { - title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`; - } else { - title = 'Problem executing workflow'; - } - - this.showMessage({ - title, - message: runDataExecutedErrorMessage, - type: 'error', - duration: 0, - dangerouslyUseHTMLString: true, - }); - } } } else { // Workflow did execute without a problem From 4b9a4191121860fbceb4850ab28ad5b993025ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 23 Nov 2023 15:16:54 +0100 Subject: [PATCH 10/16] restore getExecutionCancelSignal --- packages/core/src/NodeExecuteFunctions.ts | 3 ++- packages/workflow/src/Interfaces.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index da7594a5cd39f..1296f53c8f6c3 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2510,7 +2510,8 @@ const getCommonWorkflowFunctions = ( const executionCancellationFunctions = ( abortSignal: AbortSignal, -): Pick => ({ +): Pick => ({ + getExecutionCancelSignal: () => abortSignal, onExecutionCancellation: (handler) => { const fn = () => { abortSignal.removeEventListener('abort', fn); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 735730f7a1580..a7cccd1357a0b 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -778,6 +778,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { getExecuteData(): IExecuteData; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getInputSourceData(inputIndex?: number, inputName?: string): ISourceData; + getExecutionCancelSignal(): AbortSignal; onExecutionCancellation(handler: () => unknown): void; }; From b7dfa0d3add76dd934a99f40805114d8ce762ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 24 Nov 2023 12:06:25 +0100 Subject: [PATCH 11/16] handle cancel correctly --- packages/cli/src/ActiveExecutions.ts | 5 +++-- packages/core/src/WorkflowExecute.ts | 22 ++++++------------- .../editor-ui/src/mixins/pushConnection.ts | 20 +++++++++++++---- packages/editor-ui/src/views/NodeView.vue | 4 ---- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 3835c02bd2960..c4a03efc6c663 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -162,6 +162,8 @@ export class ActiveExecutions { return; } + const postExecutePromise = this.getPostExecutePromise(executionId); + // In case something goes wrong make sure that promise gets first // returned that it gets then also resolved correctly. if (this.activeExecutions[executionId].process !== undefined) { @@ -177,10 +179,9 @@ export class ActiveExecutions { } else { // Workflow is running in current process this.activeExecutions[executionId].workflowExecution!.cancel(); - setTimeout(() => this.remove(executionId), 10); } - return this.getPostExecutePromise(executionId); + return postExecutePromise; } /** diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index ea67ffb169cff..0822848f522c4 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -820,21 +820,16 @@ export class WorkflowExecute { let closeFunction: Promise | undefined; return new PCancelable(async (resolve, reject, onCancel) => { - let gotCancel = false; - // Let as many nodes listen to the abort signal, without getting the MaxListenersExceededWarning setMaxListeners(Infinity, this.abortController.signal); onCancel.shouldReject = false; onCancel(() => { - gotCancel = true; + this.status = 'canceled'; this.abortController.abort(); - void this.processSuccessExecution( - startedAt, - workflow, - new WorkflowOperationError('Workflow has been canceled!'), - closeFunction, - ); + const fullRunData = this.getFullRunData(startedAt); + void this.executeHook('workflowExecuteAfter', [fullRunData]); + setTimeout(() => resolve(fullRunData), 10); }); const returnPromise = (async () => { @@ -881,10 +876,10 @@ export class WorkflowExecute { this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp ) { - gotCancel = true; + this.status = 'canceled'; } - if (gotCancel) { + if (this.status === 'canceled') { return; } @@ -1014,9 +1009,6 @@ export class WorkflowExecute { } for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) { - if (gotCancel) { - return; - } try { if (tryIndex !== 0) { // Reset executionError from previous error try @@ -1646,7 +1638,7 @@ export class WorkflowExecute { return; })() .then(async () => { - if (gotCancel && executionError === undefined) { + if (this.status === 'canceled' && executionError === undefined) { return this.processSuccessExecution( startedAt, workflow, diff --git a/packages/editor-ui/src/mixins/pushConnection.ts b/packages/editor-ui/src/mixins/pushConnection.ts index eda1d1d810a03..6a375e9b96115 100644 --- a/packages/editor-ui/src/mixins/pushConnection.ts +++ b/packages/editor-ui/src/mixins/pushConnection.ts @@ -272,7 +272,8 @@ export const pushConnection = defineComponent({ return false; } - if (this.workflowsStore.activeExecutionId !== pushData.executionId) { + const { activeExecutionId } = this.workflowsStore; + if (activeExecutionId !== pushData.executionId) { // The workflow which did finish execution did either not get started // by this session or we do not have the execution id yet. if (isRetry !== true) { @@ -285,10 +286,17 @@ export const pushConnection = defineComponent({ let runDataExecutedErrorMessage = this.getExecutionError(runDataExecuted.data); - if (pushData.data.status === 'crashed') { + if (runDataExecuted.status === 'crashed') { runDataExecutedErrorMessage = this.$locale.baseText( 'pushConnection.executionFailed.message', ); + } else if (runDataExecuted.status === 'canceled') { + runDataExecutedErrorMessage = this.$locale.baseText( + 'executionsList.showMessage.stopExecution.message', + { + interpolate: { activeExecutionId }, + }, + ); } const lineNumber = runDataExecuted?.data?.resultData?.error?.lineNumber; @@ -389,7 +397,11 @@ export const pushConnection = defineComponent({ }); } else { let title: string; - if (runDataExecuted.data.resultData.lastNodeExecuted) { + let type = 'error'; + if (runDataExecuted.status === 'canceled') { + title = this.$locale.baseText('nodeView.showMessage.stopExecutionTry.title'); + type = 'warning'; + } else if (runDataExecuted.data.resultData.lastNodeExecuted) { title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`; } else { title = 'Problem executing workflow'; @@ -398,7 +410,7 @@ export const pushConnection = defineComponent({ this.showMessage({ title, message: runDataExecutedErrorMessage, - type: 'error', + type, duration: 0, dangerouslyUseHTMLString: true, }); diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 705780fac7550..94c3c0317e38a 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1562,10 +1562,6 @@ export default defineComponent({ try { this.stopExecutionInProgress = true; await this.workflowsStore.stopCurrentExecution(executionId); - this.showMessage({ - title: this.$locale.baseText('nodeView.showMessage.stopExecutionTry.title'), - type: 'success', - }); } catch (error) { // Execution stop might fail when the execution has already finished. Let's treat this here. const execution = await this.workflowsStore.getExecution(executionId); From 9f9c7a43a461a2ff6d3ab3b80d868ac52a708a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 24 Nov 2023 16:28:16 +0100 Subject: [PATCH 12/16] make abortSignal optional for now --- packages/cli/src/CredentialsHelper.ts | 1 - .../cli/src/services/dynamicNodeParameters.service.ts | 1 - packages/core/src/NodeExecuteFunctions.ts | 10 +++++----- packages/workflow/src/Interfaces.ts | 6 +++--- packages/workflow/src/RoutingNode.ts | 2 +- packages/workflow/src/Workflow.ts | 3 ++- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index eb7524da31037..9144c9fb2de5a 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -701,7 +701,6 @@ export class CredentialsHelper extends ICredentialsHelper { nodeTypeCopy, { node, data: {}, source: null }, NodeExecuteFunctions, - new AbortController().signal, credentialsDecrypted, ); } catch (error) { diff --git a/packages/cli/src/services/dynamicNodeParameters.service.ts b/packages/cli/src/services/dynamicNodeParameters.service.ts index ab42eab0065c6..d3ac8c0d6f189 100644 --- a/packages/cli/src/services/dynamicNodeParameters.service.ts +++ b/packages/cli/src/services/dynamicNodeParameters.service.ts @@ -107,7 +107,6 @@ export class DynamicNodeParametersService { tempNode, { node, source: null, data: {} }, NodeExecuteFunctions, - new AbortController().signal, ); if (optionsData?.length === 0) { diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 1296f53c8f6c3..2f0ec637d4491 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2509,15 +2509,15 @@ const getCommonWorkflowFunctions = ( }); const executionCancellationFunctions = ( - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): Pick => ({ getExecutionCancelSignal: () => abortSignal, onExecutionCancellation: (handler) => { const fn = () => { - abortSignal.removeEventListener('abort', fn); + abortSignal?.removeEventListener('abort', fn); handler(); }; - abortSignal.addEventListener('abort', fn); + abortSignal?.addEventListener('abort', fn); }, }); @@ -3100,7 +3100,7 @@ export function getExecuteFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): IExecuteFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node) => { return { @@ -3527,7 +3527,7 @@ export function getExecuteSingleFunctions( additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): IExecuteSingleFunctions { return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => { return { diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index a7cccd1357a0b..bc879ab5256b5 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -422,7 +422,7 @@ export interface IGetExecuteFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): IExecuteFunctions; } @@ -438,7 +438,7 @@ export interface IGetExecuteSingleFunctions { additionalData: IWorkflowExecuteAdditionalData, executeData: IExecuteData, mode: WorkflowExecuteMode, - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): IExecuteSingleFunctions; } @@ -778,7 +778,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { getExecuteData(): IExecuteData; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getInputSourceData(inputIndex?: number, inputName?: string): ISourceData; - getExecutionCancelSignal(): AbortSignal; + getExecutionCancelSignal(): AbortSignal | undefined; onExecutionCancellation(handler: () => unknown): void; }; diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index eb5c52b2e0010..18504f3ff18ab 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -78,8 +78,8 @@ export class RoutingNode { nodeType: INodeType, executeData: IExecuteData, nodeExecuteFunctions: INodeExecuteFunctions, - abortSignal: AbortSignal, credentialsDecrypted?: ICredentialsDecrypted, + abortSignal?: AbortSignal, ): Promise { const items = inputData.main[0] as INodeExecutionData[]; const returnData: INodeExecutionData[] = []; diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 3916bbe0eba72..a8eda2b56c3d6 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -1216,7 +1216,7 @@ export class Workflow { additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode, - abortSignal: AbortSignal, + abortSignal?: AbortSignal, ): Promise { const { node } = executionData; let inputData = executionData.data; @@ -1387,6 +1387,7 @@ export class Workflow { nodeType, executionData, nodeExecuteFunctions, + undefined, abortSignal, ), }; From 3f1a3ee5ef5b8fb0da1588f128d58503a90bb4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 24 Nov 2023 16:28:48 +0100 Subject: [PATCH 13/16] revert checks in markAsCrashed --- packages/cli/src/databases/repositories/execution.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 8b6de66b861d7..df1bb093bc8f3 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -223,7 +223,7 @@ export class ExecutionRepository extends Repository { async markAsCrashed(executionIds: string[]) { await this.update( - { id: In(executionIds), status: In(['new', 'running']) }, + { id: In(executionIds) }, { status: 'crashed', stoppedAt: new Date(), From 7a736dac6f9a7311f9638473827a09a08dab962e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 24 Nov 2023 16:28:57 +0100 Subject: [PATCH 14/16] revert changes in ActiveExecutions --- packages/cli/src/ActiveExecutions.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index c4a03efc6c663..9d606aab7d57d 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -162,8 +162,6 @@ export class ActiveExecutions { return; } - const postExecutePromise = this.getPostExecutePromise(executionId); - // In case something goes wrong make sure that promise gets first // returned that it gets then also resolved correctly. if (this.activeExecutions[executionId].process !== undefined) { @@ -181,7 +179,7 @@ export class ActiveExecutions { this.activeExecutions[executionId].workflowExecution!.cancel(); } - return postExecutePromise; + return this.getPostExecutePromise(executionId); } /** From 12a506eaf6a17a248915b23275d1e59b7fc95996 Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Wed, 22 Nov 2023 11:26:56 +0100 Subject: [PATCH 15/16] =?UTF-8?q?=E2=9C=85:=20Fix=2019-executions=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/e2e/19-execution.cy.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts index 0fcea7069c133..b66b974b3b4d9 100644 --- a/cypress/e2e/19-execution.cy.ts +++ b/cypress/e2e/19-execution.cy.ts @@ -51,10 +51,6 @@ describe('Execution', () => { .canvasNodeByName('Manual') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Set') .within(() => cy.get('.fa-check')) @@ -112,10 +108,6 @@ describe('Execution', () => { .canvasNodeByName('Manual') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Wait') .within(() => cy.get('.fa-sync-alt').should('not.visible')); @@ -191,10 +183,6 @@ describe('Execution', () => { .canvasNodeByName('Webhook') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Set') .within(() => cy.get('.fa-check')) @@ -267,10 +255,6 @@ describe('Execution', () => { .canvasNodeByName('Webhook') .within(() => cy.get('.fa-check')) .should('exist'); - workflowPage.getters - .canvasNodeByName('Wait') - .within(() => cy.get('.fa-check')) - .should('exist'); workflowPage.getters .canvasNodeByName('Wait') .within(() => cy.get('.fa-sync-alt').should('not.visible')); From 3e4a64672cad7d79e67c4a1d8238718d54392ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 24 Nov 2023 17:20:24 +0100 Subject: [PATCH 16/16] change to warning toast --- cypress/e2e/19-execution.cy.ts | 8 ++++---- cypress/pages/workflow.ts | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts index b66b974b3b4d9..93e172cc555f8 100644 --- a/cypress/e2e/19-execution.cy.ts +++ b/cypress/e2e/19-execution.cy.ts @@ -120,8 +120,8 @@ describe('Execution', () => { workflowPage.getters.clearExecutionDataButton().click(); workflowPage.getters.clearExecutionDataButton().should('not.exist'); - // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished) - workflowPage.getters.successToast().should('be.visible'); + // Check warning toast (works because Cypress waits enough for the element to show after the http request node has finished) + workflowPage.getters.warningToast().should('be.visible'); }); it('should test webhook workflow', () => { @@ -267,7 +267,7 @@ describe('Execution', () => { workflowPage.getters.clearExecutionDataButton().click(); workflowPage.getters.clearExecutionDataButton().should('not.exist'); - // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished) - workflowPage.getters.successToast().should('be.visible'); + // Check warning toast (works because Cypress waits enough for the element to show after the http request node has finished) + workflowPage.getters.warningToast().should('be.visible'); }); }); diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 85ed98d5828e1..a33ad0faa4792 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -48,6 +48,7 @@ export class WorkflowPage extends BasePage { return cy.get(this.getters.getEndpointSelector('plus', nodeName, index)); }, successToast: () => cy.get('.el-notification:has(.el-notification--success)'), + warningToast: () => cy.get('.el-notification:has(.el-notification--warning)'), errorToast: () => cy.get('.el-notification:has(.el-notification--error)'), activatorSwitch: () => cy.getByTestId('workflow-activate-switch'), workflowMenu: () => cy.getByTestId('workflow-menu'),