diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index e0e03ad119..1e36b4e65d 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -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, ) {} diff --git a/packages/cli/src/executions/executions.service.ee.ts b/packages/cli/src/executions/execution.service.ee.ts similarity index 51% rename from packages/cli/src/executions/executions.service.ee.ts rename to packages/cli/src/executions/execution.service.ee.ts index 5182f147e8..4b5ab89892 100644 --- a/packages/cli/src/executions/executions.service.ee.ts +++ b/packages/cli/src/executions/execution.service.ee.ts @@ -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 { - // 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 { - 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, diff --git a/packages/cli/src/executions/executions.service.ts b/packages/cli/src/executions/execution.service.ts similarity index 77% rename from packages/cli/src/executions/executions.service.ts rename to packages/cli/src/executions/execution.service.ts index 5ae2b26abd..c818cb660e 100644 --- a/packages/cli/src/executions/executions.service.ts +++ b/packages/cli/src/executions/execution.service.ts @@ -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 { - // 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 { - 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 { - 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 { - 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 { - 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 { + ) { 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); } } diff --git a/packages/cli/src/executions/executions.controller.ts b/packages/cli/src/executions/executions.controller.ts index d068c75ec0..b4163cad04 100644 --- a/packages/cli/src/executions/executions.controller.ts +++ b/packages/cli/src/executions/executions.controller.ts @@ -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); } } diff --git a/packages/cli/test/integration/ActiveWorkflowRunner.test.ts b/packages/cli/test/integration/ActiveWorkflowRunner.test.ts index d4f2baa5e2..c524aa082a 100644 --- a/packages/cli/test/integration/ActiveWorkflowRunner.test.ts +++ b/packages/cli/test/integration/ActiveWorkflowRunner.test.ts @@ -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); diff --git a/packages/cli/test/integration/executions.controller.test.ts b/packages/cli/test/integration/executions.controller.test.ts index 5d4bfe5307..35c5dd12f0 100644 --- a/packages/cli/test/integration/executions.controller.test.ts +++ b/packages/cli/test/integration/executions.controller.test.ts @@ -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'] }); diff --git a/packages/cli/test/integration/publicApi/workflows.test.ts b/packages/cli/test/integration/publicApi/workflows.test.ts index 47b0c2c15f..7912a1fd14 100644 --- a/packages/cli/test/integration/publicApi/workflows.test.ts +++ b/packages/cli/test/integration/publicApi/workflows.test.ts @@ -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(); diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index 63fcd48621..1959a4deec 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -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(); diff --git a/packages/cli/test/integration/users.api.test.ts b/packages/cli/test/integration/users.api.test.ts index 94818e034e..4b108e6753 100644 --- a/packages/cli/test/integration/users.api.test.ts +++ b/packages/cli/test/integration/users.api.test.ts @@ -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'],