mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 04:47:29 -08:00
refactor(core): Use DI for WorkflowRunner (no-changelog) (#8372)
This commit is contained in:
parent
bf11c7c1bd
commit
c70fa66e76
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
import { Service } from 'typedi';
|
||||||
|
import type { ChildProcess } from 'child_process';
|
||||||
import { Container, Service } from 'typedi';
|
import type PCancelable from 'p-cancelable';
|
||||||
import type {
|
import type {
|
||||||
IDeferredPromise,
|
IDeferredPromise,
|
||||||
IExecuteResponsePromiseData,
|
IExecuteResponsePromiseData,
|
||||||
|
@ -9,8 +9,6 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ApplicationError, WorkflowOperationError, createDeferredPromise } from 'n8n-workflow';
|
import { ApplicationError, WorkflowOperationError, createDeferredPromise } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { ChildProcess } from 'child_process';
|
|
||||||
import type PCancelable from 'p-cancelable';
|
|
||||||
import type {
|
import type {
|
||||||
ExecutionPayload,
|
ExecutionPayload,
|
||||||
IExecutingWorkflowData,
|
IExecutingWorkflowData,
|
||||||
|
@ -28,7 +26,10 @@ export class ActiveExecutions {
|
||||||
[index: string]: IExecutingWorkflowData;
|
[index: string]: IExecutingWorkflowData;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
constructor(private readonly logger: Logger) {}
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly executionRepository: ExecutionRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new active execution
|
* Add a new active execution
|
||||||
|
@ -61,7 +62,7 @@ export class ActiveExecutions {
|
||||||
fullExecutionData.workflowId = workflowId;
|
fullExecutionData.workflowId = workflowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
executionId = await Container.get(ExecutionRepository).createNewExecution(fullExecutionData);
|
executionId = await this.executionRepository.createNewExecution(fullExecutionData);
|
||||||
if (executionId === undefined) {
|
if (executionId === undefined) {
|
||||||
throw new ApplicationError('There was an issue assigning an execution id to the execution');
|
throw new ApplicationError('There was an issue assigning an execution id to the execution');
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ export class ActiveExecutions {
|
||||||
status: executionStatus,
|
status: executionStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Container.get(ExecutionRepository).updateExistingExecution(executionId, execution);
|
await this.executionRepository.updateExistingExecution(executionId, execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeExecutions[executionId] = {
|
this.activeExecutions[executionId] = {
|
||||||
|
@ -96,34 +97,33 @@ export class ActiveExecutions {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
||||||
extra: { executionId },
|
extra: { executionId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeExecutions[executionId].workflowExecution = workflowExecution;
|
execution.workflowExecution = workflowExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachResponsePromise(
|
attachResponsePromise(
|
||||||
executionId: string,
|
executionId: string,
|
||||||
responsePromise: IDeferredPromise<IExecuteResponsePromiseData>,
|
responsePromise: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||||
): void {
|
): void {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
||||||
extra: { executionId },
|
extra: { executionId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeExecutions[executionId].responsePromise = responsePromise;
|
execution.responsePromise = responsePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void {
|
resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
return;
|
execution?.responsePromise?.resolve(response);
|
||||||
}
|
|
||||||
|
|
||||||
this.activeExecutions[executionId].responsePromise?.resolve(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPostExecutePromiseCount(executionId: string): number {
|
getPostExecutePromiseCount(executionId: string): number {
|
||||||
|
@ -135,13 +135,14 @@ export class ActiveExecutions {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
remove(executionId: string, fullRunData?: IRun): void {
|
remove(executionId: string, fullRunData?: IRun): void {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve all the waiting promises
|
// Resolve all the waiting promises
|
||||||
|
|
||||||
for (const promise of this.activeExecutions[executionId].postExecutePromises) {
|
for (const promise of execution.postExecutePromises) {
|
||||||
promise.resolve(fullRunData);
|
promise.resolve(fullRunData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,26 +157,27 @@ export class ActiveExecutions {
|
||||||
* @param {string} timeout String 'timeout' given if stop due to timeout
|
* @param {string} timeout String 'timeout' given if stop due to timeout
|
||||||
*/
|
*/
|
||||||
async stopExecution(executionId: string, timeout?: string): Promise<IRun | undefined> {
|
async stopExecution(executionId: string, timeout?: string): Promise<IRun | undefined> {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
// There is no execution running with that id
|
// There is no execution running with that id
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case something goes wrong make sure that promise gets first
|
// In case something goes wrong make sure that promise gets first
|
||||||
// returned that it gets then also resolved correctly.
|
// returned that it gets then also resolved correctly.
|
||||||
if (this.activeExecutions[executionId].process !== undefined) {
|
if (execution.process !== undefined) {
|
||||||
// Workflow is running in subprocess
|
// Workflow is running in subprocess
|
||||||
if (this.activeExecutions[executionId].process!.connected) {
|
if (execution.process.connected) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// execute on next event loop tick;
|
// execute on next event loop tick;
|
||||||
this.activeExecutions[executionId].process!.send({
|
execution.process!.send({
|
||||||
type: timeout || 'stopExecution',
|
type: timeout || 'stopExecution',
|
||||||
});
|
});
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Workflow is running in current process
|
// Workflow is running in current process
|
||||||
this.activeExecutions[executionId].workflowExecution!.cancel();
|
execution.workflowExecution!.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.getPostExecutePromise(executionId);
|
return await this.getPostExecutePromise(executionId);
|
||||||
|
@ -188,14 +190,15 @@ export class ActiveExecutions {
|
||||||
* @param {string} executionId The id of the execution to wait for
|
* @param {string} executionId The id of the execution to wait for
|
||||||
*/
|
*/
|
||||||
async getPostExecutePromise(executionId: string): Promise<IRun | undefined> {
|
async getPostExecutePromise(executionId: string): Promise<IRun | undefined> {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
throw new WorkflowOperationError(`There is no active execution with id "${executionId}".`);
|
throw new WorkflowOperationError(`There is no active execution with id "${executionId}".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the promise which will be resolved when the execution finished
|
// Create the promise which will be resolved when the execution finished
|
||||||
const waitPromise = await createDeferredPromise<IRun | undefined>();
|
const waitPromise = await createDeferredPromise<IRun | undefined>();
|
||||||
|
|
||||||
this.activeExecutions[executionId].postExecutePromises.push(waitPromise);
|
execution.postExecutePromises.push(waitPromise);
|
||||||
|
|
||||||
return await waitPromise.promise();
|
return await waitPromise.promise();
|
||||||
}
|
}
|
||||||
|
@ -213,10 +216,10 @@ export class ActiveExecutions {
|
||||||
data = this.activeExecutions[id];
|
data = this.activeExecutions[id];
|
||||||
returnData.push({
|
returnData.push({
|
||||||
id,
|
id,
|
||||||
retryOf: data.executionData.retryOf as string | undefined,
|
retryOf: data.executionData.retryOf,
|
||||||
startedAt: data.startedAt,
|
startedAt: data.startedAt,
|
||||||
mode: data.executionData.executionMode,
|
mode: data.executionData.executionMode,
|
||||||
workflowId: data.executionData.workflowData.id! as string,
|
workflowId: data.executionData.workflowData.id,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -225,21 +228,19 @@ export class ActiveExecutions {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setStatus(executionId: string, status: ExecutionStatus): Promise<void> {
|
async setStatus(executionId: string, status: ExecutionStatus): Promise<void> {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
|
if (execution === undefined) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`There is no active execution with id "${executionId}", can't update status to ${status}.`,
|
`There is no active execution with id "${executionId}", can't update status to ${status}.`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeExecutions[executionId].status = status;
|
execution.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatus(executionId: string): ExecutionStatus {
|
getStatus(executionId: string): ExecutionStatus {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
const execution = this.activeExecutions[executionId];
|
||||||
return 'unknown';
|
return execution?.status ?? 'unknown';
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeExecutions[executionId].status;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,14 @@ import { ActiveWorkflows, NodeExecuteFunctions } from 'n8n-core';
|
||||||
import type {
|
import type {
|
||||||
ExecutionError,
|
ExecutionError,
|
||||||
IDeferredPromise,
|
IDeferredPromise,
|
||||||
IExecuteData,
|
|
||||||
IExecuteResponsePromiseData,
|
IExecuteResponsePromiseData,
|
||||||
IGetExecutePollFunctions,
|
IGetExecutePollFunctions,
|
||||||
IGetExecuteTriggerFunctions,
|
IGetExecuteTriggerFunctions,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
IRun,
|
IRun,
|
||||||
IRunExecutionData,
|
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
IWorkflowExecuteAdditionalData,
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
INodeType,
|
INodeType,
|
||||||
|
@ -29,7 +27,7 @@ import {
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
import type { IWorkflowDb } from '@/Interfaces';
|
||||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
import * as WebhookHelpers from '@/WebhookHelpers';
|
||||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
|
|
||||||
|
@ -42,7 +40,6 @@ import {
|
||||||
WORKFLOW_REACTIVATE_MAX_TIMEOUT,
|
WORKFLOW_REACTIVATE_MAX_TIMEOUT,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { WebhookService } from './services/webhook.service';
|
import { WebhookService } from './services/webhook.service';
|
||||||
import { Logger } from './Logger';
|
import { Logger } from './Logger';
|
||||||
|
@ -50,6 +47,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { ActivationErrorsService } from '@/ActivationErrors.service';
|
import { ActivationErrorsService } from '@/ActivationErrors.service';
|
||||||
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
||||||
|
import { WorkflowExecutionService } from '@/workflows/workflowExecution.service';
|
||||||
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
||||||
import { OnShutdown } from '@/decorators/OnShutdown';
|
import { OnShutdown } from '@/decorators/OnShutdown';
|
||||||
|
|
||||||
|
@ -77,6 +75,7 @@ export class ActiveWorkflowRunner {
|
||||||
private readonly executionService: ExecutionService,
|
private readonly executionService: ExecutionService,
|
||||||
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
||||||
private readonly activeWorkflowsService: ActiveWorkflowsService,
|
private readonly activeWorkflowsService: ActiveWorkflowsService,
|
||||||
|
private readonly workflowExecutionService: WorkflowExecutionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -141,7 +140,7 @@ export class ActiveWorkflowRunner {
|
||||||
*/
|
*/
|
||||||
async addWebhooks(
|
async addWebhooks(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
activation: WorkflowActivateMode,
|
activation: WorkflowActivateMode,
|
||||||
) {
|
) {
|
||||||
|
@ -264,57 +263,13 @@ export class ActiveWorkflowRunner {
|
||||||
await this.webhookService.deleteWorkflowWebhooks(workflowId);
|
await this.webhookService.deleteWorkflowWebhooks(workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async runWorkflow(
|
|
||||||
workflowData: IWorkflowDb,
|
|
||||||
node: INode,
|
|
||||||
data: INodeExecutionData[][],
|
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
|
||||||
mode: WorkflowExecuteMode,
|
|
||||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
|
||||||
) {
|
|
||||||
const nodeExecutionStack: IExecuteData[] = [
|
|
||||||
{
|
|
||||||
node,
|
|
||||||
data: {
|
|
||||||
main: data,
|
|
||||||
},
|
|
||||||
source: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const executionData: IRunExecutionData = {
|
|
||||||
startData: {},
|
|
||||||
resultData: {
|
|
||||||
runData: {},
|
|
||||||
},
|
|
||||||
executionData: {
|
|
||||||
contextData: {},
|
|
||||||
metadata: {},
|
|
||||||
nodeExecutionStack,
|
|
||||||
waitingExecution: {},
|
|
||||||
waitingExecutionSource: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start the workflow
|
|
||||||
const runData: IWorkflowExecutionDataProcess = {
|
|
||||||
userId: additionalData.userId,
|
|
||||||
executionMode: mode,
|
|
||||||
executionData,
|
|
||||||
workflowData,
|
|
||||||
};
|
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
|
||||||
return await workflowRunner.run(runData, true, undefined, undefined, responsePromise);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return poll function which gets the global functions from n8n-core
|
* Return poll function which gets the global functions from n8n-core
|
||||||
* and overwrites the emit to be able to start it in subprocess
|
* and overwrites the emit to be able to start it in subprocess
|
||||||
*/
|
*/
|
||||||
getExecutePollFunctions(
|
getExecutePollFunctions(
|
||||||
workflowData: IWorkflowDb,
|
workflowData: IWorkflowDb,
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
activation: WorkflowActivateMode,
|
activation: WorkflowActivateMode,
|
||||||
): IGetExecutePollFunctions {
|
): IGetExecutePollFunctions {
|
||||||
|
@ -333,7 +288,7 @@ export class ActiveWorkflowRunner {
|
||||||
): void => {
|
): void => {
|
||||||
this.logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
|
this.logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
|
||||||
void this.workflowStaticDataService.saveStaticData(workflow);
|
void this.workflowStaticDataService.saveStaticData(workflow);
|
||||||
const executePromise = this.runWorkflow(
|
const executePromise = this.workflowExecutionService.runWorkflow(
|
||||||
workflowData,
|
workflowData,
|
||||||
node,
|
node,
|
||||||
data,
|
data,
|
||||||
|
@ -371,7 +326,7 @@ export class ActiveWorkflowRunner {
|
||||||
*/
|
*/
|
||||||
getExecuteTriggerFunctions(
|
getExecuteTriggerFunctions(
|
||||||
workflowData: IWorkflowDb,
|
workflowData: IWorkflowDb,
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
activation: WorkflowActivateMode,
|
activation: WorkflowActivateMode,
|
||||||
): IGetExecuteTriggerFunctions {
|
): IGetExecuteTriggerFunctions {
|
||||||
|
@ -391,7 +346,7 @@ export class ActiveWorkflowRunner {
|
||||||
this.logger.debug(`Received trigger for workflow "${workflow.name}"`);
|
this.logger.debug(`Received trigger for workflow "${workflow.name}"`);
|
||||||
void this.workflowStaticDataService.saveStaticData(workflow);
|
void this.workflowStaticDataService.saveStaticData(workflow);
|
||||||
|
|
||||||
const executePromise = this.runWorkflow(
|
const executePromise = this.workflowExecutionService.runWorkflow(
|
||||||
workflowData,
|
workflowData,
|
||||||
node,
|
node,
|
||||||
data,
|
data,
|
||||||
|
@ -659,10 +614,7 @@ export class ActiveWorkflowRunner {
|
||||||
/**
|
/**
|
||||||
* Count all triggers in the workflow, excluding Manual Trigger.
|
* Count all triggers in the workflow, excluding Manual Trigger.
|
||||||
*/
|
*/
|
||||||
private countTriggers(
|
private countTriggers(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData) {
|
||||||
workflow: Workflow,
|
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
|
||||||
) {
|
|
||||||
const triggerFilter = (nodeType: INodeType) =>
|
const triggerFilter = (nodeType: INodeType) =>
|
||||||
!!nodeType.trigger && !nodeType.description.name.includes('manualTrigger');
|
!!nodeType.trigger && !nodeType.description.name.includes('manualTrigger');
|
||||||
|
|
||||||
|
@ -796,7 +748,7 @@ export class ActiveWorkflowRunner {
|
||||||
}: {
|
}: {
|
||||||
activationMode: WorkflowActivateMode;
|
activationMode: WorkflowActivateMode;
|
||||||
executionMode: WorkflowExecuteMode;
|
executionMode: WorkflowExecuteMode;
|
||||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow;
|
additionalData: IWorkflowExecuteAdditionalData;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(
|
const getTriggerFunctions = this.getExecuteTriggerFunctions(
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class WaitTracker {
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly executionRepository: ExecutionRepository,
|
private readonly executionRepository: ExecutionRepository,
|
||||||
private readonly ownershipService: OwnershipService,
|
private readonly ownershipService: OwnershipService,
|
||||||
|
private readonly workflowRunner: WorkflowRunner,
|
||||||
) {
|
) {
|
||||||
// Poll every 60 seconds a list of upcoming executions
|
// Poll every 60 seconds a list of upcoming executions
|
||||||
this.mainTimer = setInterval(() => {
|
this.mainTimer = setInterval(() => {
|
||||||
|
@ -163,8 +164,7 @@ export class WaitTracker {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start the execution again
|
// Start the execution again
|
||||||
const workflowRunner = new WorkflowRunner();
|
await this.workflowRunner.run(data, false, false, executionId);
|
||||||
await workflowRunner.run(data, false, false, executionId);
|
|
||||||
})().catch((error: Error) => {
|
})().catch((error: Error) => {
|
||||||
ErrorReporter.error(error);
|
ErrorReporter.error(error);
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|
|
@ -590,8 +590,7 @@ export async function executeWebhook(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start now to run the workflow
|
// Start now to run the workflow
|
||||||
const workflowRunner = new WorkflowRunner();
|
executionId = await Container.get(WorkflowRunner).run(
|
||||||
executionId = await workflowRunner.run(
|
|
||||||
runData,
|
runData,
|
||||||
true,
|
true,
|
||||||
!didSendResponse,
|
!didSendResponse,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-shadow */
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import { Container } from 'typedi';
|
import { Container, Service } from 'typedi';
|
||||||
import type { IProcessMessage } from 'n8n-core';
|
import type { IProcessMessage } from 'n8n-core';
|
||||||
import { WorkflowExecute } from 'n8n-core';
|
import { WorkflowExecute } from 'n8n-core';
|
||||||
|
|
||||||
|
@ -46,36 +46,37 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
|
||||||
import { generateFailedExecutionFromError } from '@/WorkflowHelpers';
|
import { generateFailedExecutionFromError } from '@/WorkflowHelpers';
|
||||||
import { initErrorHandling } from '@/ErrorReporting';
|
import { initErrorHandling } from '@/ErrorReporting';
|
||||||
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
||||||
import { Push } from '@/push';
|
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
||||||
|
|
||||||
|
@Service()
|
||||||
export class WorkflowRunner {
|
export class WorkflowRunner {
|
||||||
logger: Logger;
|
private jobQueue: Queue;
|
||||||
|
|
||||||
activeExecutions: ActiveExecutions;
|
private executionsMode = config.getEnv('executions.mode');
|
||||||
|
|
||||||
push: Push;
|
private executionsProcess = config.getEnv('executions.process');
|
||||||
|
|
||||||
jobQueue: Queue;
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly activeExecutions: ActiveExecutions,
|
||||||
|
private readonly executionRepository: ExecutionRepository,
|
||||||
|
private readonly externalHooks: ExternalHooks,
|
||||||
|
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
||||||
|
private readonly nodeTypes: NodeTypes,
|
||||||
|
private readonly permissionChecker: PermissionChecker,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor() {
|
/** The process did send a hook message so execute the appropriate hook */
|
||||||
this.logger = Container.get(Logger);
|
private async processHookMessage(
|
||||||
this.push = Container.get(Push);
|
workflowHooks: WorkflowHooks,
|
||||||
this.activeExecutions = Container.get(ActiveExecutions);
|
hookData: IProcessMessageDataHook,
|
||||||
}
|
) {
|
||||||
|
|
||||||
/**
|
|
||||||
* The process did send a hook message so execute the appropriate hook
|
|
||||||
*/
|
|
||||||
async processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) {
|
|
||||||
await workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
|
await workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The process did error */
|
||||||
* The process did error
|
|
||||||
*/
|
|
||||||
async processError(
|
async processError(
|
||||||
error: ExecutionError,
|
error: ExecutionError,
|
||||||
startedAt: Date,
|
startedAt: Date,
|
||||||
|
@ -91,12 +92,9 @@ export class WorkflowRunner {
|
||||||
// by Bull even though it executed successfully, see https://github.com/OptimalBits/bull/issues/1415
|
// by Bull even though it executed successfully, see https://github.com/OptimalBits/bull/issues/1415
|
||||||
|
|
||||||
if (isQueueMode && executionMode !== 'manual') {
|
if (isQueueMode && executionMode !== 'manual') {
|
||||||
const executionWithoutData = await Container.get(ExecutionRepository).findSingleExecution(
|
const executionWithoutData = await this.executionRepository.findSingleExecution(executionId, {
|
||||||
executionId,
|
includeData: false,
|
||||||
{
|
});
|
||||||
includeData: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (executionWithoutData?.finished === true && executionWithoutData?.status === 'success') {
|
if (executionWithoutData?.finished === true && executionWithoutData?.status === 'success') {
|
||||||
// false positive, execution was successful
|
// false positive, execution was successful
|
||||||
return;
|
return;
|
||||||
|
@ -140,12 +138,9 @@ export class WorkflowRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionFlattedData = await Container.get(ExecutionRepository).findSingleExecution(
|
const executionFlattedData = await this.executionRepository.findSingleExecution(executionId, {
|
||||||
executionId,
|
includeData: true,
|
||||||
{
|
});
|
||||||
includeData: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (executionFlattedData) {
|
if (executionFlattedData) {
|
||||||
void Container.get(InternalHooks).onWorkflowCrashed(
|
void Container.get(InternalHooks).onWorkflowCrashed(
|
||||||
|
@ -169,12 +164,7 @@ export class WorkflowRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Run the workflow */
|
||||||
* Run the workflow
|
|
||||||
*
|
|
||||||
* @param {boolean} [loadStaticData] If set will the static data be loaded from
|
|
||||||
* the workflow and added to input data
|
|
||||||
*/
|
|
||||||
async run(
|
async run(
|
||||||
data: IWorkflowExecutionDataProcess,
|
data: IWorkflowExecutionDataProcess,
|
||||||
loadStaticData?: boolean,
|
loadStaticData?: boolean,
|
||||||
|
@ -182,16 +172,13 @@ export class WorkflowRunner {
|
||||||
executionId?: string,
|
executionId?: string,
|
||||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const executionsMode = config.getEnv('executions.mode');
|
|
||||||
const executionsProcess = config.getEnv('executions.process');
|
|
||||||
|
|
||||||
await initErrorHandling();
|
await initErrorHandling();
|
||||||
|
|
||||||
if (executionsMode === 'queue') {
|
if (this.executionsMode === 'queue') {
|
||||||
this.jobQueue = Container.get(Queue);
|
this.jobQueue = Container.get(Queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (executionsMode === 'queue' && data.executionMode !== 'manual') {
|
if (this.executionsMode === 'queue' && data.executionMode !== 'manual') {
|
||||||
// Do not run "manual" executions in bull because sending events to the
|
// Do not run "manual" executions in bull because sending events to the
|
||||||
// frontend would not be possible
|
// frontend would not be possible
|
||||||
executionId = await this.enqueueExecution(
|
executionId = await this.enqueueExecution(
|
||||||
|
@ -202,7 +189,7 @@ export class WorkflowRunner {
|
||||||
responsePromise,
|
responsePromise,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (executionsProcess === 'main') {
|
if (this.executionsProcess === 'main') {
|
||||||
executionId = await this.runMainProcess(data, loadStaticData, executionId, responsePromise);
|
executionId = await this.runMainProcess(data, loadStaticData, executionId, responsePromise);
|
||||||
} else {
|
} else {
|
||||||
executionId = await this.runSubprocess(data, loadStaticData, executionId, responsePromise);
|
executionId = await this.runSubprocess(data, loadStaticData, executionId, responsePromise);
|
||||||
|
@ -213,12 +200,11 @@ export class WorkflowRunner {
|
||||||
// only run these when not in queue mode or when the execution is manual,
|
// only run these when not in queue mode or when the execution is manual,
|
||||||
// since these calls are now done by the worker directly
|
// since these calls are now done by the worker directly
|
||||||
if (
|
if (
|
||||||
executionsMode !== 'queue' ||
|
this.executionsMode !== 'queue' ||
|
||||||
config.getEnv('generic.instanceType') === 'worker' ||
|
config.getEnv('generic.instanceType') === 'worker' ||
|
||||||
data.executionMode === 'manual'
|
data.executionMode === 'manual'
|
||||||
) {
|
) {
|
||||||
const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId);
|
const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId);
|
||||||
const externalHooks = Container.get(ExternalHooks);
|
|
||||||
postExecutePromise
|
postExecutePromise
|
||||||
.then(async (executionData) => {
|
.then(async (executionData) => {
|
||||||
void Container.get(InternalHooks).onWorkflowPostExecute(
|
void Container.get(InternalHooks).onWorkflowPostExecute(
|
||||||
|
@ -227,9 +213,9 @@ export class WorkflowRunner {
|
||||||
executionData,
|
executionData,
|
||||||
data.userId,
|
data.userId,
|
||||||
);
|
);
|
||||||
if (externalHooks.exists('workflow.postExecute')) {
|
if (this.externalHooks.exists('workflow.postExecute')) {
|
||||||
try {
|
try {
|
||||||
await externalHooks.run('workflow.postExecute', [
|
await this.externalHooks.run('workflow.postExecute', [
|
||||||
executionData,
|
executionData,
|
||||||
data.workflowData,
|
data.workflowData,
|
||||||
executionId,
|
executionId,
|
||||||
|
@ -249,13 +235,8 @@ export class WorkflowRunner {
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Run the workflow in current process */
|
||||||
* Run the workflow in current process
|
private async runMainProcess(
|
||||||
*
|
|
||||||
* @param {boolean} [loadStaticData] If set will the static data be loaded from
|
|
||||||
* the workflow and added to input data
|
|
||||||
*/
|
|
||||||
async runMainProcess(
|
|
||||||
data: IWorkflowExecutionDataProcess,
|
data: IWorkflowExecutionDataProcess,
|
||||||
loadStaticData?: boolean,
|
loadStaticData?: boolean,
|
||||||
restartExecutionId?: string,
|
restartExecutionId?: string,
|
||||||
|
@ -264,11 +245,9 @@ export class WorkflowRunner {
|
||||||
const workflowId = data.workflowData.id;
|
const workflowId = data.workflowData.id;
|
||||||
if (loadStaticData === true && workflowId) {
|
if (loadStaticData === true && workflowId) {
|
||||||
data.workflowData.staticData =
|
data.workflowData.staticData =
|
||||||
await Container.get(WorkflowStaticDataService).getStaticDataById(workflowId);
|
await this.workflowStaticDataService.getStaticDataById(workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTypes = Container.get(NodeTypes);
|
|
||||||
|
|
||||||
// Soft timeout to stop workflow execution after current running node
|
// Soft timeout to stop workflow execution after current running node
|
||||||
// Changes were made by adding the `workflowTimeout` to the `additionalData`
|
// Changes were made by adding the `workflowTimeout` to the `additionalData`
|
||||||
// So that the timeout will also work for executions with nested workflows.
|
// So that the timeout will also work for executions with nested workflows.
|
||||||
|
@ -291,7 +270,7 @@ export class WorkflowRunner {
|
||||||
nodes: data.workflowData.nodes,
|
nodes: data.workflowData.nodes,
|
||||||
connections: data.workflowData.connections,
|
connections: data.workflowData.connections,
|
||||||
active: data.workflowData.active,
|
active: data.workflowData.active,
|
||||||
nodeTypes,
|
nodeTypes: this.nodeTypes,
|
||||||
staticData: data.workflowData.staticData,
|
staticData: data.workflowData.staticData,
|
||||||
settings: workflowSettings,
|
settings: workflowSettings,
|
||||||
pinData,
|
pinData,
|
||||||
|
@ -312,7 +291,7 @@ export class WorkflowRunner {
|
||||||
{ executionId },
|
{ executionId },
|
||||||
);
|
);
|
||||||
let workflowExecution: PCancelable<IRun>;
|
let workflowExecution: PCancelable<IRun>;
|
||||||
await Container.get(ExecutionRepository).updateStatus(executionId, 'running');
|
await this.executionRepository.updateStatus(executionId, 'running');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(
|
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(
|
||||||
|
@ -322,7 +301,7 @@ export class WorkflowRunner {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Container.get(PermissionChecker).check(workflow, data.userId);
|
await this.permissionChecker.check(workflow, data.userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorReporter.error(error);
|
ErrorReporter.error(error);
|
||||||
// Create a failed execution with the data for the node
|
// Create a failed execution with the data for the node
|
||||||
|
@ -439,7 +418,7 @@ export class WorkflowRunner {
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async enqueueExecution(
|
private async enqueueExecution(
|
||||||
data: IWorkflowExecutionDataProcess,
|
data: IWorkflowExecutionDataProcess,
|
||||||
loadStaticData?: boolean,
|
loadStaticData?: boolean,
|
||||||
realtime?: boolean,
|
realtime?: boolean,
|
||||||
|
@ -604,13 +583,10 @@ export class WorkflowRunner {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullExecutionData = await Container.get(ExecutionRepository).findSingleExecution(
|
const fullExecutionData = await this.executionRepository.findSingleExecution(executionId, {
|
||||||
executionId,
|
includeData: executionHasPostExecutionPromises,
|
||||||
{
|
unflattenData: executionHasPostExecutionPromises,
|
||||||
includeData: executionHasPostExecutionPromises,
|
});
|
||||||
unflattenData: executionHasPostExecutionPromises,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!fullExecutionData) {
|
if (!fullExecutionData) {
|
||||||
return reject(new Error(`Could not find execution with id "${executionId}"`));
|
return reject(new Error(`Could not find execution with id "${executionId}"`));
|
||||||
}
|
}
|
||||||
|
@ -651,13 +627,8 @@ export class WorkflowRunner {
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Run the workflow in a child-process */
|
||||||
* Run the workflow
|
private async runSubprocess(
|
||||||
*
|
|
||||||
* @param {boolean} [loadStaticData] If set will the static data be loaded from
|
|
||||||
* the workflow and added to input data
|
|
||||||
*/
|
|
||||||
async runSubprocess(
|
|
||||||
data: IWorkflowExecutionDataProcess,
|
data: IWorkflowExecutionDataProcess,
|
||||||
loadStaticData?: boolean,
|
loadStaticData?: boolean,
|
||||||
restartExecutionId?: string,
|
restartExecutionId?: string,
|
||||||
|
@ -669,7 +640,7 @@ export class WorkflowRunner {
|
||||||
|
|
||||||
if (loadStaticData === true && workflowId) {
|
if (loadStaticData === true && workflowId) {
|
||||||
data.workflowData.staticData =
|
data.workflowData.staticData =
|
||||||
await Container.get(WorkflowStaticDataService).getStaticDataById(workflowId);
|
await this.workflowStaticDataService.getStaticDataById(workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.restartExecutionId = restartExecutionId;
|
data.restartExecutionId = restartExecutionId;
|
||||||
|
@ -678,7 +649,7 @@ export class WorkflowRunner {
|
||||||
const executionId = await this.activeExecutions.add(data, subprocess, restartExecutionId);
|
const executionId = await this.activeExecutions.add(data, subprocess, restartExecutionId);
|
||||||
|
|
||||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||||
await Container.get(ExecutionRepository).updateStatus(executionId, 'running');
|
await this.executionRepository.updateStatus(executionId, 'running');
|
||||||
|
|
||||||
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
||||||
|
|
||||||
|
|
|
@ -106,8 +106,7 @@ export class Execute extends BaseCommand {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
const executionId = await Container.get(WorkflowRunner).run(runData);
|
||||||
const executionId = await workflowRunner.run(runData);
|
|
||||||
|
|
||||||
const activeExecutions = Container.get(ActiveExecutions);
|
const activeExecutions = Container.get(ActiveExecutions);
|
||||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||||
|
|
|
@ -644,8 +644,7 @@ export class ExecuteBatch extends BaseCommand {
|
||||||
userId: ExecuteBatch.instanceOwner.id,
|
userId: ExecuteBatch.instanceOwner.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
const executionId = await Container.get(WorkflowRunner).run(runData);
|
||||||
const executionId = await workflowRunner.run(runData);
|
|
||||||
|
|
||||||
const activeExecutions = Container.get(ActiveExecutions);
|
const activeExecutions = Container.get(ActiveExecutions);
|
||||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||||
|
|
|
@ -76,6 +76,7 @@ export class ExecutionService {
|
||||||
private readonly executionRepository: ExecutionRepository,
|
private readonly executionRepository: ExecutionRepository,
|
||||||
private readonly workflowRepository: WorkflowRepository,
|
private readonly workflowRepository: WorkflowRepository,
|
||||||
private readonly nodeTypes: NodeTypes,
|
private readonly nodeTypes: NodeTypes,
|
||||||
|
private readonly workflowRunner: WorkflowRunner,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findMany(req: ExecutionRequest.GetMany, sharedWorkflowIds: string[]) {
|
async findMany(req: ExecutionRequest.GetMany, sharedWorkflowIds: string[]) {
|
||||||
|
@ -276,8 +277,7 @@ export class ExecutionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
const retriedExecutionId = await this.workflowRunner.run(data);
|
||||||
const retriedExecutionId = await workflowRunner.run(data);
|
|
||||||
|
|
||||||
const executionData = await this.activeExecutions.getPostExecutePromise(retriedExecutionId);
|
const executionData = await this.activeExecutions.getPostExecutePromise(retriedExecutionId);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import type { IExecuteData, INode, IPinData, IRunExecutionData } from 'n8n-workflow';
|
import type {
|
||||||
|
IDeferredPromise,
|
||||||
|
IExecuteData,
|
||||||
|
IExecuteResponsePromiseData,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
IPinData,
|
||||||
|
IRunExecutionData,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
SubworkflowOperationError,
|
SubworkflowOperationError,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
@ -34,8 +44,52 @@ export class WorkflowExecutionService {
|
||||||
private readonly nodeTypes: NodeTypes,
|
private readonly nodeTypes: NodeTypes,
|
||||||
private readonly testWebhooks: TestWebhooks,
|
private readonly testWebhooks: TestWebhooks,
|
||||||
private readonly permissionChecker: PermissionChecker,
|
private readonly permissionChecker: PermissionChecker,
|
||||||
|
private readonly workflowRunner: WorkflowRunner,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async runWorkflow(
|
||||||
|
workflowData: IWorkflowDb,
|
||||||
|
node: INode,
|
||||||
|
data: INodeExecutionData[][],
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
mode: WorkflowExecuteMode,
|
||||||
|
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||||
|
) {
|
||||||
|
const nodeExecutionStack: IExecuteData[] = [
|
||||||
|
{
|
||||||
|
node,
|
||||||
|
data: {
|
||||||
|
main: data,
|
||||||
|
},
|
||||||
|
source: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const executionData: IRunExecutionData = {
|
||||||
|
startData: {},
|
||||||
|
resultData: {
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
executionData: {
|
||||||
|
contextData: {},
|
||||||
|
metadata: {},
|
||||||
|
nodeExecutionStack,
|
||||||
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the workflow
|
||||||
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
|
userId: additionalData.userId,
|
||||||
|
executionMode: mode,
|
||||||
|
executionData,
|
||||||
|
workflowData,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.workflowRunner.run(runData, true, undefined, undefined, responsePromise);
|
||||||
|
}
|
||||||
|
|
||||||
async executeManually(
|
async executeManually(
|
||||||
{
|
{
|
||||||
workflowData,
|
workflowData,
|
||||||
|
@ -92,8 +146,7 @@ export class WorkflowExecutionService {
|
||||||
data.startNodes = [pinnedTrigger.name];
|
data.startNodes = [pinnedTrigger.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
const executionId = await this.workflowRunner.run(data);
|
||||||
const executionId = await workflowRunner.run(data);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
executionId,
|
executionId,
|
||||||
|
@ -230,8 +283,7 @@ export class WorkflowExecutionService {
|
||||||
userId: runningUser.id,
|
userId: runningUser.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
await this.workflowRunner.run(runData);
|
||||||
await workflowRunner.run(runData);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorReporter.error(error);
|
ErrorReporter.error(error);
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
|
||||||
import { NodeApiError, NodeOperationError, Workflow } from 'n8n-workflow';
|
import { NodeApiError, NodeOperationError, Workflow } from 'n8n-workflow';
|
||||||
import type { IWebhookData, WorkflowActivateMode } from 'n8n-workflow';
|
import type { IWebhookData, WorkflowActivateMode } from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -12,20 +11,20 @@ import { SecretsHelper } from '@/SecretsHelpers';
|
||||||
import { WebhookService } from '@/services/webhook.service';
|
import { WebhookService } from '@/services/webhook.service';
|
||||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
import * as WebhookHelpers from '@/WebhookHelpers';
|
||||||
import * as AdditionalData from '@/WorkflowExecuteAdditionalData';
|
import * as AdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { chooseRandomly } from './shared/random';
|
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
import { ExecutionService } from '@/executions/execution.service';
|
||||||
|
import { WorkflowService } from '@/workflows/workflow.service';
|
||||||
|
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
||||||
|
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
|
import { chooseRandomly } from './shared/random';
|
||||||
import { setSchedulerAsLoadedNode } from './shared/utils';
|
import { setSchedulerAsLoadedNode } from './shared/utils';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import { createOwner } from './shared/db/users';
|
import { createOwner } from './shared/db/users';
|
||||||
import { createWorkflow } from './shared/db/workflows';
|
import { createWorkflow } from './shared/db/workflows';
|
||||||
import { ExecutionService } from '@/executions/execution.service';
|
|
||||||
import { WorkflowService } from '@/workflows/workflow.service';
|
|
||||||
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
|
||||||
|
|
||||||
mockInstance(ActiveExecutions);
|
mockInstance(ActiveExecutions);
|
||||||
mockInstance(Push);
|
mockInstance(Push);
|
||||||
|
@ -182,26 +181,6 @@ describe('isActive()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('runWorkflow()', () => {
|
|
||||||
test('should call `WorkflowRunner.run()`', async () => {
|
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
|
||||||
|
|
||||||
await activeWorkflowRunner.init();
|
|
||||||
|
|
||||||
const additionalData = await AdditionalData.getBase('fake-user-id');
|
|
||||||
|
|
||||||
const runSpy = jest
|
|
||||||
.spyOn(WorkflowRunner.prototype, 'run')
|
|
||||||
.mockResolvedValue('fake-execution-id');
|
|
||||||
|
|
||||||
const [node] = workflow.nodes;
|
|
||||||
|
|
||||||
await activeWorkflowRunner.runWorkflow(workflow, node, [[]], additionalData, 'trigger');
|
|
||||||
|
|
||||||
expect(runSpy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('executeErrorWorkflow()', () => {
|
describe('executeErrorWorkflow()', () => {
|
||||||
test('should call `WorkflowExecuteAdditionalData.executeErrorWorkflow()`', async () => {
|
test('should call `WorkflowExecuteAdditionalData.executeErrorWorkflow()`', async () => {
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import type { SuperAgentTest } from 'supertest';
|
||||||
import type { InstalledPackages } from '@db/entities/InstalledPackages';
|
import type { InstalledPackages } from '@db/entities/InstalledPackages';
|
||||||
import type { InstalledNodes } from '@db/entities/InstalledNodes';
|
import type { InstalledNodes } from '@db/entities/InstalledNodes';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
import { Push } from '@/push';
|
|
||||||
import { CommunityPackagesService } from '@/services/communityPackages.service';
|
import { CommunityPackagesService } from '@/services/communityPackages.service';
|
||||||
|
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
|
@ -16,7 +15,6 @@ const communityPackagesService = mockInstance(CommunityPackagesService, {
|
||||||
hasMissingPackages: false,
|
hasMissingPackages: false,
|
||||||
});
|
});
|
||||||
mockInstance(LoadNodesAndCredentials);
|
mockInstance(LoadNodesAndCredentials);
|
||||||
mockInstance(Push);
|
|
||||||
|
|
||||||
const testServer = setupTestServer({ endpointGroups: ['community-packages'] });
|
const testServer = setupTestServer({ endpointGroups: ['community-packages'] });
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { Push } from '@/push';
|
import { EnterpriseExecutionsService } from '@/executions/execution.service.ee';
|
||||||
|
import { WaitTracker } from '@/WaitTracker';
|
||||||
|
|
||||||
import { createSuccessfulExecution, getAllExecutions } from './shared/db/executions';
|
import { createSuccessfulExecution, getAllExecutions } from './shared/db/executions';
|
||||||
import { createOwner } from './shared/db/users';
|
import { createOwner } from './shared/db/users';
|
||||||
import { createWorkflow } from './shared/db/workflows';
|
import { createWorkflow } from './shared/db/workflows';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import { setupTestServer } from './shared/utils';
|
import { setupTestServer } from './shared/utils';
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
import { EnterpriseExecutionsService } from '@/executions/execution.service.ee';
|
|
||||||
|
|
||||||
mockInstance(EnterpriseExecutionsService);
|
mockInstance(EnterpriseExecutionsService);
|
||||||
|
mockInstance(WaitTracker);
|
||||||
|
|
||||||
mockInstance(Push);
|
|
||||||
let testServer = setupTestServer({ endpointGroups: ['executions'] });
|
let testServer = setupTestServer({ endpointGroups: ['executions'] });
|
||||||
|
|
||||||
let owner: User;
|
let owner: User;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import type { User } from '@db/entities/User';
|
||||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { Push } from '@/push';
|
|
||||||
import { ExecutionService } from '@/executions/execution.service';
|
import { ExecutionService } from '@/executions/execution.service';
|
||||||
|
|
||||||
import { randomApiKey } from '../shared/random';
|
import { randomApiKey } from '../shared/random';
|
||||||
|
@ -27,7 +26,6 @@ let workflowRunner: ActiveWorkflowRunner;
|
||||||
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
||||||
const license = testServer.license;
|
const license = testServer.license;
|
||||||
|
|
||||||
mockInstance(Push);
|
|
||||||
mockInstance(ExecutionService);
|
mockInstance(ExecutionService);
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
|
@ -11,14 +11,15 @@ import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { AUTH_COOKIE_NAME } from '@/constants';
|
|
||||||
|
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
|
||||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||||
import { mockNodeTypesData } from '../../../unit/Helpers';
|
import { AUTH_COOKIE_NAME } from '@/constants';
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
|
||||||
import { mockInstance } from '../../../shared/mocking';
|
|
||||||
import { ExecutionService } from '@/executions/execution.service';
|
import { ExecutionService } from '@/executions/execution.service';
|
||||||
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
|
import { Push } from '@/push';
|
||||||
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
|
import { mockNodeTypesData } from '../../../unit/Helpers';
|
||||||
|
import { mockInstance } from '../../../shared/mocking';
|
||||||
|
|
||||||
export { setupTestServer } from './testServer';
|
export { setupTestServer } from './testServer';
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ export { setupTestServer } from './testServer';
|
||||||
* Initialize node types.
|
* Initialize node types.
|
||||||
*/
|
*/
|
||||||
export async function initActiveWorkflowRunner() {
|
export async function initActiveWorkflowRunner() {
|
||||||
|
mockInstance(Push);
|
||||||
mockInstance(OrchestrationService);
|
mockInstance(OrchestrationService);
|
||||||
|
|
||||||
mockInstance(ExecutionService);
|
mockInstance(ExecutionService);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { issueJWT } from '@/auth/jwt';
|
||||||
import { registerController } from '@/decorators';
|
import { registerController } from '@/decorators';
|
||||||
import { rawBodyReader, bodyParser, setupAuthMiddlewares } from '@/middlewares';
|
import { rawBodyReader, bodyParser, setupAuthMiddlewares } from '@/middlewares';
|
||||||
import { PostHogClient } from '@/posthog';
|
import { PostHogClient } from '@/posthog';
|
||||||
|
import { Push } from '@/push';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
@ -78,6 +79,7 @@ export const setupTestServer = ({
|
||||||
mockInstance(Logger);
|
mockInstance(Logger);
|
||||||
mockInstance(InternalHooks);
|
mockInstance(InternalHooks);
|
||||||
mockInstance(PostHogClient);
|
mockInstance(PostHogClient);
|
||||||
|
mockInstance(Push);
|
||||||
|
|
||||||
const testServer: TestServer = {
|
const testServer: TestServer = {
|
||||||
app,
|
app,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import type { INode } from 'n8n-workflow';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { Push } from '@/push';
|
|
||||||
import { WorkflowSharingService } from '@/workflows/workflowSharing.service';
|
import { WorkflowSharingService } from '@/workflows/workflowSharing.service';
|
||||||
|
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
|
@ -30,7 +29,6 @@ let authAnotherMemberAgent: SuperAgentTest;
|
||||||
let saveCredential: SaveCredentialFunction;
|
let saveCredential: SaveCredentialFunction;
|
||||||
|
|
||||||
const activeWorkflowRunner = mockInstance(ActiveWorkflowRunner);
|
const activeWorkflowRunner = mockInstance(ActiveWorkflowRunner);
|
||||||
mockInstance(Push);
|
|
||||||
|
|
||||||
const sharingSpy = jest.spyOn(License.prototype, 'isSharingEnabled').mockReturnValue(true);
|
const sharingSpy = jest.spyOn(License.prototype, 'isSharingEnabled').mockReturnValue(true);
|
||||||
const testServer = utils.setupTestServer({
|
const testServer = utils.setupTestServer({
|
||||||
|
|
|
@ -9,7 +9,6 @@ import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { Push } from '@/push';
|
|
||||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||||
|
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
|
@ -34,7 +33,6 @@ const license = testServer.license;
|
||||||
const { objectContaining, arrayContaining, any } = expect;
|
const { objectContaining, arrayContaining, any } = expect;
|
||||||
|
|
||||||
const activeWorkflowRunnerLike = mockInstance(ActiveWorkflowRunner);
|
const activeWorkflowRunnerLike = mockInstance(ActiveWorkflowRunner);
|
||||||
mockInstance(Push);
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
owner = await createOwner();
|
owner = await createOwner();
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||||
import PCancelable from 'p-cancelable';
|
import PCancelable from 'p-cancelable';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { Container } from 'typedi';
|
|
||||||
import type { IExecuteResponsePromiseData, IRun } from 'n8n-workflow';
|
import type { IExecuteResponsePromiseData, IRun } from 'n8n-workflow';
|
||||||
import { createDeferredPromise } from 'n8n-workflow';
|
import { createDeferredPromise } from 'n8n-workflow';
|
||||||
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
|
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||||
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
import type { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
const FAKE_EXECUTION_ID = '15';
|
const FAKE_EXECUTION_ID = '15';
|
||||||
|
@ -14,7 +13,7 @@ const FAKE_SECOND_EXECUTION_ID = '20';
|
||||||
const updateExistingExecution = jest.fn();
|
const updateExistingExecution = jest.fn();
|
||||||
const createNewExecution = jest.fn(async () => FAKE_EXECUTION_ID);
|
const createNewExecution = jest.fn(async () => FAKE_EXECUTION_ID);
|
||||||
|
|
||||||
Container.set(ExecutionRepository, {
|
const executionRepository = mock<ExecutionRepository>({
|
||||||
updateExistingExecution,
|
updateExistingExecution,
|
||||||
createNewExecution,
|
createNewExecution,
|
||||||
});
|
});
|
||||||
|
@ -23,7 +22,7 @@ describe('ActiveExecutions', () => {
|
||||||
let activeExecutions: ActiveExecutions;
|
let activeExecutions: ActiveExecutions;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
activeExecutions = new ActiveExecutions(mock());
|
activeExecutions = new ActiveExecutions(mock(), executionRepository);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('WaitTracker', () => {
|
||||||
it('should query DB for waiting executions', async () => {
|
it('should query DB for waiting executions', async () => {
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
||||||
|
|
||||||
new WaitTracker(mock(), executionRepository, mock());
|
new WaitTracker(mock(), executionRepository, mock(), mock());
|
||||||
|
|
||||||
expect(executionRepository.getWaitingExecutions).toHaveBeenCalledTimes(1);
|
expect(executionRepository.getWaitingExecutions).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ describe('WaitTracker', () => {
|
||||||
it('if no executions to start, should do nothing', () => {
|
it('if no executions to start, should do nothing', () => {
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
||||||
|
|
||||||
new WaitTracker(mock(), executionRepository, mock());
|
new WaitTracker(mock(), executionRepository, mock(), mock());
|
||||||
|
|
||||||
expect(executionRepository.findSingleExecution).not.toHaveBeenCalled();
|
expect(executionRepository.findSingleExecution).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,7 @@ describe('WaitTracker', () => {
|
||||||
describe('if execution to start', () => {
|
describe('if execution to start', () => {
|
||||||
it('if not enough time passed, should not start execution', async () => {
|
it('if not enough time passed, should not start execution', async () => {
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
||||||
const waitTracker = new WaitTracker(mock(), executionRepository, mock());
|
const waitTracker = new WaitTracker(mock(), executionRepository, mock(), mock());
|
||||||
|
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
||||||
await waitTracker.getWaitingExecutions();
|
await waitTracker.getWaitingExecutions();
|
||||||
|
@ -51,7 +51,7 @@ describe('WaitTracker', () => {
|
||||||
|
|
||||||
it('if enough time passed, should start execution', async () => {
|
it('if enough time passed, should start execution', async () => {
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
||||||
const waitTracker = new WaitTracker(mock(), executionRepository, mock());
|
const waitTracker = new WaitTracker(mock(), executionRepository, mock(), mock());
|
||||||
|
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([execution]);
|
||||||
await waitTracker.getWaitingExecutions();
|
await waitTracker.getWaitingExecutions();
|
||||||
|
@ -68,7 +68,7 @@ describe('WaitTracker', () => {
|
||||||
describe('startExecution()', () => {
|
describe('startExecution()', () => {
|
||||||
it('should query for execution to start', async () => {
|
it('should query for execution to start', async () => {
|
||||||
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
executionRepository.getWaitingExecutions.mockResolvedValue([]);
|
||||||
const waitTracker = new WaitTracker(mock(), executionRepository, mock());
|
const waitTracker = new WaitTracker(mock(), executionRepository, mock(), mock());
|
||||||
|
|
||||||
executionRepository.findSingleExecution.mockResolvedValue(execution);
|
executionRepository.findSingleExecution.mockResolvedValue(execution);
|
||||||
waitTracker.startExecution(execution.id);
|
waitTracker.startExecution(execution.id);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
import Container from 'typedi';
|
||||||
|
import { WorkflowHooks, type ExecutionError, type IWorkflowExecuteHooks } from 'n8n-workflow';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
import { WorkflowHooks, type ExecutionError, type IWorkflowExecuteHooks } from 'n8n-workflow';
|
|
||||||
import { Push } from '@/push';
|
|
||||||
import Container from 'typedi';
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
import { mockInstance } from '../shared/mocking';
|
|
||||||
import * as testDb from '../integration/shared/testDb';
|
import * as testDb from '../integration/shared/testDb';
|
||||||
import { setupTestServer } from '../integration/shared/utils';
|
import { setupTestServer } from '../integration/shared/utils';
|
||||||
import { createUser } from '../integration/shared/db/users';
|
import { createUser } from '../integration/shared/db/users';
|
||||||
|
@ -26,10 +24,7 @@ const watchedWorkflowExecuteAfter = jest.spyOn(watchers, 'workflowExecuteAfter')
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
owner = await createUser({ role: 'global:owner' });
|
owner = await createUser({ role: 'global:owner' });
|
||||||
|
|
||||||
mockInstance(Push);
|
runner = Container.get(WorkflowRunner);
|
||||||
Container.set(Push, new Push());
|
|
||||||
|
|
||||||
runner = new WorkflowRunner();
|
|
||||||
|
|
||||||
hookFunctions = {
|
hookFunctions = {
|
||||||
workflowExecuteAfter: [watchers.workflowExecuteAfter],
|
workflowExecuteAfter: [watchers.workflowExecuteAfter],
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import type { INode } from 'n8n-workflow';
|
import type { INode } from 'n8n-workflow';
|
||||||
import { WorkflowExecutionService } from '@/workflows/workflowExecution.service';
|
|
||||||
import type { IWorkflowDb } from '@/Interfaces';
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
|
import type { IWorkflowDb } from '@/Interfaces';
|
||||||
|
import { WorkflowExecutionService } from '@/workflows/workflowExecution.service';
|
||||||
|
import type { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
|
|
||||||
const webhookNode: INode = {
|
const webhookNode: INode = {
|
||||||
name: 'Webhook',
|
name: 'Webhook',
|
||||||
type: 'n8n-nodes-base.webhook',
|
type: 'n8n-nodes-base.webhook',
|
||||||
|
@ -47,17 +50,28 @@ const hackerNewsNode: INode = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('WorkflowExecutionService', () => {
|
describe('WorkflowExecutionService', () => {
|
||||||
let workflowExecutionService: WorkflowExecutionService;
|
const workflowRunner = mock<WorkflowRunner>();
|
||||||
|
const workflowExecutionService = new WorkflowExecutionService(
|
||||||
|
mock(),
|
||||||
|
mock(),
|
||||||
|
mock(),
|
||||||
|
mock(),
|
||||||
|
mock(),
|
||||||
|
mock(),
|
||||||
|
workflowRunner,
|
||||||
|
);
|
||||||
|
|
||||||
beforeAll(() => {
|
describe('runWorkflow()', () => {
|
||||||
workflowExecutionService = new WorkflowExecutionService(
|
test('should call `WorkflowRunner.run()`', async () => {
|
||||||
mock(),
|
const node = mock<INode>();
|
||||||
mock(),
|
const workflow = mock<WorkflowEntity>({ active: true, nodes: [node] });
|
||||||
mock(),
|
|
||||||
mock(),
|
workflowRunner.run.mockResolvedValue('fake-execution-id');
|
||||||
mock(),
|
|
||||||
mock(),
|
await workflowExecutionService.runWorkflow(workflow, node, [[]], mock(), 'trigger');
|
||||||
);
|
|
||||||
|
expect(workflowRunner.run).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('selectPinnedActivatorStarter()', () => {
|
describe('selectPinnedActivatorStarter()', () => {
|
||||||
|
|
Loading…
Reference in a new issue