feat(core): Add execution runData recovery and status field (#5112)
* adds ExecutionEvents view modal to ExecutionList * fix time rendering and remove wf column * checks for unfinished executions and fails them * prevent re-setting stoppedAt for execution * some cleanup / manually create rundata after crash * quicksave * remove Threads lib, log worker rewrite * cleanup comment * fix sentry destination return value * test for tests... * run tests with single worker * fix tests * remove console log * add endpoint for execution data recovery * lint cleanup and some refactoring * fix accidental recursion * remove cyclic imports * add rundata recovery to Workflowrunner * remove comments * cleanup and refactor * adds a status field to executions * setExecutionStatus on queued worker * fix onWorkflowPostExecute * set waiting from worker * get crashed status into frontend * remove comment * merge fix * cleanup * catch empty rundata in recovery * refactor IExecutionsSummary and inject nodeExecution Errors * reduce default event log size to 10mb from 100mb * add per node execution status * lint fix * merge and lint fix * phrasing change * improve preview rendering and messaging * remove debug * Improve partial rundata recovery * fix labels * fix line through * send manual rundata to ui at crash * some type and msg push fixes * improve recovered item rendering in preview * update workflowStatistics on recover * merge fix * review fixes * merge fix * notify eventbus when ui is back up * add a small timeout to make sure the UI is back up * increase reconnect timeout to 30s * adjust recover timeout and ui connection lost msg * do not stop execution in editor after x reconnects * add executionRecovered push event * fix recovered connection not green * remove reconnect toast and merge existing rundata * merge editor and recovered data for own mode
This commit is contained in:
committed by
GitHub
parent
3a9c257f55
commit
d143f3f2ec
@@ -3,7 +3,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { validate as jsonSchemaValidate } from 'jsonschema';
|
||||
import { BinaryDataManager } from 'n8n-core';
|
||||
import type { IDataObject, IWorkflowBase, JsonObject } from 'n8n-workflow';
|
||||
import type {
|
||||
IDataObject,
|
||||
IWorkflowBase,
|
||||
JsonObject,
|
||||
ExecutionStatus,
|
||||
IRunExecutionData,
|
||||
NodeOperationError,
|
||||
IExecutionsSummary,
|
||||
} from 'n8n-workflow';
|
||||
import { deepCopy, LoggerProxy, jsonParse, Workflow } from 'n8n-workflow';
|
||||
import type { FindOperator, FindOptionsWhere } from 'typeorm';
|
||||
import { In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm';
|
||||
@@ -25,6 +33,7 @@ import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
import * as Db from '@/Db';
|
||||
import * as GenericHelpers from '@/GenericHelpers';
|
||||
import { parse } from 'flatted';
|
||||
|
||||
interface IGetExecutionsQueryFilter {
|
||||
id?: FindOperator<string>;
|
||||
@@ -32,6 +41,7 @@ interface IGetExecutionsQueryFilter {
|
||||
mode?: string;
|
||||
retryOf?: string;
|
||||
retrySuccessId?: string;
|
||||
status?: ExecutionStatus[];
|
||||
workflowId?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
waitTill?: FindOperator<any> | boolean;
|
||||
@@ -45,6 +55,10 @@ const schemaGetExecutionsQueryFilter = {
|
||||
mode: { type: 'string' },
|
||||
retryOf: { type: 'string' },
|
||||
retrySuccessId: { type: 'string' },
|
||||
status: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
waitTill: { type: 'boolean' },
|
||||
workflowId: { anyOf: [{ type: 'integer' }, { type: 'string' }] },
|
||||
},
|
||||
@@ -193,7 +207,16 @@ export class ExecutionsService {
|
||||
.map(({ id }) => id),
|
||||
);
|
||||
|
||||
const findWhere: FindOptionsWhere<ExecutionEntity> = { workflowId: In(sharedWorkflowIds) };
|
||||
const findWhere: FindOptionsWhere<ExecutionEntity> = {
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
};
|
||||
if (filter?.status) {
|
||||
Object.assign(findWhere, { status: In(filter.status) });
|
||||
delete filter.status; // remove status from filter so it does not get applied twice
|
||||
}
|
||||
if (filter?.finished) {
|
||||
Object.assign(findWhere, { finished: filter.finished });
|
||||
}
|
||||
|
||||
const rangeQuery: string[] = [];
|
||||
const rangeQueryParams: {
|
||||
@@ -257,7 +280,42 @@ export class ExecutionsService {
|
||||
req.user,
|
||||
);
|
||||
|
||||
const formattedExecutions = executions.map((execution) => {
|
||||
const formattedExecutions: IExecutionsSummary[] = executions.map((execution) => {
|
||||
// inject potential node execution errors into the execution response
|
||||
const nodeExecutionStatus = {};
|
||||
let lastNodeExecuted;
|
||||
let executionError;
|
||||
try {
|
||||
const data = parse(execution.data) as IRunExecutionData;
|
||||
lastNodeExecuted = data?.resultData?.lastNodeExecuted ?? '';
|
||||
executionError = data?.resultData?.error;
|
||||
if (data?.resultData?.runData) {
|
||||
for (const key of Object.keys(data.resultData.runData)) {
|
||||
const errors = data.resultData.runData[key]
|
||||
?.filter((taskdata) => taskdata.error?.name)
|
||||
?.map((taskdata) => {
|
||||
if (taskdata.error?.name === 'NodeOperationError') {
|
||||
return {
|
||||
name: (taskdata.error as NodeOperationError).name,
|
||||
message: (taskdata.error as NodeOperationError).message,
|
||||
description: (taskdata.error as NodeOperationError).description,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: taskdata.error?.name,
|
||||
};
|
||||
}
|
||||
});
|
||||
Object.assign(nodeExecutionStatus, {
|
||||
[key]: {
|
||||
executionStatus: data.resultData.runData[key][0].executionStatus,
|
||||
errors,
|
||||
data: data.resultData.runData[key][0].data ?? undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return {
|
||||
id: execution.id,
|
||||
finished: execution.finished,
|
||||
@@ -269,9 +327,12 @@ export class ExecutionsService {
|
||||
stoppedAt: execution.stoppedAt,
|
||||
workflowId: execution.workflowData?.id ?? '',
|
||||
workflowName: execution.workflowData?.name,
|
||||
};
|
||||
status: execution.status,
|
||||
lastNodeExecuted,
|
||||
executionError,
|
||||
nodeExecutionStatus,
|
||||
} as IExecutionsSummary;
|
||||
});
|
||||
|
||||
return {
|
||||
count,
|
||||
results: formattedExecutions,
|
||||
|
||||
Reference in New Issue
Block a user