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

@@ -1,4 +1,9 @@
import { IExecutionResponse, IExecutionsCurrentSummaryExtended, IPushData } from '@/Interface';
import {
IExecutionResponse,
IExecutionsCurrentSummaryExtended,
IPushData,
IPushDataExecutionFinished,
} from '@/Interface';
import { externalHooks } from '@/mixins/externalHooks';
import { nodeHelpers } from '@/mixins/nodeHelpers';
@@ -10,6 +15,8 @@ import {
ExpressionError,
IDataObject,
INodeTypeNameVersion,
IRun,
IRunExecutionData,
IWorkflowBase,
SubworkflowOperationError,
TelemetryHelpers,
@@ -25,6 +32,7 @@ import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
import { useSettingsStore } from '@/stores/settings';
import { parse } from 'flatted';
export const pushConnection = mixins(
externalHooks,
@@ -57,21 +65,6 @@ export const pushConnection = mixins(
},
methods: {
attemptReconnect() {
const isWorkflowRunning = this.uiStore.isActionActive('workflowRunning');
if (this.connectRetries > 3 && !this.lostConnection && isWorkflowRunning) {
this.lostConnection = true;
this.workflowsStore.executingNode = null;
this.uiStore.removeActiveAction('workflowRunning');
this.$showMessage({
title: this.$locale.baseText('pushConnection.executionFailed'),
message: this.$locale.baseText('pushConnection.executionFailed.message'),
type: 'error',
duration: 0,
});
}
this.pushConnect();
},
@@ -115,13 +108,17 @@ export const pushConnection = mixins(
this.connectRetries = 0;
this.lostConnection = false;
this.rootStore.pushConnectionActive = true;
this.clearAllStickyNotifications();
this.pushSource?.removeEventListener('open', this.onConnectionSuccess);
},
onConnectionError() {
this.pushDisconnect();
this.connectRetries++;
this.reconnectTimeout = setTimeout(this.attemptReconnect, this.connectRetries * 5000);
this.reconnectTimeout = setTimeout(
this.attemptReconnect,
Math.min(this.connectRetries * 3000, 30000), // maximum 30 seconds backoff
);
},
/**
@@ -186,7 +183,7 @@ export const pushConnection = mixins(
/**
* Process a newly received message
*/
pushMessageReceived(event: Event, isRetry?: boolean): boolean {
async pushMessageReceived(event: Event, isRetry?: boolean): Promise<boolean> {
const retryAttempts = 5;
let receivedData: IPushData;
try {
@@ -229,11 +226,81 @@ export const pushConnection = mixins(
}
}
if (receivedData.type === 'executionFinished') {
// The workflow finished executing
const pushData = receivedData.data;
// recovered execution data is handled like executionFinished data, however for security reasons
// we need to fetch the data from the server again rather than push it to all clients
let recoveredPushData: IPushDataExecutionFinished | undefined = undefined;
if (receivedData.type === 'executionRecovered') {
const recoveredExecutionId = receivedData.data?.executionId;
const isWorkflowRunning = this.uiStore.isActionActive('workflowRunning');
if (isWorkflowRunning && this.workflowsStore.activeExecutionId === recoveredExecutionId) {
// pull execution data for the recovered execution from the server
const executionData = await this.workflowsStore.fetchExecutionDataById(
this.workflowsStore.activeExecutionId,
);
if (executionData?.data) {
// data comes in as 'flatten' object, so we need to parse it
executionData.data = parse(
executionData.data as unknown as string,
) as IRunExecutionData;
const iRunExecutionData: IRunExecutionData = {
startData: executionData.data?.startData,
resultData: executionData.data?.resultData ?? { runData: {} },
executionData: executionData.data?.executionData,
};
if (
this.workflowsStore.workflowExecutionData?.workflowId === executionData.workflowId
) {
const activeRunData =
this.workflowsStore.workflowExecutionData?.data?.resultData?.runData;
if (activeRunData) {
for (const key of Object.keys(activeRunData)) {
iRunExecutionData.resultData.runData[key] = activeRunData[key];
}
}
}
const iRun: IRun = {
data: iRunExecutionData,
finished: executionData.finished,
mode: executionData.mode,
waitTill: executionData.data?.waitTill,
startedAt: executionData.startedAt,
stoppedAt: executionData.stoppedAt,
status: 'crashed',
};
if (executionData.data) {
recoveredPushData = {
executionId: executionData.id,
data: iRun,
};
}
}
}
}
this.workflowsStore.finishActiveExecution(pushData);
if (receivedData.type === 'executionFinished' || receivedData.type === 'executionRecovered') {
// The workflow finished executing
let pushData: IPushDataExecutionFinished;
if (receivedData.type === 'executionRecovered' && recoveredPushData !== undefined) {
pushData = recoveredPushData as IPushDataExecutionFinished;
} else {
pushData = receivedData.data as IPushDataExecutionFinished;
}
if (this.workflowsStore.activeExecutionId === pushData.executionId) {
const activeRunData =
this.workflowsStore.workflowExecutionData?.data?.resultData?.runData;
if (activeRunData) {
for (const key of Object.keys(activeRunData)) {
if (
pushData.data.data.resultData.runData[key]?.[0]?.data?.main?.[0]?.[0]?.json
.isArtificalRecoveredEventItem === true &&
activeRunData[key].length > 0
)
pushData.data.data.resultData.runData[key] = activeRunData[key];
}
}
this.workflowsStore.finishActiveExecution(pushData);
}
if (!this.uiStore.isActionActive('workflowRunning')) {
// No workflow is running so ignore the messages
@@ -251,7 +318,13 @@ export const pushConnection = mixins(
const runDataExecuted = pushData.data;
const runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data);
let runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data);
if (pushData.data.status === 'crashed') {
runDataExecutedErrorMessage = this.$locale.baseText(
'pushConnection.executionFailed.message',
);
}
const lineNumber =
runDataExecuted &&