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:
Michael Auerswald
2023-02-17 10:54:07 +01:00
committed by GitHub
parent 3a9c257f55
commit d143f3f2ec
71 changed files with 1245 additions and 307 deletions

View File

@@ -33,6 +33,7 @@ import type {
IWorkflowHooksOptionalParameters,
IWorkflowSettings,
WorkflowExecuteMode,
ExecutionStatus,
} from 'n8n-workflow';
import {
ErrorReporterProxy as ErrorReporter,
@@ -335,16 +336,22 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
// Clone the object except the runData. That one is not supposed
// to be send. Because that data got send piece by piece after
// each node which finished executing
const pushRunData = {
...fullRunData,
data: {
...fullRunData.data,
resultData: {
...fullRunData.data.resultData,
runData: {},
// Edit: we now DO send the runData to the UI if mode=manual so that it shows the point of crashes
let pushRunData;
if (fullRunData.mode === 'manual') {
pushRunData = fullRunData;
} else {
pushRunData = {
...fullRunData,
data: {
...fullRunData.data,
resultData: {
...fullRunData.data.resultData,
runData: {},
},
},
},
};
};
}
// Push data to editor-ui once workflow finished
Logger.debug(`Save execution progress to database for execution ID ${executionId} `, {
@@ -445,6 +452,8 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
// Set last executed node so that it may resume on failure
fullExecutionData.data.resultData.lastNodeExecuted = nodeName;
fullExecutionData.status = 'running';
const flattenedExecutionData = ResponseHelper.flattenExecutionData(fullExecutionData);
await Db.collections.Execution.update(
@@ -600,6 +609,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
stoppedAt: fullRunData.stoppedAt,
workflowData: pristineWorkflowData,
waitTill: fullRunData.waitTill,
status: fullRunData.status,
};
if (this.retryOf !== undefined) {
@@ -711,7 +721,11 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
}
}
const workflowDidSucceed = !fullRunData.data.resultData.error;
const workflowHasCrashed = fullRunData.status === 'crashed';
const workflowDidSucceed = !fullRunData.data.resultData.error && !workflowHasCrashed;
let workflowStatusFinal: ExecutionStatus = workflowDidSucceed ? 'success' : 'failed';
if (workflowHasCrashed) workflowStatusFinal = 'crashed';
if (!workflowDidSucceed) {
executeErrorWorkflow(
this.workflowData,
@@ -730,6 +744,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
stoppedAt: fullRunData.stoppedAt,
workflowData: this.workflowData,
waitTill: fullRunData.data.waitTill,
status: workflowStatusFinal,
};
if (this.retryOf !== undefined) {
@@ -749,6 +764,11 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
executionData as IExecutionFlattedDb,
);
// For reasons(tm) the execution status is not updated correctly in the first update, so has to be written again (tbd)
await Db.collections.Execution.update(this.executionId, {
status: executionData.status,
});
if (fullRunData.finished === true && this.retryOf !== undefined) {
// If the retry was successful save the reference it on the original execution
await Db.collections.Execution.update(this.retryOf, {
@@ -995,6 +1015,7 @@ async function executeWorkflow(
mode: 'integrated',
startedAt: new Date(),
stoppedAt: new Date(),
status: 'error',
};
// When failing, we might not have finished the execution
// Therefore, database might not contain finished errors.
@@ -1006,6 +1027,7 @@ async function executeWorkflow(
finished: fullRunData.finished ? fullRunData.finished : false,
startedAt: fullRunData.startedAt,
stoppedAt: fullRunData.stoppedAt,
status: fullRunData.status,
workflowData,
};
if (workflowData.id) {
@@ -1048,6 +1070,19 @@ async function executeWorkflow(
};
}
export function setExecutionStatus(status: ExecutionStatus) {
if (this.executionId === undefined) {
Logger.debug(`Setting execution status "${status}" failed because executionId is undefined`);
return;
}
Logger.debug(`Setting execution status for ${this.executionId} to "${status}"`);
ActiveExecutions.getInstance()
.setStatus(this.executionId, status)
.catch((error) => {
Logger.debug(`Setting execution status "${status}" failed: ${error.message}`);
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function sendMessageToUI(source: string, messages: any[]) {
const { sessionId } = this;
@@ -1101,6 +1136,7 @@ export async function getBase(
currentNodeParameters,
executionTimeoutTimestamp,
userId,
setExecutionStatus,
};
}