mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
refactor(core): Use DI in execution services (no-changelog) (#8358)
This commit is contained in:
parent
01280815c9
commit
2eb829a6b4
|
@ -35,7 +35,7 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
|
|||
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import { ExecutionsService } from './executions/executions.service';
|
||||
import { ExecutionService } from './executions/execution.service';
|
||||
import {
|
||||
STARTING_NODES,
|
||||
WORKFLOW_REACTIVATE_INITIAL_TIMEOUT,
|
||||
|
@ -74,7 +74,7 @@ export class ActiveWorkflowRunner {
|
|||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly multiMainSetup: MultiMainSetup,
|
||||
private readonly activationErrorsService: ActivationErrorsService,
|
||||
private readonly executionService: ExecutionsService,
|
||||
private readonly executionService: ExecutionService,
|
||||
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
||||
private readonly activeWorkflowsService: ActiveWorkflowsService,
|
||||
) {}
|
||||
|
|
|
@ -1,41 +1,38 @@
|
|||
import Container from 'typedi';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { ExecutionsService } from './executions.service';
|
||||
import { ExecutionService } from './execution.service';
|
||||
import type { ExecutionRequest } from './execution.request';
|
||||
import type { IExecutionResponse, IExecutionFlattedResponse } from '@/Interfaces';
|
||||
import { EnterpriseWorkflowService } from '../workflows/workflow.service.ee';
|
||||
import type { WorkflowWithSharingsAndCredentials } from '@/workflows/workflows.types';
|
||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||
import { WorkflowSharingService } from '@/workflows/workflowSharing.service';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
export class EnterpriseExecutionsService extends ExecutionsService {
|
||||
/**
|
||||
* Function to get the workflow Ids for a User regardless of role
|
||||
*/
|
||||
static async getWorkflowIdsForUser(user: User): Promise<string[]> {
|
||||
// Get all workflows
|
||||
return Container.get(WorkflowSharingService).getSharedWorkflowIds(user);
|
||||
}
|
||||
@Service()
|
||||
export class EnterpriseExecutionsService {
|
||||
constructor(
|
||||
private readonly executionService: ExecutionService,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly enterpriseWorkflowService: EnterpriseWorkflowService,
|
||||
) {}
|
||||
|
||||
static async getExecution(
|
||||
async getExecution(
|
||||
req: ExecutionRequest.Get,
|
||||
sharedWorkflowIds: string[],
|
||||
): Promise<IExecutionResponse | IExecutionFlattedResponse | undefined> {
|
||||
const execution = await super.getExecution(req);
|
||||
const execution = await this.executionService.getExecution(req, sharedWorkflowIds);
|
||||
|
||||
if (!execution) return;
|
||||
|
||||
const relations = ['shared', 'shared.user', 'shared.role'];
|
||||
|
||||
const workflow = (await Container.get(WorkflowRepository).get(
|
||||
const workflow = (await this.workflowRepository.get(
|
||||
{ id: execution.workflowId },
|
||||
{ relations },
|
||||
)) as WorkflowWithSharingsAndCredentials;
|
||||
|
||||
if (!workflow) return;
|
||||
|
||||
const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService);
|
||||
|
||||
enterpriseWorkflowService.addOwnerAndSharings(workflow);
|
||||
await enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user);
|
||||
this.enterpriseWorkflowService.addOwnerAndSharings(workflow);
|
||||
await this.enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user);
|
||||
|
||||
execution.workflowData = {
|
||||
...execution.workflowData,
|
|
@ -1,4 +1,4 @@
|
|||
import { Container, Service } from 'typedi';
|
||||
import { Service } from 'typedi';
|
||||
import { validate as jsonSchemaValidate } from 'jsonschema';
|
||||
import type {
|
||||
IWorkflowBase,
|
||||
|
@ -12,12 +12,10 @@ import { ApplicationError, jsonParse, Workflow, WorkflowOperationError } from 'n
|
|||
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import config from '@/config';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type {
|
||||
ExecutionPayload,
|
||||
IExecutionFlattedResponse,
|
||||
IExecutionResponse,
|
||||
IExecutionsListResponse,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
} from '@/Interfaces';
|
||||
|
@ -33,7 +31,6 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
|||
import { Logger } from '@/Logger';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { WorkflowSharingService } from '@/workflows/workflowSharing.service';
|
||||
|
||||
const schemaGetExecutionsQueryFilter = {
|
||||
$id: '/IGetExecutionsQueryFilter',
|
||||
|
@ -71,21 +68,18 @@ const schemaGetExecutionsQueryFilter = {
|
|||
const allowedExecutionsQueryFilterFields = Object.keys(schemaGetExecutionsQueryFilter.properties);
|
||||
|
||||
@Service()
|
||||
export class ExecutionsService {
|
||||
/**
|
||||
* Function to get the workflow Ids for a User
|
||||
* Overridden in EE version to ignore roles
|
||||
*/
|
||||
static async getWorkflowIdsForUser(user: User): Promise<string[]> {
|
||||
// Get all workflows using owner role
|
||||
return Container.get(WorkflowSharingService).getSharedWorkflowIds(user, ['owner']);
|
||||
}
|
||||
export class ExecutionService {
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly queue: Queue,
|
||||
private readonly activeExecutions: ActiveExecutions,
|
||||
private readonly executionRepository: ExecutionRepository,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly nodeTypes: NodeTypes,
|
||||
) {}
|
||||
|
||||
static async getExecutionsList(req: ExecutionRequest.GetAll): Promise<IExecutionsListResponse> {
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(req.user);
|
||||
async getExecutionsList(req: ExecutionRequest.GetAll, sharedWorkflowIds: string[]) {
|
||||
if (sharedWorkflowIds.length === 0) {
|
||||
// return early since without shared workflows there can be no hits
|
||||
// (note: getSharedWorkflowIds() returns _all_ workflow ids for global owners)
|
||||
return {
|
||||
count: 0,
|
||||
estimated: false,
|
||||
|
@ -107,7 +101,7 @@ export class ExecutionsService {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Container.get(Logger).error('Failed to parse filter', {
|
||||
this.logger.error('Failed to parse filter', {
|
||||
userId: req.user.id,
|
||||
filter: req.query.filter,
|
||||
});
|
||||
|
@ -118,7 +112,7 @@ export class ExecutionsService {
|
|||
// safeguard against querying workflowIds not shared with the user
|
||||
const workflowId = filter?.workflowId?.toString();
|
||||
if (workflowId !== undefined && !sharedWorkflowIds.includes(workflowId)) {
|
||||
Container.get(Logger).verbose(
|
||||
this.logger.verbose(
|
||||
`User ${req.user.id} attempted to query non-shared workflow ${workflowId}`,
|
||||
);
|
||||
return {
|
||||
|
@ -135,26 +129,21 @@ export class ExecutionsService {
|
|||
const executingWorkflowIds: string[] = [];
|
||||
|
||||
if (config.getEnv('executions.mode') === 'queue') {
|
||||
const queue = Container.get(Queue);
|
||||
const currentJobs = await queue.getJobs(['active', 'waiting']);
|
||||
const currentJobs = await this.queue.getJobs(['active', 'waiting']);
|
||||
executingWorkflowIds.push(...currentJobs.map(({ data }) => data.executionId));
|
||||
}
|
||||
|
||||
// We may have manual executions even with queue so we must account for these.
|
||||
executingWorkflowIds.push(
|
||||
...Container.get(ActiveExecutions)
|
||||
.getActiveExecutions()
|
||||
.map(({ id }) => id),
|
||||
);
|
||||
executingWorkflowIds.push(...this.activeExecutions.getActiveExecutions().map(({ id }) => id));
|
||||
|
||||
const { count, estimated } = await Container.get(ExecutionRepository).countExecutions(
|
||||
const { count, estimated } = await this.executionRepository.countExecutions(
|
||||
filter,
|
||||
sharedWorkflowIds,
|
||||
executingWorkflowIds,
|
||||
req.user.hasGlobalScope('workflow:list'),
|
||||
);
|
||||
|
||||
const formattedExecutions = await Container.get(ExecutionRepository).searchExecutions(
|
||||
const formattedExecutions = await this.executionRepository.searchExecutions(
|
||||
filter,
|
||||
limit,
|
||||
executingWorkflowIds,
|
||||
|
@ -171,26 +160,20 @@ export class ExecutionsService {
|
|||
};
|
||||
}
|
||||
|
||||
static async getExecution(
|
||||
async getExecution(
|
||||
req: ExecutionRequest.Get,
|
||||
sharedWorkflowIds: string[],
|
||||
): Promise<IExecutionResponse | IExecutionFlattedResponse | undefined> {
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(req.user);
|
||||
if (!sharedWorkflowIds.length) return undefined;
|
||||
|
||||
const { id: executionId } = req.params;
|
||||
const execution = await Container.get(ExecutionRepository).findIfShared(
|
||||
executionId,
|
||||
sharedWorkflowIds,
|
||||
);
|
||||
const execution = await this.executionRepository.findIfShared(executionId, sharedWorkflowIds);
|
||||
|
||||
if (!execution) {
|
||||
Container.get(Logger).info(
|
||||
'Attempt to read execution was blocked due to insufficient permissions',
|
||||
{
|
||||
userId: req.user.id,
|
||||
executionId,
|
||||
},
|
||||
);
|
||||
this.logger.info('Attempt to read execution was blocked due to insufficient permissions', {
|
||||
userId: req.user.id,
|
||||
executionId,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -201,18 +184,17 @@ export class ExecutionsService {
|
|||
return execution;
|
||||
}
|
||||
|
||||
static async retryExecution(req: ExecutionRequest.Retry): Promise<boolean> {
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(req.user);
|
||||
async retryExecution(req: ExecutionRequest.Retry, sharedWorkflowIds: string[]) {
|
||||
if (!sharedWorkflowIds.length) return false;
|
||||
|
||||
const { id: executionId } = req.params;
|
||||
const execution = (await Container.get(ExecutionRepository).findIfShared(
|
||||
const execution = (await this.executionRepository.findIfShared(
|
||||
executionId,
|
||||
sharedWorkflowIds,
|
||||
)) as unknown as IExecutionResponse;
|
||||
|
||||
if (!execution) {
|
||||
Container.get(Logger).info(
|
||||
this.logger.info(
|
||||
'Attempt to retry an execution was blocked due to insufficient permissions',
|
||||
{
|
||||
userId: req.user.id,
|
||||
|
@ -260,7 +242,7 @@ export class ExecutionsService {
|
|||
// Loads the currently saved workflow to execute instead of the
|
||||
// one saved at the time of the execution.
|
||||
const workflowId = execution.workflowData.id;
|
||||
const workflowData = (await Container.get(WorkflowRepository).findOneBy({
|
||||
const workflowData = (await this.workflowRepository.findOneBy({
|
||||
id: workflowId,
|
||||
})) as IWorkflowBase;
|
||||
|
||||
|
@ -272,14 +254,14 @@ export class ExecutionsService {
|
|||
}
|
||||
|
||||
data.workflowData = workflowData;
|
||||
const nodeTypes = Container.get(NodeTypes);
|
||||
|
||||
const workflowInstance = new Workflow({
|
||||
id: workflowData.id,
|
||||
name: workflowData.name,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
nodeTypes: this.nodeTypes,
|
||||
staticData: undefined,
|
||||
settings: workflowData.settings,
|
||||
});
|
||||
|
@ -289,14 +271,11 @@ export class ExecutionsService {
|
|||
// Find the data of the last executed node in the new workflow
|
||||
const node = workflowInstance.getNode(stack.node.name);
|
||||
if (node === null) {
|
||||
Container.get(Logger).error(
|
||||
'Failed to retry an execution because a node could not be found',
|
||||
{
|
||||
userId: req.user.id,
|
||||
executionId,
|
||||
nodeName: stack.node.name,
|
||||
},
|
||||
);
|
||||
this.logger.error('Failed to retry an execution because a node could not be found', {
|
||||
userId: req.user.id,
|
||||
executionId,
|
||||
nodeName: stack.node.name,
|
||||
});
|
||||
throw new WorkflowOperationError(
|
||||
`Could not find the node "${stack.node.name}" in workflow. It probably got deleted or renamed. Without it the workflow can sadly not be retried.`,
|
||||
);
|
||||
|
@ -310,8 +289,7 @@ export class ExecutionsService {
|
|||
const workflowRunner = new WorkflowRunner();
|
||||
const retriedExecutionId = await workflowRunner.run(data);
|
||||
|
||||
const executionData =
|
||||
await Container.get(ActiveExecutions).getPostExecutePromise(retriedExecutionId);
|
||||
const executionData = await this.activeExecutions.getPostExecutePromise(retriedExecutionId);
|
||||
|
||||
if (!executionData) {
|
||||
throw new ApplicationError('The retry did not start for an unknown reason.');
|
||||
|
@ -320,8 +298,7 @@ export class ExecutionsService {
|
|||
return !!executionData.finished;
|
||||
}
|
||||
|
||||
static async deleteExecutions(req: ExecutionRequest.Delete): Promise<void> {
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(req.user);
|
||||
async deleteExecutions(req: ExecutionRequest.Delete, sharedWorkflowIds: string[]) {
|
||||
if (sharedWorkflowIds.length === 0) {
|
||||
// return early since without shared workflows there can be no hits
|
||||
// (note: getSharedWorkflowIds() returns _all_ workflow ids for global owners)
|
||||
|
@ -342,14 +319,10 @@ export class ExecutionsService {
|
|||
}
|
||||
}
|
||||
|
||||
return Container.get(ExecutionRepository).deleteExecutionsByFilter(
|
||||
requestFilters,
|
||||
sharedWorkflowIds,
|
||||
{
|
||||
deleteBefore,
|
||||
ids,
|
||||
},
|
||||
);
|
||||
return this.executionRepository.deleteExecutionsByFilter(requestFilters, sharedWorkflowIds, {
|
||||
deleteBefore,
|
||||
ids,
|
||||
});
|
||||
}
|
||||
|
||||
async createErrorExecution(
|
||||
|
@ -358,7 +331,7 @@ export class ExecutionsService {
|
|||
workflowData: IWorkflowDb,
|
||||
workflow: Workflow,
|
||||
mode: WorkflowExecuteMode,
|
||||
): Promise<void> {
|
||||
) {
|
||||
const saveDataErrorExecutionDisabled =
|
||||
workflowData?.settings?.saveDataErrorExecution === 'none';
|
||||
|
||||
|
@ -420,6 +393,6 @@ export class ExecutionsService {
|
|||
status: 'error',
|
||||
};
|
||||
|
||||
await Container.get(ExecutionRepository).createNewExecution(fullExecutionData);
|
||||
await this.executionRepository.createNewExecution(fullExecutionData);
|
||||
}
|
||||
}
|
|
@ -1,31 +1,53 @@
|
|||
import { ExecutionRequest } from './execution.request';
|
||||
import { ExecutionsService } from './executions.service';
|
||||
import { ExecutionService } from './execution.service';
|
||||
import { Authorized, Get, Post, RestController } from '@/decorators';
|
||||
import { EnterpriseExecutionsService } from './executions.service.ee';
|
||||
import { EnterpriseExecutionsService } from './execution.service.ee';
|
||||
import { isSharingEnabled } from '@/UserManagement/UserManagementHelper';
|
||||
import { WorkflowSharingService } from '@/workflows/workflowSharing.service';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/executions')
|
||||
export class ExecutionsController {
|
||||
constructor(
|
||||
private readonly executionService: ExecutionService,
|
||||
private readonly enterpriseExecutionService: EnterpriseExecutionsService,
|
||||
private readonly workflowSharingService: WorkflowSharingService,
|
||||
) {}
|
||||
|
||||
private async getAccessibleWorkflowIds(user: User) {
|
||||
return isSharingEnabled()
|
||||
? this.workflowSharingService.getSharedWorkflowIds(user)
|
||||
: this.workflowSharingService.getSharedWorkflowIds(user, ['owner']);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
async getExecutionsList(req: ExecutionRequest.GetAll) {
|
||||
return ExecutionsService.getExecutionsList(req);
|
||||
const workflowIds = await this.getAccessibleWorkflowIds(req.user);
|
||||
|
||||
return this.executionService.getExecutionsList(req, workflowIds);
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
async getExecution(req: ExecutionRequest.Get) {
|
||||
const workflowIds = await this.getAccessibleWorkflowIds(req.user);
|
||||
|
||||
return isSharingEnabled()
|
||||
? EnterpriseExecutionsService.getExecution(req)
|
||||
: ExecutionsService.getExecution(req);
|
||||
? this.enterpriseExecutionService.getExecution(req, workflowIds)
|
||||
: this.executionService.getExecution(req, workflowIds);
|
||||
}
|
||||
|
||||
@Post('/:id/retry')
|
||||
async retryExecution(req: ExecutionRequest.Retry) {
|
||||
return ExecutionsService.retryExecution(req);
|
||||
const workflowIds = await this.getAccessibleWorkflowIds(req.user);
|
||||
|
||||
return this.executionService.retryExecution(req, workflowIds);
|
||||
}
|
||||
|
||||
@Post('/delete')
|
||||
async deleteExecutions(req: ExecutionRequest.Delete) {
|
||||
return ExecutionsService.deleteExecutions(req);
|
||||
const workflowIds = await this.getAccessibleWorkflowIds(req.user);
|
||||
|
||||
return this.executionService.deleteExecutions(req, workflowIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ import { setSchedulerAsLoadedNode } from './shared/utils';
|
|||
import * as testDb from './shared/testDb';
|
||||
import { createOwner } from './shared/db/users';
|
||||
import { createWorkflow } from './shared/db/workflows';
|
||||
import { ExecutionsService } from '@/executions/executions.service';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
import { WorkflowService } from '@/workflows/workflow.service';
|
||||
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
||||
|
||||
mockInstance(ActiveExecutions);
|
||||
mockInstance(Push);
|
||||
mockInstance(SecretsHelper);
|
||||
mockInstance(ExecutionsService);
|
||||
mockInstance(ExecutionService);
|
||||
mockInstance(WorkflowService);
|
||||
|
||||
const webhookService = mockInstance(WebhookService);
|
||||
|
|
|
@ -6,6 +6,9 @@ import { createWorkflow } from './shared/db/workflows';
|
|||
import * as testDb from './shared/testDb';
|
||||
import { setupTestServer } from './shared/utils';
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
import { EnterpriseExecutionsService } from '@/executions/execution.service.ee';
|
||||
|
||||
mockInstance(EnterpriseExecutionsService);
|
||||
|
||||
mockInstance(Push);
|
||||
let testServer = setupTestServer({ endpointGroups: ['executions'] });
|
||||
|
|
|
@ -18,7 +18,7 @@ import { createWorkflow, createWorkflowWithTrigger } from '../shared/db/workflow
|
|||
import { createTag } from '../shared/db/tags';
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
import { Push } from '@/push';
|
||||
import { ExecutionsService } from '@/executions/executions.service';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
|
||||
let workflowOwnerRole: Role;
|
||||
let owner: User;
|
||||
|
@ -31,7 +31,7 @@ const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
|||
const license = testServer.license;
|
||||
|
||||
mockInstance(Push);
|
||||
mockInstance(ExecutionsService);
|
||||
mockInstance(ExecutionService);
|
||||
|
||||
beforeAll(async () => {
|
||||
const [globalOwnerRole, globalMemberRole, fetchedWorkflowOwnerRole] = await getAllRoles();
|
||||
|
|
|
@ -18,7 +18,7 @@ import { SettingsRepository } from '@db/repositories/settings.repository';
|
|||
import { mockNodeTypesData } from '../../../unit/Helpers';
|
||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
||||
import { mockInstance } from '../../../shared/mocking';
|
||||
import { ExecutionsService } from '@/executions/executions.service';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
|
||||
export { setupTestServer } from './testServer';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export { setupTestServer } from './testServer';
|
|||
export async function initActiveWorkflowRunner() {
|
||||
mockInstance(MultiMainSetup);
|
||||
|
||||
mockInstance(ExecutionsService);
|
||||
mockInstance(ExecutionService);
|
||||
const { ActiveWorkflowRunner } = await import('@/ActiveWorkflowRunner');
|
||||
const workflowRunner = Container.get(ActiveWorkflowRunner);
|
||||
await workflowRunner.init();
|
||||
|
|
|
@ -18,10 +18,10 @@ import * as testDb from './shared/testDb';
|
|||
import type { SuperAgentTest } from 'supertest';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { ExecutionsService } from '@/executions/executions.service';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
|
||||
mockInstance(ExecutionsService);
|
||||
mockInstance(ExecutionService);
|
||||
|
||||
const testServer = utils.setupTestServer({
|
||||
endpointGroups: ['users'],
|
||||
|
|
Loading…
Reference in a new issue