refactor(core): Move ExecutionLifecycleHooks to core (#13042)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2025-02-07 18:16:37 +01:00
committed by GitHub
parent cae98e733d
commit d41ca832dc
24 changed files with 911 additions and 886 deletions

View File

@@ -1,18 +1,10 @@
import { Container } from '@n8n/di';
import { stringify } from 'flatted';
import { ErrorReporter, Logger, InstanceSettings } from 'n8n-core';
import { WorkflowHooks } from 'n8n-workflow';
import { ErrorReporter, Logger, InstanceSettings, ExecutionLifecycleHooks } from 'n8n-core';
import type {
IDataObject,
INode,
IRun,
IRunExecutionData,
ITaskData,
IWorkflowBase,
IWorkflowExecuteHooks,
WorkflowExecuteMode,
IWorkflowExecutionDataProcess,
Workflow,
} from 'n8n-workflow';
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
@@ -39,339 +31,255 @@ type HooksSetupParameters = {
retryOf?: string;
};
function mergeHookFunctions(...hookFunctions: IWorkflowExecuteHooks[]): IWorkflowExecuteHooks {
const result: IWorkflowExecuteHooks = {
nodeExecuteBefore: [],
nodeExecuteAfter: [],
workflowExecuteBefore: [],
workflowExecuteAfter: [],
sendResponse: [],
nodeFetchedData: [],
};
for (const hooks of hookFunctions) {
for (const key in hooks) {
if (!result[key] || !hooks[key]) continue;
result[key].push(...hooks[key]);
}
}
return result;
function hookFunctionsWorkflowEvents(hooks: ExecutionLifecycleHooks, userId?: string) {
const eventService = Container.get(EventService);
hooks.addHandler('workflowExecuteBefore', function () {
const { executionId, workflowData } = this;
eventService.emit('workflow-pre-execute', { executionId, data: workflowData });
});
hooks.addHandler('workflowExecuteAfter', function (runData) {
if (runData.status === 'waiting') return;
const { executionId, workflowData: workflow } = this;
eventService.emit('workflow-post-execute', { executionId, runData, workflow, userId });
});
}
function hookFunctionsWorkflowEvents(userId?: string): IWorkflowExecuteHooks {
function hookFunctionsNodeEvents(hooks: ExecutionLifecycleHooks) {
const eventService = Container.get(EventService);
return {
workflowExecuteBefore: [
async function (this: WorkflowHooks): Promise<void> {
const { executionId, workflowData } = this;
eventService.emit('workflow-pre-execute', { executionId, data: workflowData });
},
],
workflowExecuteAfter: [
async function (this: WorkflowHooks, runData: IRun): Promise<void> {
if (runData.status === 'waiting') return;
const { executionId, workflowData: workflow } = this;
eventService.emit('workflow-post-execute', { executionId, runData, workflow, userId });
},
],
};
}
function hookFunctionsNodeEvents(): IWorkflowExecuteHooks {
const eventService = Container.get(EventService);
return {
nodeExecuteBefore: [
async function (this: WorkflowHooks, nodeName: string): Promise<void> {
const { executionId, workflowData: workflow } = this;
eventService.emit('node-pre-execute', { executionId, workflow, nodeName });
},
],
nodeExecuteAfter: [
async function (this: WorkflowHooks, nodeName: string): Promise<void> {
const { executionId, workflowData: workflow } = this;
eventService.emit('node-post-execute', { executionId, workflow, nodeName });
},
],
};
hooks.addHandler('nodeExecuteBefore', function (nodeName) {
const { executionId, workflowData: workflow } = this;
eventService.emit('node-pre-execute', { executionId, workflow, nodeName });
});
hooks.addHandler('nodeExecuteAfter', function (nodeName) {
const { executionId, workflowData: workflow } = this;
eventService.emit('node-post-execute', { executionId, workflow, nodeName });
});
}
/**
* Returns hook functions to push data to Editor-UI
*/
function hookFunctionsPush({ pushRef, retryOf }: HooksSetupParameters): IWorkflowExecuteHooks {
if (!pushRef) return {};
function hookFunctionsPush(
hooks: ExecutionLifecycleHooks,
{ pushRef, retryOf }: HooksSetupParameters,
) {
if (!pushRef) return;
const logger = Container.get(Logger);
const pushInstance = Container.get(Push);
return {
nodeExecuteBefore: [
async function (this: WorkflowHooks, nodeName: string): Promise<void> {
const { executionId } = this;
// Push data to session which started workflow before each
// node which starts rendering
logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
executionId,
pushRef,
workflowId: this.workflowData.id,
});
hooks.addHandler('nodeExecuteBefore', function (nodeName) {
const { executionId } = this;
// Push data to session which started workflow before each
// node which starts rendering
logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
executionId,
pushRef,
workflowId: this.workflowData.id,
});
pushInstance.send({ type: 'nodeExecuteBefore', data: { executionId, nodeName } }, pushRef);
},
],
nodeExecuteAfter: [
async function (this: WorkflowHooks, nodeName: string, data: ITaskData): Promise<void> {
const { executionId } = this;
// Push data to session which started workflow after each rendered node
logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
executionId,
pushRef,
workflowId: this.workflowData.id,
});
pushInstance.send({ type: 'nodeExecuteBefore', data: { executionId, nodeName } }, pushRef);
});
hooks.addHandler('nodeExecuteAfter', function (nodeName, data) {
const { executionId } = this;
// Push data to session which started workflow after each rendered node
logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
executionId,
pushRef,
workflowId: this.workflowData.id,
});
pushInstance.send(
{ type: 'nodeExecuteAfter', data: { executionId, nodeName, data } },
pushRef,
);
},
],
workflowExecuteBefore: [
async function (this: WorkflowHooks, _workflow, data): Promise<void> {
const { executionId } = this;
const { id: workflowId, name: workflowName } = this.workflowData;
logger.debug('Executing hook (hookFunctionsPush)', {
pushInstance.send({ type: 'nodeExecuteAfter', data: { executionId, nodeName, data } }, pushRef);
});
hooks.addHandler('workflowExecuteBefore', function (_workflow, data) {
const { executionId } = this;
const { id: workflowId, name: workflowName } = this.workflowData;
logger.debug('Executing hook (hookFunctionsPush)', {
executionId,
pushRef,
workflowId,
});
// Push data to session which started the workflow
pushInstance.send(
{
type: 'executionStarted',
data: {
executionId,
pushRef,
mode: this.mode,
startedAt: new Date(),
retryOf,
workflowId,
});
// Push data to session which started the workflow
pushInstance.send(
{
type: 'executionStarted',
data: {
executionId,
mode: this.mode,
startedAt: new Date(),
retryOf,
workflowId,
workflowName,
flattedRunData: data?.resultData.runData
? stringify(data.resultData.runData)
: stringify({}),
},
},
pushRef,
);
workflowName,
flattedRunData: data?.resultData.runData
? stringify(data.resultData.runData)
: stringify({}),
},
},
],
workflowExecuteAfter: [
async function (this: WorkflowHooks, fullRunData: IRun): Promise<void> {
const { executionId } = this;
const { id: workflowId } = this.workflowData;
logger.debug('Executing hook (hookFunctionsPush)', {
executionId,
pushRef,
workflowId,
});
pushRef,
);
});
hooks.addHandler('workflowExecuteAfter', function (fullRunData) {
const { executionId } = this;
const { id: workflowId } = this.workflowData;
logger.debug('Executing hook (hookFunctionsPush)', {
executionId,
pushRef,
workflowId,
});
const { status } = fullRunData;
if (status === 'waiting') {
pushInstance.send({ type: 'executionWaiting', data: { executionId } }, pushRef);
} else {
const rawData = stringify(fullRunData.data);
pushInstance.send(
{ type: 'executionFinished', data: { executionId, workflowId, status, rawData } },
pushRef,
);
}
},
],
};
const { status } = fullRunData;
if (status === 'waiting') {
pushInstance.send({ type: 'executionWaiting', data: { executionId } }, pushRef);
} else {
const rawData = stringify(fullRunData.data);
pushInstance.send(
{ type: 'executionFinished', data: { executionId, workflowId, status, rawData } },
pushRef,
);
}
});
}
function hookFunctionsExternalHooks(): IWorkflowExecuteHooks {
function hookFunctionsExternalHooks(hooks: ExecutionLifecycleHooks) {
const externalHooks = Container.get(ExternalHooks);
return {
workflowExecuteBefore: [
async function (this: WorkflowHooks, workflow: Workflow): Promise<void> {
await externalHooks.run('workflow.preExecute', [workflow, this.mode]);
},
],
workflowExecuteAfter: [
async function (this: WorkflowHooks, fullRunData: IRun) {
await externalHooks.run('workflow.postExecute', [
fullRunData,
this.workflowData,
this.executionId,
]);
},
],
};
hooks.addHandler('workflowExecuteBefore', async function (workflow) {
await externalHooks.run('workflow.preExecute', [workflow, this.mode]);
});
hooks.addHandler('workflowExecuteAfter', async function (fullRunData) {
await externalHooks.run('workflow.postExecute', [
fullRunData,
this.workflowData,
this.executionId,
]);
});
}
function hookFunctionsSaveProgress({ saveSettings }: HooksSetupParameters): IWorkflowExecuteHooks {
if (!saveSettings.progress) return {};
return {
nodeExecuteAfter: [
async function (
this: WorkflowHooks,
nodeName: string,
data: ITaskData,
executionData: IRunExecutionData,
): Promise<void> {
await saveExecutionProgress(
this.workflowData.id,
this.executionId,
nodeName,
data,
executionData,
);
},
],
};
function hookFunctionsSaveProgress(
hooks: ExecutionLifecycleHooks,
{ saveSettings }: HooksSetupParameters,
) {
if (!saveSettings.progress) return;
hooks.addHandler('nodeExecuteAfter', async function (nodeName, data, executionData) {
await saveExecutionProgress(
this.workflowData.id,
this.executionId,
nodeName,
data,
executionData,
);
});
}
/** This should ideally be added before any other `workflowExecuteAfter` hook to ensure all hooks get the same execution status */
function hookFunctionsFinalizeExecutionStatus(): IWorkflowExecuteHooks {
return {
workflowExecuteAfter: [
async function (fullRunData: IRun) {
fullRunData.status = determineFinalExecutionStatus(fullRunData);
},
],
};
function hookFunctionsFinalizeExecutionStatus(hooks: ExecutionLifecycleHooks) {
hooks.addHandler('workflowExecuteAfter', (fullRunData) => {
fullRunData.status = determineFinalExecutionStatus(fullRunData);
});
}
function hookFunctionsStatistics(): IWorkflowExecuteHooks {
function hookFunctionsStatistics(hooks: ExecutionLifecycleHooks) {
const workflowStatisticsService = Container.get(WorkflowStatisticsService);
return {
nodeFetchedData: [
async (workflowId: string, node: INode) => {
workflowStatisticsService.emit('nodeFetchedData', { workflowId, node });
},
],
};
hooks.addHandler('nodeFetchedData', (workflowId, node) => {
workflowStatisticsService.emit('nodeFetchedData', { workflowId, node });
});
}
/**
* Returns hook functions to save workflow execution and call error workflow
*/
function hookFunctionsSave({
pushRef,
retryOf,
saveSettings,
}: HooksSetupParameters): IWorkflowExecuteHooks {
function hookFunctionsSave(
hooks: ExecutionLifecycleHooks,
{ pushRef, retryOf, saveSettings }: HooksSetupParameters,
) {
const logger = Container.get(Logger);
const errorReporter = Container.get(ErrorReporter);
const executionRepository = Container.get(ExecutionRepository);
const workflowStaticDataService = Container.get(WorkflowStaticDataService);
const workflowStatisticsService = Container.get(WorkflowStatisticsService);
return {
workflowExecuteAfter: [
async function (
this: WorkflowHooks,
fullRunData: IRun,
newStaticData: IDataObject,
): Promise<void> {
logger.debug('Executing hook (hookFunctionsSave)', {
executionId: this.executionId,
hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) {
logger.debug('Executing hook (hookFunctionsSave)', {
executionId: this.executionId,
workflowId: this.workflowData.id,
});
await restoreBinaryDataId(fullRunData, this.executionId, this.mode);
const isManualMode = this.mode === 'manual';
try {
if (!isManualMode && isWorkflowIdValid(this.workflowData.id) && newStaticData) {
// Workflow is saved so update in database
try {
await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData);
} catch (e) {
errorReporter.error(e);
logger.error(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`,
{ executionId: this.executionId, workflowId: this.workflowData.id },
);
}
}
if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
/**
* When manual executions are not being saved, we only soft-delete
* the execution so that the user can access its binary data
* while building their workflow.
*
* The manual execution and its binary data will be hard-deleted
* on the next pruning cycle after the grace period set by
* `EXECUTIONS_DATA_HARD_DELETE_BUFFER`.
*/
await executionRepository.softDelete(this.executionId);
return;
}
const shouldNotSave =
(fullRunData.status === 'success' && !saveSettings.success) ||
(fullRunData.status !== 'success' && !saveSettings.error);
if (shouldNotSave && !fullRunData.waitTill && !isManualMode) {
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
await executionRepository.hardDelete({
workflowId: this.workflowData.id,
executionId: this.executionId,
});
await restoreBinaryDataId(fullRunData, this.executionId, this.mode);
return;
}
const isManualMode = this.mode === 'manual';
// Although it is treated as IWorkflowBase here, it's being instantiated elsewhere with properties that may be sensitive
// As a result, we should create an IWorkflowBase object with only the data we want to save in it.
const fullExecutionData = prepareExecutionDataForDbUpdate({
runData: fullRunData,
workflowData: this.workflowData,
workflowStatusFinal: fullRunData.status,
retryOf,
});
try {
if (!isManualMode && isWorkflowIdValid(this.workflowData.id) && newStaticData) {
// Workflow is saved so update in database
try {
await workflowStaticDataService.saveStaticDataById(
this.workflowData.id,
newStaticData,
);
} catch (e) {
errorReporter.error(e);
logger.error(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`,
{ executionId: this.executionId, workflowId: this.workflowData.id },
);
}
}
// When going into the waiting state, store the pushRef in the execution-data
if (fullRunData.waitTill && isManualMode) {
fullExecutionData.data.pushRef = pushRef;
}
if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
/**
* When manual executions are not being saved, we only soft-delete
* the execution so that the user can access its binary data
* while building their workflow.
*
* The manual execution and its binary data will be hard-deleted
* on the next pruning cycle after the grace period set by
* `EXECUTIONS_DATA_HARD_DELETE_BUFFER`.
*/
await executionRepository.softDelete(this.executionId);
await updateExistingExecution({
executionId: this.executionId,
workflowId: this.workflowData.id,
executionData: fullExecutionData,
});
return;
}
const shouldNotSave =
(fullRunData.status === 'success' && !saveSettings.success) ||
(fullRunData.status !== 'success' && !saveSettings.error);
if (shouldNotSave && !fullRunData.waitTill && !isManualMode) {
executeErrorWorkflow(
this.workflowData,
fullRunData,
this.mode,
this.executionId,
retryOf,
);
await executionRepository.hardDelete({
workflowId: this.workflowData.id,
executionId: this.executionId,
});
return;
}
// Although it is treated as IWorkflowBase here, it's being instantiated elsewhere with properties that may be sensitive
// As a result, we should create an IWorkflowBase object with only the data we want to save in it.
const fullExecutionData = prepareExecutionDataForDbUpdate({
runData: fullRunData,
workflowData: this.workflowData,
workflowStatusFinal: fullRunData.status,
retryOf,
});
// When going into the waiting state, store the pushRef in the execution-data
if (fullRunData.waitTill && isManualMode) {
fullExecutionData.data.pushRef = pushRef;
}
await updateExistingExecution({
executionId: this.executionId,
workflowId: this.workflowData.id,
executionData: fullExecutionData,
});
if (!isManualMode) {
executeErrorWorkflow(
this.workflowData,
fullRunData,
this.mode,
this.executionId,
retryOf,
);
}
} finally {
workflowStatisticsService.emit('workflowExecutionCompleted', {
workflowData: this.workflowData,
fullRunData,
});
}
},
],
};
if (!isManualMode) {
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
}
} finally {
workflowStatisticsService.emit('workflowExecutionCompleted', {
workflowData: this.workflowData,
fullRunData,
});
}
});
}
/**
@@ -379,224 +287,196 @@ function hookFunctionsSave({
* for running with queues. Manual executions should never run on queues as
* they are always executed in the main process.
*/
function hookFunctionsSaveWorker({
pushRef,
retryOf,
}: HooksSetupParameters): IWorkflowExecuteHooks {
function hookFunctionsSaveWorker(
hooks: ExecutionLifecycleHooks,
{ pushRef, retryOf }: HooksSetupParameters,
) {
const logger = Container.get(Logger);
const errorReporter = Container.get(ErrorReporter);
const workflowStaticDataService = Container.get(WorkflowStaticDataService);
const workflowStatisticsService = Container.get(WorkflowStatisticsService);
return {
workflowExecuteAfter: [
async function (
this: WorkflowHooks,
fullRunData: IRun,
newStaticData: IDataObject,
): Promise<void> {
logger.debug('Executing hook (hookFunctionsSaveWorker)', {
executionId: this.executionId,
workflowId: this.workflowData.id,
});
hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) {
logger.debug('Executing hook (hookFunctionsSaveWorker)', {
executionId: this.executionId,
workflowId: this.workflowData.id,
});
const isManualMode = this.mode === 'manual';
const isManualMode = this.mode === 'manual';
try {
if (!isManualMode && isWorkflowIdValid(this.workflowData.id) && newStaticData) {
// Workflow is saved so update in database
try {
if (!isManualMode && isWorkflowIdValid(this.workflowData.id) && newStaticData) {
// Workflow is saved so update in database
try {
await workflowStaticDataService.saveStaticDataById(
this.workflowData.id,
newStaticData,
);
} catch (e) {
errorReporter.error(e);
logger.error(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`,
{ workflowId: this.workflowData.id },
);
}
}
if (
!isManualMode &&
fullRunData.status !== 'success' &&
fullRunData.status !== 'waiting'
) {
executeErrorWorkflow(
this.workflowData,
fullRunData,
this.mode,
this.executionId,
retryOf,
);
}
// Although it is treated as IWorkflowBase here, it's being instantiated elsewhere with properties that may be sensitive
// As a result, we should create an IWorkflowBase object with only the data we want to save in it.
const fullExecutionData = prepareExecutionDataForDbUpdate({
runData: fullRunData,
workflowData: this.workflowData,
workflowStatusFinal: fullRunData.status,
retryOf,
});
// When going into the waiting state, store the pushRef in the execution-data
if (fullRunData.waitTill && isManualMode) {
fullExecutionData.data.pushRef = pushRef;
}
await updateExistingExecution({
executionId: this.executionId,
workflowId: this.workflowData.id,
executionData: fullExecutionData,
});
} finally {
workflowStatisticsService.emit('workflowExecutionCompleted', {
workflowData: this.workflowData,
fullRunData,
});
await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData);
} catch (e) {
errorReporter.error(e);
logger.error(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`,
{ workflowId: this.workflowData.id },
);
}
},
],
};
}
if (!isManualMode && fullRunData.status !== 'success' && fullRunData.status !== 'waiting') {
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
}
// Although it is treated as IWorkflowBase here, it's being instantiated elsewhere with properties that may be sensitive
// As a result, we should create an IWorkflowBase object with only the data we want to save in it.
const fullExecutionData = prepareExecutionDataForDbUpdate({
runData: fullRunData,
workflowData: this.workflowData,
workflowStatusFinal: fullRunData.status,
retryOf,
});
// When going into the waiting state, store the pushRef in the execution-data
if (fullRunData.waitTill && isManualMode) {
fullExecutionData.data.pushRef = pushRef;
}
await updateExistingExecution({
executionId: this.executionId,
workflowId: this.workflowData.id,
executionData: fullExecutionData,
});
} finally {
workflowStatisticsService.emit('workflowExecutionCompleted', {
workflowData: this.workflowData,
fullRunData,
});
}
});
}
/**
* Returns WorkflowHooks instance for running integrated workflows
* Returns ExecutionLifecycleHooks instance for running integrated workflows
* (Workflows which get started inside of another workflow)
*/
export function getWorkflowHooksIntegrated(
export function getLifecycleHooksForSubExecutions(
mode: WorkflowExecuteMode,
executionId: string,
workflowData: IWorkflowBase,
userId?: string,
): WorkflowHooks {
): ExecutionLifecycleHooks {
const hooks = new ExecutionLifecycleHooks(mode, executionId, workflowData);
const saveSettings = toSaveSettings(workflowData.settings);
const hookFunctions = mergeHookFunctions(
hookFunctionsWorkflowEvents(userId),
hookFunctionsNodeEvents(),
hookFunctionsFinalizeExecutionStatus(),
hookFunctionsSave({ saveSettings }),
hookFunctionsSaveProgress({ saveSettings }),
hookFunctionsStatistics(),
hookFunctionsExternalHooks(),
);
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData);
hookFunctionsWorkflowEvents(hooks, userId);
hookFunctionsNodeEvents(hooks);
hookFunctionsFinalizeExecutionStatus(hooks);
hookFunctionsSave(hooks, { saveSettings });
hookFunctionsSaveProgress(hooks, { saveSettings });
hookFunctionsStatistics(hooks);
hookFunctionsExternalHooks(hooks);
return hooks;
}
/**
* Returns WorkflowHooks instance for worker in scaling mode.
* Returns ExecutionLifecycleHooks instance for worker in scaling mode.
*/
export function getWorkflowHooksWorkerExecuter(
export function getLifecycleHooksForScalingWorker(
mode: WorkflowExecuteMode,
executionId: string,
workflowData: IWorkflowBase,
{ pushRef, retryOf }: Omit<HooksSetupParameters, 'saveSettings'> = {},
): WorkflowHooks {
): ExecutionLifecycleHooks {
const hooks = new ExecutionLifecycleHooks(mode, executionId, workflowData);
const saveSettings = toSaveSettings(workflowData.settings);
const optionalParameters = { pushRef, retryOf, saveSettings };
const toMerge = [
hookFunctionsNodeEvents(),
hookFunctionsFinalizeExecutionStatus(),
hookFunctionsSaveWorker(optionalParameters),
hookFunctionsSaveProgress(optionalParameters),
hookFunctionsStatistics(),
hookFunctionsExternalHooks(),
];
hookFunctionsNodeEvents(hooks);
hookFunctionsFinalizeExecutionStatus(hooks);
hookFunctionsSaveWorker(hooks, optionalParameters);
hookFunctionsSaveProgress(hooks, optionalParameters);
hookFunctionsStatistics(hooks);
hookFunctionsExternalHooks(hooks);
if (mode === 'manual' && Container.get(InstanceSettings).isWorker) {
toMerge.push(hookFunctionsPush(optionalParameters));
hookFunctionsPush(hooks, optionalParameters);
}
const hookFunctions = mergeHookFunctions(...toMerge);
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData);
return hooks;
}
/**
* Returns WorkflowHooks instance for main process if workflow runs via worker
* Returns ExecutionLifecycleHooks instance for main process if workflow runs via worker
*/
export function getWorkflowHooksWorkerMain(
export function getLifecycleHooksForScalingMain(
mode: WorkflowExecuteMode,
executionId: string,
workflowData: IWorkflowBase,
{ pushRef, retryOf }: Omit<HooksSetupParameters, 'saveSettings'> = {},
): WorkflowHooks {
): ExecutionLifecycleHooks {
const hooks = new ExecutionLifecycleHooks(mode, executionId, workflowData);
const saveSettings = toSaveSettings(workflowData.settings);
const optionalParameters = { pushRef, retryOf, saveSettings };
const executionRepository = Container.get(ExecutionRepository);
const hookFunctions = mergeHookFunctions(
hookFunctionsWorkflowEvents(),
hookFunctionsSaveProgress(optionalParameters),
hookFunctionsExternalHooks(),
hookFunctionsFinalizeExecutionStatus(),
{
workflowExecuteAfter: [
async function (this: WorkflowHooks, fullRunData: IRun): Promise<void> {
// Don't delete executions before they are finished
if (!fullRunData.finished) return;
const isManualMode = this.mode === 'manual';
hookFunctionsWorkflowEvents(hooks);
hookFunctionsSaveProgress(hooks, optionalParameters);
hookFunctionsExternalHooks(hooks);
hookFunctionsFinalizeExecutionStatus(hooks);
if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
/**
* When manual executions are not being saved, we only soft-delete
* the execution so that the user can access its binary data
* while building their workflow.
*
* The manual execution and its binary data will be hard-deleted
* on the next pruning cycle after the grace period set by
* `EXECUTIONS_DATA_HARD_DELETE_BUFFER`.
*/
await executionRepository.softDelete(this.executionId);
hooks.addHandler('workflowExecuteAfter', async function (fullRunData) {
// Don't delete executions before they are finished
if (!fullRunData.finished) return;
return;
}
const isManualMode = this.mode === 'manual';
const shouldNotSave =
(fullRunData.status === 'success' && !saveSettings.success) ||
(fullRunData.status !== 'success' && !saveSettings.error);
if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
/**
* When manual executions are not being saved, we only soft-delete
* the execution so that the user can access its binary data
* while building their workflow.
*
* The manual execution and its binary data will be hard-deleted
* on the next pruning cycle after the grace period set by
* `EXECUTIONS_DATA_HARD_DELETE_BUFFER`.
*/
await executionRepository.softDelete(this.executionId);
if (!isManualMode && shouldNotSave && !fullRunData.waitTill) {
await executionRepository.hardDelete({
workflowId: this.workflowData.id,
executionId: this.executionId,
});
}
},
],
},
);
return;
}
const shouldNotSave =
(fullRunData.status === 'success' && !saveSettings.success) ||
(fullRunData.status !== 'success' && !saveSettings.error);
if (!isManualMode && shouldNotSave && !fullRunData.waitTill) {
await executionRepository.hardDelete({
workflowId: this.workflowData.id,
executionId: this.executionId,
});
}
});
// When running with worker mode, main process executes
// Only workflowExecuteBefore + workflowExecuteAfter
// So to avoid confusion, we are removing other hooks.
hookFunctions.nodeExecuteBefore = [];
hookFunctions.nodeExecuteAfter = [];
hooks.handlers.nodeExecuteBefore = [];
hooks.handlers.nodeExecuteAfter = [];
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData);
return hooks;
}
/**
* Returns WorkflowHooks instance for running the main workflow
* Returns ExecutionLifecycleHooks instance for running the main workflow
*/
export function getWorkflowHooksMain(
export function getLifecycleHooksForRegularMain(
data: IWorkflowExecutionDataProcess,
executionId: string,
): WorkflowHooks {
const { pushRef, retryOf } = data;
const saveSettings = toSaveSettings(data.workflowData.settings);
): ExecutionLifecycleHooks {
const { pushRef, retryOf, executionMode, workflowData } = data;
const hooks = new ExecutionLifecycleHooks(executionMode, executionId, workflowData);
const saveSettings = toSaveSettings(workflowData.settings);
const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings };
const hookFunctions = mergeHookFunctions(
hookFunctionsWorkflowEvents(),
hookFunctionsNodeEvents(),
hookFunctionsFinalizeExecutionStatus(),
hookFunctionsSave(optionalParameters),
hookFunctionsPush(optionalParameters),
hookFunctionsSaveProgress(optionalParameters),
hookFunctionsStatistics(),
hookFunctionsExternalHooks(),
);
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData);
hookFunctionsWorkflowEvents(hooks);
hookFunctionsNodeEvents(hooks);
hookFunctionsFinalizeExecutionStatus(hooks);
hookFunctionsSave(hooks, optionalParameters);
hookFunctionsPush(hooks, optionalParameters);
hookFunctionsSaveProgress(hooks, optionalParameters);
hookFunctionsStatistics(hooks);
hookFunctionsExternalHooks(hooks);
return hooks;
}