fix(core): Fixes event msg confirmations if no subscribers present (#5118)

* 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

* removing UI changes but keeping eventbus fixes

* remove comment
This commit is contained in:
Michael Auerswald 2023-01-11 14:09:09 +01:00 committed by GitHub
parent 044b153275
commit 62d06b1e6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 272 additions and 70 deletions

17
.vscode/launch.json vendored
View file

@ -35,6 +35,23 @@
"outputCapture": "std", "outputCapture": "std",
"killBehavior": "polite" "killBehavior": "polite"
}, },
{
"name": "Launch n8n CLI dev with debug",
"runtimeExecutable": "pnpm",
"cwd": "${workspaceFolder}/packages/cli",
"runtimeArgs": ["run", "dev", "--", "--inspect-brk"],
"console": "integratedTerminal",
"restart": true,
"autoAttachChildProcesses": true,
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node",
"env": {
// "N8N_PORT": "5679",
},
"outputCapture": "std",
"killBehavior": "polite"
},
{ {
"name": "Debug CLI tests", "name": "Debug CLI tests",
"cwd": "${workspaceFolder}/packages/cli", "cwd": "${workspaceFolder}/packages/cli",

View file

@ -3,6 +3,8 @@ import { EventMessageGeneric, EventMessageGenericOptions } from './EventMessageG
import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions';
import { EventMessageWorkflow, EventMessageWorkflowOptions } from './EventMessageWorkflow'; import { EventMessageWorkflow, EventMessageWorkflowOptions } from './EventMessageWorkflow';
import { EventMessageTypeNames } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow';
import { EventMessageAudit, EventMessageAuditOptions } from './EventMessageAudit';
import { EventMessageNode, EventMessageNodeOptions } from './EventMessageNode';
export const getEventMessageObjectByType = ( export const getEventMessageObjectByType = (
message: AbstractEventMessageOptions, message: AbstractEventMessageOptions,
@ -12,6 +14,10 @@ export const getEventMessageObjectByType = (
return new EventMessageGeneric(message as EventMessageGenericOptions); return new EventMessageGeneric(message as EventMessageGenericOptions);
case EventMessageTypeNames.workflow: case EventMessageTypeNames.workflow:
return new EventMessageWorkflow(message as EventMessageWorkflowOptions); return new EventMessageWorkflow(message as EventMessageWorkflowOptions);
case EventMessageTypeNames.audit:
return new EventMessageAudit(message as EventMessageAuditOptions);
case EventMessageTypeNames.node:
return new EventMessageNode(message as EventMessageNodeOptions);
default: default:
return null; return null;
} }

View file

@ -24,7 +24,7 @@ import {
eventMessageGenericDestinationTestEvent, eventMessageGenericDestinationTestEvent,
} from '../EventMessageClasses/EventMessageGeneric'; } from '../EventMessageClasses/EventMessageGeneric';
export type EventMessageReturnMode = 'sent' | 'unsent' | 'all'; export type EventMessageReturnMode = 'sent' | 'unsent' | 'all' | 'unfinished';
class MessageEventBus extends EventEmitter { class MessageEventBus extends EventEmitter {
private static instance: MessageEventBus; private static instance: MessageEventBus;
@ -89,14 +89,20 @@ class MessageEventBus extends EventEmitter {
// - cycle event logs and start the logging to a fresh file // - cycle event logs and start the logging to a fresh file
// - retry sending events // - retry sending events
LoggerProxy.debug('Checking for unsent event messages'); LoggerProxy.debug('Checking for unsent event messages');
const unsentMessages = await this.getEventsUnsent(); const unsentAndUnfinished = await this.getUnsentAndUnfinishedExecutions();
LoggerProxy.debug( LoggerProxy.debug(
`Start logging into ${ `Start logging into ${
(await this.logWriter?.getThread()?.getLogFileName()) ?? 'unknown filename' (await this.logWriter?.getThread()?.getLogFileName()) ?? 'unknown filename'
} `, } `,
); );
await this.logWriter?.startLogging(); await this.logWriter?.startLogging();
await this.send(unsentMessages); 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 configured, run this test every n ms // if configured, run this test every n ms
if (config.getEnv('eventBus.checkUnsentInterval') > 0) { if (config.getEnv('eventBus.checkUnsentInterval') > 0) {
@ -190,6 +196,15 @@ class MessageEventBus extends EventEmitter {
await this.logWriter?.confirmMessageSent(msg.id, source); await this.logWriter?.confirmMessageSent(msg.id, source);
} }
private hasAnyDestinationSubscribedToEvent(msg: EventMessageTypes): boolean {
for (const destinationName of Object.keys(this.destinations)) {
if (this.destinations[destinationName].hasSubscribedToEvent(msg)) {
return true;
}
}
return false;
}
private async emitMessage(msg: EventMessageTypes) { private async emitMessage(msg: EventMessageTypes) {
// generic emit for external modules to capture events // generic emit for external modules to capture events
// this is for internal use ONLY and not for use with custom destinations! // this is for internal use ONLY and not for use with custom destinations!
@ -198,7 +213,11 @@ class MessageEventBus extends EventEmitter {
LoggerProxy.debug(`Listeners: ${this.eventNames().join(',')}`); LoggerProxy.debug(`Listeners: ${this.eventNames().join(',')}`);
// if there are no set up destinations, immediately mark the event as sent // if there are no set up destinations, immediately mark the event as sent
if (!isLogStreamingEnabled() || Object.keys(this.destinations).length === 0) { if (
!isLogStreamingEnabled() ||
Object.keys(this.destinations).length === 0 ||
!this.hasAnyDestinationSubscribedToEvent(msg)
) {
await this.confirmSent(msg, { id: '0', name: 'eventBus' }); await this.confirmSent(msg, { id: '0', name: 'eventBus' });
} else { } else {
for (const destinationName of Object.keys(this.destinations)) { for (const destinationName of Object.keys(this.destinations)) {
@ -207,32 +226,50 @@ class MessageEventBus extends EventEmitter {
} }
} }
async getEvents(mode: EventMessageReturnMode = 'all'): Promise<EventMessageTypes[]> { async getEventsAll(): Promise<EventMessageTypes[]> {
let queryResult: EventMessageTypes[]; const queryResult = await this.logWriter?.getMessagesAll();
switch (mode) {
case 'all':
queryResult = await this.logWriter?.getMessages();
break;
case 'sent':
queryResult = await this.logWriter?.getMessagesSent();
break;
case 'unsent':
queryResult = await this.logWriter?.getMessagesUnsent();
}
const filtered = uniqby(queryResult, 'id'); const filtered = uniqby(queryResult, 'id');
return filtered; return filtered;
} }
async getEventsSent(): Promise<EventMessageTypes[]> { async getEventsSent(): Promise<EventMessageTypes[]> {
const sentMessages = await this.getEvents('sent'); const queryResult = await this.logWriter?.getMessagesSent();
return sentMessages; const filtered = uniqby(queryResult, 'id');
return filtered;
} }
async getEventsUnsent(): Promise<EventMessageTypes[]> { async getEventsUnsent(): Promise<EventMessageTypes[]> {
const unSentMessages = await this.getEvents('unsent'); const queryResult = await this.logWriter?.getMessagesUnsent();
return unSentMessages; const filtered = uniqby(queryResult, 'id');
return filtered;
} }
async getUnfinishedExecutions(): Promise<Set<string>> {
const queryResult = await this.logWriter?.getUnfinishedExecutions();
return queryResult;
}
async getUnsentAndUnfinishedExecutions(): Promise<{
unsentMessages: EventMessageTypes[];
unfinishedExecutions: Set<string>;
}> {
const queryResult = await this.logWriter?.getUnsentAndUnfinishedExecutions();
return queryResult;
}
/**
* This will pull all events for a given execution id from the event log files. Note that this can be a very expensive operation, depending on the number of events and the size of the log files.
* @param executionId id to look for
* @param logHistory defaults to 1, which means it will look at the current log file AND the previous one.
* @returns Array of EventMessageTypes
*/
async getEventsByExecutionId(
executionId: string,
logHistory?: number,
): Promise<EventMessageTypes[]> {
const result = await this.logWriter?.getMessagesByExecutionId(executionId, logHistory);
return result;
}
/** /**
* Convenience Methods * Convenience Methods
*/ */

View file

@ -28,6 +28,12 @@ interface MessageEventBusLogWriterOptions {
maxFileSizeInKB?: number; maxFileSizeInKB?: number;
} }
interface ReadMessagesFromLogFileResult {
loggedMessages: EventMessageTypes[];
sentMessages: EventMessageTypes[];
unfinishedExecutions: Set<string>;
}
/** /**
* MessageEventBusWriter for Files * MessageEventBusWriter for Files
*/ */
@ -138,45 +144,31 @@ export class MessageEventBusLogWriter {
async getMessages( async getMessages(
mode: EventMessageReturnMode = 'all', mode: EventMessageReturnMode = 'all',
includePreviousLog = true, logHistory = 1,
): Promise<EventMessageTypes[]> { ): Promise<ReadMessagesFromLogFileResult> {
const logFileName0 = await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(); const results: ReadMessagesFromLogFileResult = {
const logFileName1 = includePreviousLog
? await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(1)
: undefined;
const results: {
loggedMessages: EventMessageTypes[];
sentMessages: EventMessageTypes[];
} = {
loggedMessages: [], loggedMessages: [],
sentMessages: [], sentMessages: [],
unfinishedExecutions: new Set<string>(),
}; };
if (logFileName0) { const logCount = logHistory
await this.readLoggedMessagesFromFile(results, mode, logFileName0); ? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory)
} : (config.get('eventBus.logWriter.keepLogCount') as number);
if (logFileName1) { for (let i = logCount; i >= 0; i--) {
await this.readLoggedMessagesFromFile(results, mode, logFileName1); const logFileName = await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(i);
} if (logFileName) {
switch (mode) { await this.readLoggedMessagesFromFile(results, mode, logFileName);
case 'all': }
case 'unsent':
return results.loggedMessages;
case 'sent':
return results.sentMessages;
} }
return results;
} }
async readLoggedMessagesFromFile( async readLoggedMessagesFromFile(
results: { results: ReadMessagesFromLogFileResult,
loggedMessages: EventMessageTypes[];
sentMessages: EventMessageTypes[];
},
mode: EventMessageReturnMode, mode: EventMessageReturnMode,
logFileName: string, logFileName: string,
): Promise<{ ): Promise<ReadMessagesFromLogFileResult> {
loggedMessages: EventMessageTypes[];
sentMessages: EventMessageTypes[];
}> {
if (logFileName && existsSync(logFileName)) { if (logFileName && existsSync(logFileName)) {
try { try {
const rl = readline.createInterface({ const rl = readline.createInterface({
@ -189,6 +181,15 @@ export class MessageEventBusLogWriter {
if (isEventMessageOptions(json) && json.__type !== undefined) { if (isEventMessageOptions(json) && json.__type !== undefined) {
const msg = getEventMessageObjectByType(json); const msg = getEventMessageObjectByType(json);
if (msg !== null) results.loggedMessages.push(msg); if (msg !== null) results.loggedMessages.push(msg);
if (msg?.eventName === 'n8n.workflow.started' && msg?.payload?.executionId) {
results.unfinishedExecutions.add(msg?.payload?.executionId as string);
} else if (
(msg?.eventName === 'n8n.workflow.success' ||
msg?.eventName === 'n8n.workflow.failed') &&
msg?.payload?.executionId
) {
results.unfinishedExecutions.delete(msg?.payload?.executionId as string);
}
} }
if (isEventMessageConfirm(json) && mode !== 'all') { if (isEventMessageConfirm(json) && mode !== 'all') {
const removedMessage = remove(results.loggedMessages, (e) => e.id === json.confirm); const removedMessage = remove(results.loggedMessages, (e) => e.id === json.confirm);
@ -211,11 +212,84 @@ export class MessageEventBusLogWriter {
return results; return results;
} }
async getMessagesByExecutionId(
executionId: string,
logHistory?: number,
): Promise<EventMessageTypes[]> {
const result: EventMessageTypes[] = [];
const logCount = logHistory
? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory)
: (config.get('eventBus.logWriter.keepLogCount') as number);
for (let i = 0; i < logCount; i++) {
const logFileName = await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(i);
if (logFileName) {
result.push(...(await this.readFromFileByExecutionId(executionId, logFileName)));
}
}
return result.sort((a, b) => a.ts.diff(b.ts).toMillis());
}
async readFromFileByExecutionId(
executionId: string,
logFileName: string,
): Promise<EventMessageTypes[]> {
const messages: EventMessageTypes[] = [];
if (logFileName && existsSync(logFileName)) {
try {
const rl = readline.createInterface({
input: createReadStream(logFileName),
crlfDelay: Infinity,
});
rl.on('line', (line) => {
try {
const json = jsonParse(line);
if (
isEventMessageOptions(json) &&
json.__type !== undefined &&
json.payload?.executionId === executionId
) {
const msg = getEventMessageObjectByType(json);
if (msg !== null) messages.push(msg);
}
} catch {
LoggerProxy.error(
`Error reading line messages from file: ${logFileName}, line: ${line}`,
);
}
});
// wait for stream to finish before continue
await eventOnce(rl, 'close');
} catch {
LoggerProxy.error(`Error reading logged messages from file: ${logFileName}`);
}
}
return messages;
}
async getMessagesAll(): Promise<EventMessageTypes[]> {
return (await this.getMessages('all')).loggedMessages;
}
async getMessagesSent(): Promise<EventMessageTypes[]> { async getMessagesSent(): Promise<EventMessageTypes[]> {
return this.getMessages('sent'); return (await this.getMessages('sent')).sentMessages;
} }
async getMessagesUnsent(): Promise<EventMessageTypes[]> { async getMessagesUnsent(): Promise<EventMessageTypes[]> {
return this.getMessages('unsent'); return (await this.getMessages('unsent')).loggedMessages;
}
async getUnfinishedExecutions(): Promise<Set<string>> {
return (await this.getMessages('unfinished')).unfinishedExecutions;
}
async getUnsentAndUnfinishedExecutions(): Promise<{
unsentMessages: EventMessageTypes[];
unfinishedExecutions: Set<string>;
}> {
const result = await this.getMessages('unsent');
return {
unsentMessages: result.loggedMessages,
unfinishedExecutions: result.unfinishedExecutions,
};
} }
} }

View file

@ -2,7 +2,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import express from 'express'; import express from 'express';
import { ResponseHelper } from '..';
import { isEventMessageOptions } from './EventMessageClasses/AbstractEventMessage'; import { isEventMessageOptions } from './EventMessageClasses/AbstractEventMessage';
import { EventMessageGeneric } from './EventMessageClasses/EventMessageGeneric'; import { EventMessageGeneric } from './EventMessageClasses/EventMessageGeneric';
import { import {
@ -32,6 +31,7 @@ import {
MessageEventBusDestinationOptions, MessageEventBusDestinationOptions,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { User } from '../databases/entities/User'; import { User } from '../databases/entities/User';
import * as ResponseHelper from '@/ResponseHelper';
export const eventBusRouter = express.Router(); export const eventBusRouter = express.Router();
@ -83,11 +83,27 @@ eventBusRouter.get(
return eventBus.getEventsSent(); return eventBus.getEventsSent();
case 'unsent': case 'unsent':
return eventBus.getEventsUnsent(); return eventBus.getEventsUnsent();
case 'unfinished':
return eventBus.getUnfinishedExecutions();
case 'all': case 'all':
default: default:
return eventBus.getEventsAll();
} }
} }
return eventBus.getEvents(); return eventBus.getEventsAll();
}),
);
eventBusRouter.get(
'/execution/:id',
ResponseHelper.send(async (req: express.Request): Promise<any> => {
if (req.params?.id) {
let logHistory;
if (req.query?.logHistory) {
logHistory = parseInt(req.query.logHistory as string, 10);
}
return eventBus.getEventsByExecutionId(req.params.id, logHistory);
}
}), }),
); );

View file

@ -64,17 +64,16 @@ const testSentryDestination: MessageEventBusDestinationSentryOptions = {
async function cleanLogs() { async function cleanLogs() {
await eventBus.logWriter.getThread()?.cleanLogs(); await eventBus.logWriter.getThread()?.cleanLogs();
const allMessages = await eventBus.getEvents('all'); const allMessages = await eventBus.getEventsAll();
expect(allMessages.length).toBe(0); expect(allMessages.length).toBe(0);
} }
async function confirmIdsSentUnsent() { async function confirmIdsSentUnsent(id: string) {
const sent = await eventBus.getEvents('sent'); const sent = await eventBus.getEventsSent();
const unsent = await eventBus.getEvents('unsent'); const unsent = await eventBus.getEventsUnsent();
expect(sent.length).toBe(1); expect(sent.length).toBe(1);
expect(sent[0].id).toBe(testMessage.id); expect(sent[0].id).toBe(id);
expect(unsent.length).toBe(1); expect(unsent.length).toBe(0);
expect(unsent[0].id).toBe(testMessageUnsubscribed.id);
} }
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message' }); const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message' });
@ -139,14 +138,14 @@ test('should have a running logwriter process', async () => {
test('should have a clean log', async () => { test('should have a clean log', async () => {
await eventBus.logWriter.getThread()?.cleanLogs(); await eventBus.logWriter.getThread()?.cleanLogs();
const allMessages = await eventBus.getEvents('all'); const allMessages = await eventBus.getEventsAll();
expect(allMessages.length).toBe(0); expect(allMessages.length).toBe(0);
}); });
test('should have logwriter log messages', async () => { test('should have logwriter log messages', async () => {
await eventBus.send(testMessage); await eventBus.send(testMessage);
const sent = await eventBus.getEvents('sent'); const sent = await eventBus.getEventsSent();
const unsent = await eventBus.getEvents('unsent'); const unsent = await eventBus.getEventsUnsent();
expect(sent.length).toBeGreaterThan(0); expect(sent.length).toBeGreaterThan(0);
expect(unsent.length).toBe(0); expect(unsent.length).toBe(0);
expect(sent.find((e) => e.id === testMessage.id)).toEqual(testMessage); expect(sent.find((e) => e.id === testMessage.id)).toEqual(testMessage);
@ -209,11 +208,38 @@ test('should send message to syslog ', async () => {
}); });
await eventBus.send(testMessage); await eventBus.send(testMessage);
await new Promise((resolve) => setTimeout(resolve, 100));
expect(mockedSyslogClientLog).toHaveBeenCalled();
await confirmIdsSentUnsent(testMessage.id);
syslogDestination.disable();
});
test('should confirm send message if there are no subscribers', async () => {
config.set('enterprise.features.logStreaming', true);
await cleanLogs();
const syslogDestination = eventBus.destinations[
testSyslogDestination.id!
] as MessageEventBusDestinationSyslog;
syslogDestination.enable();
const mockedSyslogClientLog = jest.spyOn(syslogDestination.client, 'log');
mockedSyslogClientLog.mockImplementation((_m, _options, _cb) => {
eventBus.confirmSent(testMessage, {
id: syslogDestination.id,
name: syslogDestination.label,
});
return syslogDestination.client;
});
await eventBus.send(testMessageUnsubscribed); await eventBus.send(testMessageUnsubscribed);
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
expect(mockedSyslogClientLog).toHaveBeenCalled(); expect(mockedSyslogClientLog).toHaveBeenCalled();
await confirmIdsSentUnsent(); await confirmIdsSentUnsent(testMessageUnsubscribed.id);
syslogDestination.disable(); syslogDestination.disable();
}); });
@ -266,10 +292,9 @@ test('should send message to webhook ', async () => {
mockedAxios.request.mockResolvedValue({ status: 200, data: { msg: 'OK' } }); mockedAxios.request.mockResolvedValue({ status: 200, data: { msg: 'OK' } });
await eventBus.send(testMessage); await eventBus.send(testMessage);
await eventBus.send(testMessageUnsubscribed);
// not elegant, but since communication happens through emitters, we'll wait for a bit // not elegant, but since communication happens through emitters, we'll wait for a bit
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
await confirmIdsSentUnsent(); await confirmIdsSentUnsent(testMessage.id);
webhookDestination.disable(); webhookDestination.disable();
}); });
@ -294,11 +319,10 @@ test('should send message to sentry ', async () => {
}); });
await eventBus.send(testMessage); await eventBus.send(testMessage);
await eventBus.send(testMessageUnsubscribed);
// not elegant, but since communication happens through emitters, we'll wait for a bit // not elegant, but since communication happens through emitters, we'll wait for a bit
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
expect(mockedSentryCaptureMessage).toHaveBeenCalled(); expect(mockedSentryCaptureMessage).toHaveBeenCalled();
await confirmIdsSentUnsent(); await confirmIdsSentUnsent(testMessage.id);
sentryDestination.disable(); sentryDestination.disable();
}); });

View file

@ -37,6 +37,7 @@ import {
INodeListSearchItems, INodeListSearchItems,
NodeParameterValueType, NodeParameterValueType,
INodeActionTypeDescription, INodeActionTypeDescription,
IAbstractEventMessage,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FAKE_DOOR_FEATURES } from './constants'; import { FAKE_DOOR_FEATURES } from './constants';
import { BulkCommand, Undoable } from '@/models/history'; import { BulkCommand, Undoable } from '@/models/history';
@ -236,6 +237,7 @@ export interface IRestApi {
retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean>; retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean>;
getTimezones(): Promise<IDataObject>; getTimezones(): Promise<IDataObject>;
getBinaryUrl(dataPath: string, mode: 'view' | 'download'): string; getBinaryUrl(dataPath: string, mode: 'view' | 'download'): string;
getExecutionEvents(id: string): Promise<IAbstractEventMessage[]>;
} }
export interface INodeTranslationHeaders { export interface INodeTranslationHeaders {

View file

@ -19,6 +19,7 @@ import {
INodeTranslationHeaders, INodeTranslationHeaders,
} from '@/Interface'; } from '@/Interface';
import { import {
IAbstractEventMessage,
IDataObject, IDataObject,
ILoadOptions, ILoadOptions,
INodeCredentials, INodeCredentials,
@ -203,6 +204,11 @@ export const restApi = Vue.extend({
// Binary data // Binary data
getBinaryUrl: (dataPath, mode): string => getBinaryUrl: (dataPath, mode): string =>
self.rootStore.getRestApiContext.baseUrl + `/data/${dataPath}?mode=${mode}`, self.rootStore.getRestApiContext.baseUrl + `/data/${dataPath}?mode=${mode}`,
// Returns all the available timezones
getExecutionEvents: (id: string): Promise<IAbstractEventMessage[]> => {
return self.restApi().makeRestApiRequest('GET', '/eventbus/execution/' + id);
},
}; };
}, },
}, },

View file

@ -459,6 +459,7 @@
"executionsList.showError.refreshData.title": "Problem loading data", "executionsList.showError.refreshData.title": "Problem loading data",
"executionsList.showError.retryExecution.title": "Problem with retry", "executionsList.showError.retryExecution.title": "Problem with retry",
"executionsList.showError.stopExecution.title": "Problem stopping execution", "executionsList.showError.stopExecution.title": "Problem stopping execution",
"executionsList.showError.getExecutionEvents.title": "Problem fetching execution events",
"executionsList.showMessage.handleDeleteSelected.title": "Execution deleted", "executionsList.showMessage.handleDeleteSelected.title": "Execution deleted",
"executionsList.showMessage.retrySuccessfulFalse.title": "Retry unsuccessful", "executionsList.showMessage.retrySuccessfulFalse.title": "Retry unsuccessful",
"executionsList.showMessage.retrySuccessfulTrue.title": "Retry successful", "executionsList.showMessage.retrySuccessfulTrue.title": "Retry successful",

View file

@ -1,3 +1,4 @@
import { DateTime } from 'luxon';
import { INodeCredentials } from './Interfaces'; import { INodeCredentials } from './Interfaces';
// =============================== // ===============================
@ -7,7 +8,6 @@ import { INodeCredentials } from './Interfaces';
export enum EventMessageTypeNames { export enum EventMessageTypeNames {
generic = '$$EventMessage', generic = '$$EventMessage',
audit = '$$EventMessageAudit', audit = '$$EventMessageAudit',
user = '$$EventMessageUser',
confirm = '$$EventMessageConfirm', confirm = '$$EventMessageConfirm',
workflow = '$$EventMessageWorkflow', workflow = '$$EventMessageWorkflow',
node = '$$EventMessageNode', node = '$$EventMessageNode',
@ -20,6 +20,25 @@ export enum MessageEventBusDestinationTypeNames {
syslog = '$$MessageEventBusDestinationSyslog', syslog = '$$MessageEventBusDestinationSyslog',
} }
// ===============================
// Event Message Interfaces
// ===============================
export interface IAbstractEventMessage {
__type: EventMessageTypeNames;
id: string;
ts: DateTime;
eventName: string;
message: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload: any;
}
// =============================== // ===============================
// Event Destination Interfaces // Event Destination Interfaces
// =============================== // ===============================