diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index b99bd8adee..d76d78c99a 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -54,6 +54,8 @@ import { ExecutionDataRepository } from './execution-data.repository'; import type { ExecutionData } from '../entities/execution-data'; import { ExecutionEntity } from '../entities/execution-entity'; import { ExecutionMetadata } from '../entities/execution-metadata'; +import { SharedWorkflow } from '../entities/shared-workflow'; +import { WorkflowEntity } from '../entities/workflow-entity'; export interface IGetExecutionsQueryFilter { id?: FindOperator | string; @@ -874,6 +876,7 @@ export class ExecutionRepository extends Repository { metadata, annotationTags, vote, + projectId, } = query; const fields = Object.keys(this.summaryFields) @@ -945,6 +948,12 @@ export class ExecutionRepository extends Repository { } } + if (projectId) { + qb.innerJoin(WorkflowEntity, 'w', 'w.id = execution.workflowId') + .innerJoin(SharedWorkflow, 'sw', 'sw.workflowId = w.id') + .where('sw.projectId = :projectId', { projectId }); + } + return qb; } diff --git a/packages/cli/src/executions/execution.types.ts b/packages/cli/src/executions/execution.types.ts index ba574199a0..04d68d8197 100644 --- a/packages/cli/src/executions/execution.types.ts +++ b/packages/cli/src/executions/execution.types.ts @@ -80,6 +80,7 @@ export namespace ExecutionSummaries { startedBefore: string; annotationTags: string[]; // tag IDs vote: AnnotationVote; + projectId: string; }>; type AccessFields = { diff --git a/packages/cli/test/integration/execution.service.integration.test.ts b/packages/cli/test/integration/execution.service.integration.test.ts index 05061e536e..15d97f69ab 100644 --- a/packages/cli/test/integration/execution.service.integration.test.ts +++ b/packages/cli/test/integration/execution.service.integration.test.ts @@ -6,6 +6,7 @@ import { ExecutionRepository } from '@/databases/repositories/execution.reposito import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { ExecutionService } from '@/executions/execution.service'; import type { ExecutionSummaries } from '@/executions/execution.types'; +import { createTeamProject } from '@test-integration/db/projects'; import { annotateExecution, createAnnotationTags, createExecution } from './shared/db/executions'; import { createWorkflow } from './shared/db/workflows'; @@ -294,6 +295,37 @@ describe('ExecutionService', () => { }); }); + test('should filter executions by `projectId`', async () => { + const firstProject = await createTeamProject(); + const secondProject = await createTeamProject(); + + const firstWorkflow = await createWorkflow(undefined, firstProject); + const secondWorkflow = await createWorkflow(undefined, secondProject); + + await createExecution({ status: 'success' }, firstWorkflow); + await createExecution({ status: 'success' }, firstWorkflow); + await createExecution({ status: 'success' }, secondWorkflow); // to filter out + + const query: ExecutionSummaries.RangeQuery = { + kind: 'range', + range: { limit: 20 }, + accessibleWorkflowIds: [firstWorkflow.id], + projectId: firstProject.id, + }; + + const output = await executionService.findRangeWithCount(query); + + expect(output).toEqual({ + count: 2, + estimated: false, + results: expect.arrayContaining([ + expect.objectContaining({ workflowId: firstWorkflow.id }), + expect.objectContaining({ workflowId: firstWorkflow.id }), + // execution for workflow in second project was filtered out + ]), + }); + }); + test('should exclude executions by inaccessible `workflowId`', async () => { const accessibleWorkflow = await createWorkflow(); const inaccessibleWorkflow = await createWorkflow();