From 747d3fea568ea19d949f03eedeb3b26e8f7b9781 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Thu, 17 Aug 2023 16:15:56 +0200 Subject: [PATCH 1/4] feat: Add a warning to error workflows that cannot be started due to permission or settings --- packages/cli/src/WorkflowHelpers.ts | 127 +++++++++++------- .../editor-ui/src/mixins/executionsHelpers.ts | 10 +- packages/workflow/src/WorkflowErrors.ts | 5 +- 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index ad4bccd89a390..dda38a10aed57 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -1,7 +1,7 @@ import type { FindOptionsWhere } from 'typeorm'; import { In } from 'typeorm'; import { Container } from 'typedi'; -import type { +import { IDataObject, IExecuteData, INode, @@ -10,7 +10,9 @@ import type { IRunExecutionData, ITaskData, NodeApiError, + SubworkflowOperationError, WorkflowExecuteMode, + WorkflowOperationError, } from 'n8n-workflow'; import { ErrorReporterProxy as ErrorReporter, @@ -22,6 +24,7 @@ import { v4 as uuid } from 'uuid'; import * as Db from '@/Db'; import type { ICredentialsDb, + IExecutionDb, IWorkflowErrorData, IWorkflowExecutionDataProcess, } from '@/Interfaces'; @@ -39,11 +42,57 @@ import { UserService } from './user/user.service'; import type { SharedWorkflow } from '@db/entities/SharedWorkflow'; import type { RoleNames } from '@db/entities/Role'; import { RoleService } from './services/role.service'; -import { RoleRepository } from './databases/repositories'; +import { ExecutionRepository, RoleRepository } from './databases/repositories'; import { VariablesService } from './environments/variables/variables.service'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); +export function generateFailedExecutionFromError( + mode: WorkflowExecuteMode, + error: NodeApiError | NodeOperationError | WorkflowOperationError, + node: INode, +): IRun { + return { + data: { + startData: { + destinationNode: node.name, + runNodeFilter: [node.name], + }, + resultData: { + error, + runData: { + [node.name]: [ + { + startTime: 0, + executionTime: 0, + error, + source: [], + }, + ], + }, + lastNodeExecuted: node.name, + }, + executionData: { + contextData: {}, + nodeExecutionStack: [ + { + node, + data: {}, + source: null, + }, + ], + waitingExecution: {}, + waitingExecutionSource: {}, + }, + }, + finished: false, + mode, + startedAt: new Date(), + stoppedAt: new Date(), + status: 'failed', + }; +} + /** * Returns the data of the last executed node * @@ -127,6 +176,34 @@ export async function executeErrorWorkflow( workflowErrorData.workflow.id, ); } catch (error) { + const initialNode = workflowInstance.getStartNode(); + if (initialNode) { + const errorWorkflowPermissionError = new SubworkflowOperationError( + 'This workflow could not be triggered due to subworkflow settings or permissions.', + `Another workflow with ID ${workflowErrorData.workflow.id} tried to invoke this workflow to handle errors. Please check permissions and whether this workflow allows being called by others`, + ); + + // Create a fake execution and save it to DB. + const fakeExecution = generateFailedExecutionFromError( + 'error', + errorWorkflowPermissionError, + initialNode, + ); + + const fullExecutionData: IExecutionDb = { + data: fakeExecution.data, + mode: fakeExecution.mode, + finished: false, + startedAt: new Date(), + stoppedAt: new Date(), + workflowData, + waitTill: null, + status: fakeExecution.status, + workflowId: workflowData.id, + }; + + await Container.get(ExecutionRepository).createNewExecution(fullExecutionData); + } Logger.info('Error workflow execution blocked due to subworkflow settings', { erroredWorkflowId: workflowErrorData.workflow.id, errorWorkflowId: workflowId, @@ -445,52 +522,6 @@ export async function isBelowOnboardingThreshold(user: User): Promise { return belowThreshold; } -export function generateFailedExecutionFromError( - mode: WorkflowExecuteMode, - error: NodeApiError | NodeOperationError, - node: INode, -): IRun { - return { - data: { - startData: { - destinationNode: node.name, - runNodeFilter: [node.name], - }, - resultData: { - error, - runData: { - [node.name]: [ - { - startTime: 0, - executionTime: 0, - error, - source: [], - }, - ], - }, - lastNodeExecuted: node.name, - }, - executionData: { - contextData: {}, - nodeExecutionStack: [ - { - node, - data: {}, - source: null, - }, - ], - waitingExecution: {}, - waitingExecutionSource: {}, - }, - }, - finished: false, - mode, - startedAt: new Date(), - stoppedAt: new Date(), - status: 'failed', - }; -} - /** Get all nodes in a workflow where the node credential is not accessible to the user. */ export function getNodesWithInaccessibleCreds(workflow: WorkflowEntity, userCredIds: string[]) { if (!workflow.nodes) { diff --git a/packages/editor-ui/src/mixins/executionsHelpers.ts b/packages/editor-ui/src/mixins/executionsHelpers.ts index c5e417a8ef9e5..29a6c729485dc 100644 --- a/packages/editor-ui/src/mixins/executionsHelpers.ts +++ b/packages/editor-ui/src/mixins/executionsHelpers.ts @@ -41,19 +41,15 @@ export const executionHelpers = defineComponent({ runningTime: '', }; - if (execution.status === 'waiting' || execution.waitTill) { + if (execution.status === 'waiting') { status.name = 'waiting'; status.label = this.$locale.baseText('executionsList.waiting'); } else if (execution.status === 'canceled') { status.label = this.$locale.baseText('executionsList.canceled'); - } else if ( - execution.status === 'running' || - execution.status === 'new' || - execution.stoppedAt === undefined - ) { + } else if (execution.status === 'running' || execution.status === 'new') { status.name = 'running'; status.label = this.$locale.baseText('executionsList.running'); - } else if (execution.status === 'success' || execution.finished) { + } else if (execution.status === 'success') { status.name = 'success'; status.label = this.$locale.baseText('executionsList.succeeded'); } else if (execution.status === 'failed' || execution.status === 'crashed') { diff --git a/packages/workflow/src/WorkflowErrors.ts b/packages/workflow/src/WorkflowErrors.ts index c161d4475fad5..79ab34f7159d0 100644 --- a/packages/workflow/src/WorkflowErrors.ts +++ b/packages/workflow/src/WorkflowErrors.ts @@ -1,9 +1,10 @@ import type { INode } from './Interfaces'; +import { ExecutionBaseError } from './NodeErrors'; /** * Class for instantiating an operational error, e.g. a timeout error. */ -export class WorkflowOperationError extends Error { +export class WorkflowOperationError extends ExecutionBaseError { node: INode | undefined; timestamp: number; @@ -13,7 +14,7 @@ export class WorkflowOperationError extends Error { description: string | undefined; constructor(message: string, node?: INode) { - super(message); + super(message, { cause: undefined }); this.name = this.constructor.name; this.node = node; this.timestamp = Date.now(); From 0160904be0d8f1815913fb91ffa7fbe820fe4008 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 21 Aug 2023 09:28:31 +0200 Subject: [PATCH 2/4] fix: Import types --- packages/cli/src/WorkflowHelpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index dda38a10aed57..9bb7120130635 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -1,7 +1,9 @@ import type { FindOptionsWhere } from 'typeorm'; import { In } from 'typeorm'; import { Container } from 'typedi'; -import { +import { SubworkflowOperationError } from 'n8n-workflow'; + +import type { IDataObject, IExecuteData, INode, @@ -10,7 +12,6 @@ import { IRunExecutionData, ITaskData, NodeApiError, - SubworkflowOperationError, WorkflowExecuteMode, WorkflowOperationError, } from 'n8n-workflow'; From e76592d947ea52da17b7a4b51dda7fa61fde35bb Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 21 Aug 2023 12:36:26 +0200 Subject: [PATCH 3/4] refactor: Change copy --- packages/cli/src/WorkflowHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 9bb7120130635..5b5eaaea6564b 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -180,8 +180,8 @@ export async function executeErrorWorkflow( const initialNode = workflowInstance.getStartNode(); if (initialNode) { const errorWorkflowPermissionError = new SubworkflowOperationError( - 'This workflow could not be triggered due to subworkflow settings or permissions.', - `Another workflow with ID ${workflowErrorData.workflow.id} tried to invoke this workflow to handle errors. Please check permissions and whether this workflow allows being called by others`, + `Another workflow: (ID ${workflowErrorData.workflow.id}) tried to invoke this workflow to handle errors.`, + "Unfortunately current permissions do not allow this. Please check that this workflow's settings allow it to be called by others", ); // Create a fake execution and save it to DB. From a438d7b8c08c78de79c221f91ac176da37b58e21 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 21 Aug 2023 12:49:08 +0200 Subject: [PATCH 4/4] refactor: Fix import --- packages/cli/src/WorkflowHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 5b5eaaea6564b..0d5140d2ac34c 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -1,7 +1,6 @@ import type { FindOptionsWhere } from 'typeorm'; import { In } from 'typeorm'; import { Container } from 'typedi'; -import { SubworkflowOperationError } from 'n8n-workflow'; import type { IDataObject, @@ -19,6 +18,7 @@ import { ErrorReporterProxy as ErrorReporter, LoggerProxy as Logger, NodeOperationError, + SubworkflowOperationError, Workflow, } from 'n8n-workflow'; import { v4 as uuid } from 'uuid';