mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2026-09-23 10:30:42 +02:00
refactor(core): Move ExecutionLifecycleHooks to core (#13042)
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteResponsePromiseData,
|
||||
INode,
|
||||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IWorkflowBase,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type {
|
||||
ExecutionLifecycleHookName,
|
||||
ExecutionLifecyleHookHandlers,
|
||||
} from '../execution-lifecycle-hooks';
|
||||
import { ExecutionLifecycleHooks } from '../execution-lifecycle-hooks';
|
||||
|
||||
describe('ExecutionLifecycleHooks', () => {
|
||||
const executionId = '123';
|
||||
const workflowData = mock<IWorkflowBase>();
|
||||
|
||||
let hooks: ExecutionLifecycleHooks;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
hooks = new ExecutionLifecycleHooks('internal', executionId, workflowData);
|
||||
});
|
||||
|
||||
describe('constructor()', () => {
|
||||
it('should initialize with correct properties', () => {
|
||||
expect(hooks.mode).toBe('internal');
|
||||
expect(hooks.executionId).toBe(executionId);
|
||||
expect(hooks.workflowData).toBe(workflowData);
|
||||
expect(hooks.handlers).toEqual({
|
||||
nodeExecuteAfter: [],
|
||||
nodeExecuteBefore: [],
|
||||
nodeFetchedData: [],
|
||||
sendResponse: [],
|
||||
workflowExecuteAfter: [],
|
||||
workflowExecuteBefore: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addHandler()', () => {
|
||||
const hooksHandlers =
|
||||
mock<{
|
||||
[K in keyof ExecutionLifecyleHookHandlers]: ExecutionLifecyleHookHandlers[K][number];
|
||||
}>();
|
||||
|
||||
const testCases: Array<{
|
||||
hook: ExecutionLifecycleHookName;
|
||||
args: Parameters<ExecutionLifecyleHookHandlers[keyof ExecutionLifecyleHookHandlers][number]>;
|
||||
}> = [
|
||||
{ hook: 'nodeExecuteBefore', args: ['testNode'] },
|
||||
{
|
||||
hook: 'nodeExecuteAfter',
|
||||
args: ['testNode', mock<ITaskData>(), mock<IRunExecutionData>()],
|
||||
},
|
||||
{ hook: 'workflowExecuteBefore', args: [mock<Workflow>(), mock<IRunExecutionData>()] },
|
||||
{ hook: 'workflowExecuteAfter', args: [mock<IRun>(), mock<IDataObject>()] },
|
||||
{ hook: 'sendResponse', args: [mock<IExecuteResponsePromiseData>()] },
|
||||
{ hook: 'nodeFetchedData', args: ['workflow123', mock<INode>()] },
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
'should add handlers to $hook hook and call them',
|
||||
async ({ hook, args }) => {
|
||||
hooks.addHandler(hook, hooksHandlers[hook]);
|
||||
await hooks.runHook(hook, args);
|
||||
expect(hooksHandlers[hook]).toHaveBeenCalledWith(...args);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('runHook()', () => {
|
||||
it('should execute multiple hooks in order', async () => {
|
||||
const executionOrder: string[] = [];
|
||||
const hook1 = jest.fn().mockImplementation(async () => {
|
||||
executionOrder.push('hook1');
|
||||
});
|
||||
const hook2 = jest.fn().mockImplementation(async () => {
|
||||
executionOrder.push('hook2');
|
||||
});
|
||||
|
||||
hooks.addHandler('nodeExecuteBefore', hook1, hook2);
|
||||
await hooks.runHook('nodeExecuteBefore', ['testNode']);
|
||||
|
||||
expect(executionOrder).toEqual(['hook1', 'hook2']);
|
||||
expect(hook1).toHaveBeenCalled();
|
||||
expect(hook2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should maintain correct "this" context', async () => {
|
||||
const hook = jest.fn().mockImplementation(async function (this: ExecutionLifecycleHooks) {
|
||||
expect(this.executionId).toBe(executionId);
|
||||
expect(this.mode).toBe('internal');
|
||||
});
|
||||
|
||||
hooks.addHandler('nodeExecuteBefore', hook);
|
||||
await hooks.runHook('nodeExecuteBefore', ['testNode']);
|
||||
|
||||
expect(hook).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle errors in hooks', async () => {
|
||||
const errorHook = jest.fn().mockRejectedValue(new Error('Hook failed'));
|
||||
hooks.addHandler('nodeExecuteBefore', errorHook);
|
||||
|
||||
await expect(hooks.runHook('nodeExecuteBefore', ['testNode'])).rejects.toThrow('Hook failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,10 +9,10 @@ import type {
|
||||
INodeType,
|
||||
INodeTypes,
|
||||
ITriggerFunctions,
|
||||
WorkflowHooks,
|
||||
IRun,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { ExecutionLifecycleHooks } from '../execution-lifecycle-hooks';
|
||||
import { TriggersAndPollers } from '../triggers-and-pollers';
|
||||
|
||||
describe('TriggersAndPollers', () => {
|
||||
@@ -23,15 +23,8 @@ describe('TriggersAndPollers', () => {
|
||||
});
|
||||
const nodeTypes = mock<INodeTypes>();
|
||||
const workflow = mock<Workflow>({ nodeTypes });
|
||||
const hookFunctions = mock<WorkflowHooks['hookFunctions']>({
|
||||
sendResponse: [],
|
||||
workflowExecuteAfter: [],
|
||||
});
|
||||
const additionalData = mock<IWorkflowExecuteAdditionalData>({
|
||||
hooks: {
|
||||
hookFunctions,
|
||||
},
|
||||
});
|
||||
const hooks = new ExecutionLifecycleHooks('internal', '123', mock());
|
||||
const additionalData = mock<IWorkflowExecuteAdditionalData>({ hooks });
|
||||
const triggersAndPollers = new TriggersAndPollers();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -98,8 +91,7 @@ describe('TriggersAndPollers', () => {
|
||||
|
||||
getMockTriggerFunctions()?.emit?.(mockEmitData, responsePromise);
|
||||
|
||||
expect(hookFunctions.sendResponse?.length).toBe(1);
|
||||
await hookFunctions.sendResponse![0]?.({ testResponse: true });
|
||||
await hooks.runHook('sendResponse', [{ testResponse: true }]);
|
||||
expect(responsePromise.resolve).toHaveBeenCalledWith({ testResponse: true });
|
||||
});
|
||||
|
||||
@@ -111,10 +103,10 @@ describe('TriggersAndPollers', () => {
|
||||
await runTriggerHelper('manual');
|
||||
getMockTriggerFunctions()?.emit?.(mockEmitData, responsePromise, donePromise);
|
||||
|
||||
await hookFunctions.sendResponse![0]?.({ testResponse: true });
|
||||
await hooks.runHook('sendResponse', [{ testResponse: true }]);
|
||||
expect(responsePromise.resolve).toHaveBeenCalledWith({ testResponse: true });
|
||||
|
||||
await hookFunctions.workflowExecuteAfter?.[0]?.(mockRunData, {});
|
||||
await hooks.runHook('workflowExecuteAfter', [mockRunData, {}]);
|
||||
expect(donePromise.resolve).toHaveBeenCalledWith(mockRunData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import * as Helpers from '@test/helpers';
|
||||
import { legacyWorkflowExecuteTests, v1WorkflowExecuteTests } from '@test/helpers/constants';
|
||||
|
||||
import type { ExecutionLifecycleHooks } from '../execution-lifecycle-hooks';
|
||||
import { DirectedGraph } from '../partial-execution-utils';
|
||||
import * as partialExecutionUtils from '../partial-execution-utils';
|
||||
import { createNodeData, toITaskData } from '../partial-execution-utils/__tests__/helpers';
|
||||
@@ -1211,6 +1212,7 @@ describe('WorkflowExecute', () => {
|
||||
|
||||
let runExecutionData: IRunExecutionData;
|
||||
let workflowExecute: WorkflowExecute;
|
||||
let additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
beforeEach(() => {
|
||||
runExecutionData = {
|
||||
@@ -1224,9 +1226,12 @@ describe('WorkflowExecute', () => {
|
||||
waitingExecutionSource: null,
|
||||
},
|
||||
};
|
||||
workflowExecute = new WorkflowExecute(mock(), 'manual', runExecutionData);
|
||||
additionalData = mock();
|
||||
additionalData.hooks = mock<ExecutionLifecycleHooks>();
|
||||
|
||||
jest.spyOn(workflowExecute, 'executeHook').mockResolvedValue(undefined);
|
||||
workflowExecute = new WorkflowExecute(additionalData, 'manual', runExecutionData);
|
||||
|
||||
jest.spyOn(additionalData.hooks, 'runHook').mockResolvedValue(undefined);
|
||||
jest.spyOn(workflowExecute, 'moveNodeMetadata').mockImplementation();
|
||||
});
|
||||
|
||||
@@ -1294,7 +1299,7 @@ describe('WorkflowExecute', () => {
|
||||
// Verify static data handling
|
||||
expect(result).toBeDefined();
|
||||
expect(workflowExecute.moveNodeMetadata).toHaveBeenCalled();
|
||||
expect(workflowExecute.executeHook).toHaveBeenCalledWith('workflowExecuteAfter', [
|
||||
expect(additionalData.hooks?.runHook).toHaveBeenCalledWith('workflowExecuteAfter', [
|
||||
result,
|
||||
workflow.staticData,
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteResponsePromiseData,
|
||||
INode,
|
||||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IWorkflowBase,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export type ExecutionLifecyleHookHandlers = {
|
||||
nodeExecuteBefore: Array<
|
||||
(this: ExecutionLifecycleHooks, nodeName: string) => Promise<void> | void
|
||||
>;
|
||||
|
||||
nodeExecuteAfter: Array<
|
||||
(
|
||||
this: ExecutionLifecycleHooks,
|
||||
nodeName: string,
|
||||
data: ITaskData,
|
||||
executionData: IRunExecutionData,
|
||||
) => Promise<void> | void
|
||||
>;
|
||||
|
||||
workflowExecuteBefore: Array<
|
||||
(
|
||||
this: ExecutionLifecycleHooks,
|
||||
workflow: Workflow,
|
||||
data?: IRunExecutionData,
|
||||
) => Promise<void> | void
|
||||
>;
|
||||
|
||||
workflowExecuteAfter: Array<
|
||||
(this: ExecutionLifecycleHooks, data: IRun, newStaticData: IDataObject) => Promise<void> | void
|
||||
>;
|
||||
|
||||
/** Used by trigger and webhook nodes to respond back to the request */
|
||||
sendResponse: Array<
|
||||
(this: ExecutionLifecycleHooks, response: IExecuteResponsePromiseData) => Promise<void> | void
|
||||
>;
|
||||
|
||||
/**
|
||||
* Executed after a node fetches data
|
||||
* - For a webhook node, after the node had been run.
|
||||
* - For a http-request node, or any other node that makes http requests that still use the deprecated request* methods, after every successful http request
|
||||
s */
|
||||
nodeFetchedData: Array<
|
||||
(this: ExecutionLifecycleHooks, workflowId: string, node: INode) => Promise<void> | void
|
||||
>;
|
||||
};
|
||||
|
||||
export type ExecutionLifecycleHookName = keyof ExecutionLifecyleHookHandlers;
|
||||
|
||||
/**
|
||||
* Contains hooks that trigger at specific events in an execution's lifecycle. Every hook has an array of callbacks to run.
|
||||
*
|
||||
* Common use cases include:
|
||||
* - Saving execution progress to database
|
||||
* - Pushing execution status updates to the frontend
|
||||
* - Recording workflow statistics
|
||||
* - Running external hooks for execution events
|
||||
* - Error and Cancellation handling and cleanup
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hooks = new ExecutionLifecycleHooks(mode, executionId, workflowData);
|
||||
* hooks.add('workflowExecuteAfter, async function(fullRunData) {
|
||||
* await saveToDatabase(executionId, fullRunData);
|
||||
*});
|
||||
* ```
|
||||
*/
|
||||
export class ExecutionLifecycleHooks {
|
||||
readonly handlers: ExecutionLifecyleHookHandlers = {
|
||||
nodeExecuteAfter: [],
|
||||
nodeExecuteBefore: [],
|
||||
nodeFetchedData: [],
|
||||
sendResponse: [],
|
||||
workflowExecuteAfter: [],
|
||||
workflowExecuteBefore: [],
|
||||
};
|
||||
|
||||
constructor(
|
||||
readonly mode: WorkflowExecuteMode,
|
||||
readonly executionId: string,
|
||||
readonly workflowData: IWorkflowBase,
|
||||
) {}
|
||||
|
||||
addHandler<Hook extends keyof ExecutionLifecyleHookHandlers>(
|
||||
hookName: Hook,
|
||||
...handlers: Array<ExecutionLifecyleHookHandlers[Hook][number]>
|
||||
): void {
|
||||
// @ts-expect-error FIX THIS
|
||||
this.handlers[hookName].push(...handlers);
|
||||
}
|
||||
|
||||
async runHook<
|
||||
Hook extends keyof ExecutionLifecyleHookHandlers,
|
||||
Params extends unknown[] = Parameters<
|
||||
Exclude<ExecutionLifecyleHookHandlers[Hook], undefined>[number]
|
||||
>,
|
||||
>(hookName: Hook, parameters: Params) {
|
||||
const hooks = this.handlers[hookName];
|
||||
for (const hookFunction of hooks) {
|
||||
const typedHookFunction = hookFunction as unknown as (
|
||||
this: ExecutionLifecycleHooks,
|
||||
...args: Params
|
||||
) => Promise<void>;
|
||||
await typedHookFunction.apply(this, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'n8n-workflow' {
|
||||
interface IWorkflowExecuteAdditionalData {
|
||||
hooks?: ExecutionLifecycleHooks;
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,4 @@ export * from './node-execution-context';
|
||||
export * from './partial-execution-utils';
|
||||
export * from './node-execution-context/utils/execution-metadata';
|
||||
export * from './workflow-execute';
|
||||
export { ExecutionLifecycleHooks } from './execution-lifecycle-hooks';
|
||||
|
||||
@@ -194,7 +194,7 @@ export class ExecuteContext extends BaseExecuteContext implements IExecuteFuncti
|
||||
}
|
||||
|
||||
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
|
||||
await this.additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
|
||||
await this.additionalData.hooks?.runHook('sendResponse', [response]);
|
||||
}
|
||||
|
||||
/** @deprecated use ISupplyDataFunctions.addInputData */
|
||||
|
||||
@@ -258,12 +258,12 @@ export class SupplyDataContext extends BaseExecuteContext implements ISupplyData
|
||||
}
|
||||
|
||||
runExecutionData.resultData.runData[nodeName][currentNodeRunIndex] = taskData;
|
||||
await additionalData.hooks?.executeHookFunctions('nodeExecuteBefore', [nodeName]);
|
||||
await additionalData.hooks?.runHook('nodeExecuteBefore', [nodeName]);
|
||||
} else {
|
||||
// Outputs
|
||||
taskData.executionTime = new Date().getTime() - taskData.startTime;
|
||||
|
||||
await additionalData.hooks?.executeHookFunctions('nodeExecuteAfter', [
|
||||
await additionalData.hooks?.runHook('nodeExecuteAfter', [
|
||||
nodeName,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
IExecuteResponsePromiseData,
|
||||
IRun,
|
||||
} from 'n8n-workflow';
|
||||
import assert from 'node:assert';
|
||||
|
||||
import type { IGetExecuteTriggerFunctions } from './interfaces';
|
||||
|
||||
@@ -47,46 +48,34 @@ export class TriggersAndPollers {
|
||||
|
||||
// Add the manual trigger response which resolves when the first time data got emitted
|
||||
triggerResponse!.manualTriggerResponse = new Promise((resolve, reject) => {
|
||||
const { hooks } = additionalData;
|
||||
assert.ok(hooks, 'Execution lifecycle hooks are not defined');
|
||||
|
||||
triggerFunctions.emit = (
|
||||
(resolveEmit) =>
|
||||
(
|
||||
data: INodeExecutionData[][],
|
||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||
donePromise?: IDeferredPromise<IRun>,
|
||||
) => {
|
||||
additionalData.hooks!.hookFunctions.sendResponse = [
|
||||
async (response: IExecuteResponsePromiseData): Promise<void> => {
|
||||
if (responsePromise) {
|
||||
responsePromise.resolve(response);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if (donePromise) {
|
||||
additionalData.hooks!.hookFunctions.workflowExecuteAfter?.unshift(
|
||||
async (runData: IRun): Promise<void> => {
|
||||
return donePromise.resolve(runData);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
resolveEmit(data);
|
||||
data: INodeExecutionData[][],
|
||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||
donePromise?: IDeferredPromise<IRun>,
|
||||
) => {
|
||||
if (responsePromise) {
|
||||
hooks.addHandler('sendResponse', (response) => responsePromise.resolve(response));
|
||||
}
|
||||
)(resolve);
|
||||
|
||||
if (donePromise) {
|
||||
hooks.addHandler('workflowExecuteAfter', (runData) => donePromise.resolve(runData));
|
||||
}
|
||||
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
triggerFunctions.emitError = (
|
||||
(rejectEmit) =>
|
||||
(error: Error, responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>) => {
|
||||
additionalData.hooks!.hookFunctions.sendResponse = [
|
||||
async (): Promise<void> => {
|
||||
if (responsePromise) {
|
||||
responsePromise.reject(error);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
rejectEmit(error);
|
||||
error: Error,
|
||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||
) => {
|
||||
if (responsePromise) {
|
||||
hooks.addHandler('sendResponse', () => responsePromise.reject(error));
|
||||
}
|
||||
)(reject);
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
|
||||
return triggerResponse;
|
||||
|
||||
@@ -405,19 +405,6 @@ export class WorkflowExecute {
|
||||
return this.processRunExecutionData(graph.toWorkflow({ ...workflow }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the hook with the given name
|
||||
*
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> {
|
||||
if (this.additionalData.hooks === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges temporary execution metadata into the final runData structure.
|
||||
* During workflow execution, metadata is collected in a temporary location
|
||||
@@ -1207,11 +1194,14 @@ export class WorkflowExecute {
|
||||
|
||||
this.status = 'running';
|
||||
|
||||
const { hooks, executionId } = this.additionalData;
|
||||
assert.ok(hooks, 'Failed to run workflow due to missing execution lifecycle hooks');
|
||||
|
||||
if (!this.runExecutionData.executionData) {
|
||||
throw new ApplicationError('Failed to run workflow due to missing execution data', {
|
||||
extra: {
|
||||
workflowId: workflow.id,
|
||||
executionid: this.additionalData.executionId,
|
||||
executionId,
|
||||
mode: this.mode,
|
||||
},
|
||||
});
|
||||
@@ -1269,14 +1259,14 @@ export class WorkflowExecute {
|
||||
this.status = 'canceled';
|
||||
this.abortController.abort();
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
void this.executeHook('workflowExecuteAfter', [fullRunData]);
|
||||
void hooks.runHook('workflowExecuteAfter', [fullRunData]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const returnPromise = (async () => {
|
||||
try {
|
||||
if (!this.additionalData.restartExecutionId) {
|
||||
await this.executeHook('workflowExecuteBefore', [workflow, this.runExecutionData]);
|
||||
await hooks.runHook('workflowExecuteBefore', [workflow, this.runExecutionData]);
|
||||
}
|
||||
} catch (error) {
|
||||
const e = error as unknown as ExecutionBaseError;
|
||||
@@ -1360,7 +1350,7 @@ export class WorkflowExecute {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
await hooks.runHook('nodeExecuteBefore', [executionNode.name]);
|
||||
|
||||
// Get the index of the current run
|
||||
runIndex = 0;
|
||||
@@ -1651,7 +1641,7 @@ export class WorkflowExecute {
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
// Only execute the nodeExecuteAfter hook if the node did not get aborted
|
||||
if (!this.isCancelled) {
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
await hooks.runHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
@@ -1693,7 +1683,7 @@ export class WorkflowExecute {
|
||||
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||
|
||||
if (this.runExecutionData.waitTill) {
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
await hooks.runHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
@@ -1712,7 +1702,7 @@ export class WorkflowExecute {
|
||||
) {
|
||||
// Before stopping, make sure we are executing hooks so
|
||||
// That frontend is notified for example for manual executions.
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
await hooks.runHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
@@ -1822,7 +1812,7 @@ export class WorkflowExecute {
|
||||
// Execute hooks now to make sure that all hooks are executed properly
|
||||
// Await is needed to make sure that we don't fall into concurrency problems
|
||||
// When saving node execution data
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
await hooks.runHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
@@ -2025,7 +2015,7 @@ export class WorkflowExecute {
|
||||
|
||||
this.moveNodeMetadata();
|
||||
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
|
||||
await hooks.runHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
(error) => {
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
@@ -2118,7 +2108,10 @@ export class WorkflowExecute {
|
||||
this.moveNodeMetadata();
|
||||
// Prevent from running the hook if the error is an abort error as it was already handled
|
||||
if (!this.isCancelled) {
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]);
|
||||
await this.additionalData.hooks?.runHook('workflowExecuteAfter', [
|
||||
fullRunData,
|
||||
newStaticData,
|
||||
]);
|
||||
}
|
||||
|
||||
if (closeFunction) {
|
||||
|
||||
Reference in New Issue
Block a user