mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
feat(core): Add workflow cancellation events to log streaming (#23151)
This commit is contained in:
@@ -178,7 +178,14 @@ export class ActiveExecutions {
|
||||
// There is no execution running with that id
|
||||
return;
|
||||
}
|
||||
this.eventService.emit('execution-cancelled', { executionId });
|
||||
|
||||
const workflowData = execution.executionData.workflowData;
|
||||
this.eventService.emit('execution-cancelled', {
|
||||
executionId,
|
||||
workflowId: workflowData?.id,
|
||||
workflowName: workflowData?.name,
|
||||
reason: cancellationError.reason,
|
||||
});
|
||||
execution.responsePromise?.reject(cancellationError);
|
||||
if (execution.status === 'waiting') {
|
||||
// A waiting execution will not have a valid workflowExecution or postExecutePromise
|
||||
|
||||
@@ -47,6 +47,7 @@ export const eventNamesWorkflow = [
|
||||
'n8n.workflow.started',
|
||||
'n8n.workflow.success',
|
||||
'n8n.workflow.failed',
|
||||
'n8n.workflow.cancelled',
|
||||
] as const;
|
||||
export const eventNamesGeneric = ['n8n.worker.started', 'n8n.worker.stopped'] as const;
|
||||
export const eventNamesNode = ['n8n.node.started', 'n8n.node.finished'] as const;
|
||||
|
||||
@@ -1083,6 +1083,30 @@ describe('LogStreamingEventRelay', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['manual', 'timeout', 'shutdown'] as const)(
|
||||
'should log on `execution-cancelled` event with %s reason',
|
||||
(reason) => {
|
||||
const event: RelayEventMap['execution-cancelled'] = {
|
||||
executionId: 'exec-cancelled-123',
|
||||
workflowId: 'wf-456',
|
||||
workflowName: 'Cancelled Workflow',
|
||||
reason,
|
||||
};
|
||||
|
||||
eventService.emit('execution-cancelled', event);
|
||||
|
||||
expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({
|
||||
eventName: 'n8n.workflow.cancelled',
|
||||
payload: {
|
||||
executionId: 'exec-cancelled-123',
|
||||
workflowId: 'wf-456',
|
||||
workflowName: 'Cancelled Workflow',
|
||||
reason,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('AI events', () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AuthenticationMethod, ProjectRelation } from '@n8n/api-types';
|
||||
import type { AuthProviderType, User, IWorkflowDb } from '@n8n/db';
|
||||
import type {
|
||||
CancellationReason,
|
||||
IPersonalizationSurveyAnswersV4,
|
||||
IRun,
|
||||
IWorkflowBase,
|
||||
@@ -395,6 +396,9 @@ export type RelayEventMap = {
|
||||
|
||||
'execution-cancelled': {
|
||||
executionId: string;
|
||||
workflowId?: string;
|
||||
workflowName?: string;
|
||||
reason: CancellationReason;
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -51,6 +51,7 @@ export class LogStreamingEventRelay extends EventRelay {
|
||||
'community-package-deleted': (event) => this.communityPackageDeleted(event),
|
||||
'execution-throttled': (event) => this.executionThrottled(event),
|
||||
'execution-started-during-bootup': (event) => this.executionStartedDuringBootup(event),
|
||||
'execution-cancelled': (event) => this.executionCancelled(event),
|
||||
'ai-messages-retrieved-from-memory': (event) => this.aiMessagesRetrievedFromMemory(event),
|
||||
'ai-message-added-to-memory': (event) => this.aiMessageAddedToMemory(event),
|
||||
'ai-output-parsed': (event) => this.aiOutputParsed(event),
|
||||
@@ -465,6 +466,23 @@ export class LogStreamingEventRelay extends EventRelay {
|
||||
});
|
||||
}
|
||||
|
||||
private executionCancelled({
|
||||
executionId,
|
||||
workflowId,
|
||||
workflowName,
|
||||
reason,
|
||||
}: RelayEventMap['execution-cancelled']) {
|
||||
void this.eventBus.sendWorkflowEvent({
|
||||
eventName: 'n8n.workflow.cancelled',
|
||||
payload: {
|
||||
executionId,
|
||||
workflowId,
|
||||
workflowName,
|
||||
reason,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region AI
|
||||
|
||||
@@ -287,8 +287,13 @@ export class JobProcessor {
|
||||
const runningJob = this.runningJobs[jobId];
|
||||
if (!runningJob) return;
|
||||
|
||||
const executionId = runningJob.executionId;
|
||||
this.eventService.emit('execution-cancelled', { executionId });
|
||||
const { executionId, workflowId, workflowName } = runningJob;
|
||||
this.eventService.emit('execution-cancelled', {
|
||||
executionId,
|
||||
workflowId,
|
||||
workflowName,
|
||||
reason: 'manual', // Job stops via scaling service are always user-initiated
|
||||
});
|
||||
|
||||
runningJob.run.cancel();
|
||||
delete this.runningJobs[jobId];
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
import { ExecutionBaseError } from './abstract/execution-base.error';
|
||||
|
||||
export type CancellationReason = 'manual' | 'timeout' | 'shutdown';
|
||||
|
||||
export abstract class ExecutionCancelledError extends ExecutionBaseError {
|
||||
readonly reason: CancellationReason;
|
||||
|
||||
// NOTE: prefer one of the more specific
|
||||
constructor(executionId: string) {
|
||||
constructor(executionId: string, reason: CancellationReason) {
|
||||
super('The execution was cancelled', {
|
||||
level: 'warning',
|
||||
extra: { executionId },
|
||||
});
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
export class ManualExecutionCancelledError extends ExecutionCancelledError {
|
||||
constructor(executionId: string) {
|
||||
super(executionId);
|
||||
super(executionId, 'manual');
|
||||
this.message = 'The execution was cancelled manually';
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeoutExecutionCancelledError extends ExecutionCancelledError {
|
||||
constructor(executionId: string) {
|
||||
super(executionId);
|
||||
super(executionId, 'timeout');
|
||||
this.message = 'The execution was cancelled because it timed out';
|
||||
}
|
||||
}
|
||||
|
||||
export class SystemShutdownExecutionCancelledError extends ExecutionCancelledError {
|
||||
constructor(executionId: string) {
|
||||
super(executionId);
|
||||
super(executionId, 'shutdown');
|
||||
this.message = 'The execution was cancelled because the system is shutting down';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export {
|
||||
ManualExecutionCancelledError,
|
||||
SystemShutdownExecutionCancelledError,
|
||||
TimeoutExecutionCancelledError,
|
||||
type CancellationReason,
|
||||
} from './execution-cancelled.error';
|
||||
export { NodeApiError } from './node-api.error';
|
||||
export { NodeOperationError } from './node-operation.error';
|
||||
|
||||
Reference in New Issue
Block a user