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
@@ -1,5 +1,5 @@
|
||||
import type { MessageEventBusDestinationOptions } from 'n8n-workflow';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import type { MessageEventBusDestinationOptions } from 'n8n-workflow';
|
||||
import type { DeleteResult } from 'typeorm';
|
||||
import type { EventMessageTypes } from '../EventMessageClasses/';
|
||||
import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee';
|
||||
@@ -24,10 +24,16 @@ import {
|
||||
EventMessageGeneric,
|
||||
eventMessageGenericDestinationTestEvent,
|
||||
} from '../EventMessageClasses/EventMessageGeneric';
|
||||
import { recoverExecutionDataFromEventLogMessages } from './recoverEvents';
|
||||
|
||||
export type EventMessageReturnMode = 'sent' | 'unsent' | 'all' | 'unfinished';
|
||||
|
||||
class MessageEventBus extends EventEmitter {
|
||||
export interface MessageWithCallback {
|
||||
msg: EventMessageTypes;
|
||||
confirmCallback: (message: EventMessageTypes, src: EventMessageConfirmSource) => void;
|
||||
}
|
||||
|
||||
export class MessageEventBus extends EventEmitter {
|
||||
private static instance: MessageEventBus;
|
||||
|
||||
isInitialized: boolean;
|
||||
@@ -71,12 +77,13 @@ class MessageEventBus extends EventEmitter {
|
||||
if (savedEventDestinations.length > 0) {
|
||||
for (const destinationData of savedEventDestinations) {
|
||||
try {
|
||||
const destination = messageEventBusDestinationFromDb(destinationData);
|
||||
const destination = messageEventBusDestinationFromDb(this, destinationData);
|
||||
if (destination) {
|
||||
await this.addDestination(destination);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (error.message) LoggerProxy.debug(error.message as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,9 +103,13 @@ class MessageEventBus extends EventEmitter {
|
||||
this.logWriter?.startLogging();
|
||||
await this.send(unsentAndUnfinished.unsentMessages);
|
||||
|
||||
if (unsentAndUnfinished.unfinishedExecutions.size > 0) {
|
||||
for (const executionId of unsentAndUnfinished.unfinishedExecutions) {
|
||||
LoggerProxy.debug(`Found unfinished execution ${executionId} in event log(s)`);
|
||||
if (Object.keys(unsentAndUnfinished.unfinishedExecutions).length > 0) {
|
||||
for (const executionId of Object.keys(unsentAndUnfinished.unfinishedExecutions)) {
|
||||
await recoverExecutionDataFromEventLogMessages(
|
||||
executionId,
|
||||
unsentAndUnfinished.unfinishedExecutions[executionId],
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,12 +192,15 @@ class MessageEventBus extends EventEmitter {
|
||||
}
|
||||
|
||||
async testDestination(destinationId: string): Promise<boolean> {
|
||||
const testMessage = new EventMessageGeneric({
|
||||
const msg = new EventMessageGeneric({
|
||||
eventName: eventMessageGenericDestinationTestEvent,
|
||||
});
|
||||
const destination = await this.findDestination(destinationId);
|
||||
if (destination.length > 0) {
|
||||
const sendResult = await this.destinations[destinationId].receiveFromEventBus(testMessage);
|
||||
const sendResult = await this.destinations[destinationId].receiveFromEventBus({
|
||||
msg,
|
||||
confirmCallback: () => this.confirmSent(msg, { id: '0', name: 'eventBus' }),
|
||||
});
|
||||
return sendResult;
|
||||
}
|
||||
return false;
|
||||
@@ -212,17 +226,21 @@ class MessageEventBus extends EventEmitter {
|
||||
|
||||
// generic emit for external modules to capture events
|
||||
// this is for internal use ONLY and not for use with custom destinations!
|
||||
this.emit('message', msg);
|
||||
|
||||
// LoggerProxy.debug(`Listeners: ${this.eventNames().join(',')}`);
|
||||
this.emitMessageWithCallback('message', msg);
|
||||
|
||||
if (this.shouldSendMsg(msg)) {
|
||||
for (const destinationName of Object.keys(this.destinations)) {
|
||||
this.emit(this.destinations[destinationName].getId(), msg);
|
||||
this.emitMessageWithCallback(this.destinations[destinationName].getId(), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private emitMessageWithCallback(eventName: string, msg: EventMessageTypes): boolean {
|
||||
const confirmCallback = (message: EventMessageTypes, src: EventMessageConfirmSource) =>
|
||||
this.confirmSent(message, src);
|
||||
return this.emit(eventName, msg, confirmCallback);
|
||||
}
|
||||
|
||||
shouldSendMsg(msg: EventMessageTypes): boolean {
|
||||
return (
|
||||
isLogStreamingEnabled() &&
|
||||
@@ -249,14 +267,14 @@ class MessageEventBus extends EventEmitter {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
async getUnfinishedExecutions(): Promise<Set<string>> {
|
||||
async getUnfinishedExecutions(): Promise<Record<string, EventMessageTypes[]>> {
|
||||
const queryResult = await this.logWriter?.getUnfinishedExecutions();
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
async getUnsentAndUnfinishedExecutions(): Promise<{
|
||||
unsentMessages: EventMessageTypes[];
|
||||
unfinishedExecutions: Set<string>;
|
||||
unfinishedExecutions: Record<string, EventMessageTypes[]>;
|
||||
}> {
|
||||
const queryResult = await this.logWriter?.getUnsentAndUnfinishedExecutions();
|
||||
return queryResult;
|
||||
|
||||
Reference in New Issue
Block a user