refactor(core): Use DI in execution services (no-changelog) (#8358)

This commit is contained in:
Iván Ovejero 2024-01-17 15:42:19 +01:00 committed by GitHub
parent 01280815c9
commit 2eb829a6b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 101 additions and 106 deletions

View file

@ -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,
) {}

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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'] });

View file

@ -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();

View file

@ -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();

View file

@ -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'],