feat(core): Allow filtering workflows by project and transferring workflows in Public API (#10231)

This commit is contained in:
Iván Ovejero 2024-07-30 17:05:48 +02:00 committed by GitHub
parent 452f52c124
commit d719899223
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 131 additions and 2 deletions

View file

@ -72,6 +72,7 @@ export declare namespace WorkflowRequest {
workflowId?: number;
active: boolean;
name?: string;
projectId?: string;
}
>;
@ -82,6 +83,11 @@ export declare namespace WorkflowRequest {
type Activate = Get;
type GetTags = Get;
type UpdateTags = AuthenticatedRequest<{ id: string }, {}, TagEntity[]>;
type Transfer = AuthenticatedRequest<
{ workflowId: string },
{},
{ destinationProjectId: string }
>;
}
export declare namespace UserRequest {

View file

@ -0,0 +1,31 @@
put:
x-eov-operation-id: transferWorkflow
x-eov-operation-handler: v1/handlers/workflows/workflows.handler
tags:
- Workflow
summary: Transfer a workflow to another project.
description: Transfer a workflow to another project.
parameters:
- $ref: '../schemas/parameters/workflowId.yml'
requestBody:
description: Destination project information for the workflow transfer.
content:
application/json:
schema:
type: object
properties:
destinationProjectId:
type: string
description: The ID of the project to transfer the workflow to.
required:
- destinationProjectId
required: true
responses:
'200':
description: Operation successful.
'400':
$ref: '../../../../shared/spec/responses/badRequest.yml'
'401':
$ref: '../../../../shared/spec/responses/unauthorized.yml'
'404':
$ref: '../../../../shared/spec/responses/notFound.yml'

View file

@ -52,6 +52,14 @@ get:
schema:
type: string
example: My Workflow
- name: projectId
in: query
required: false
explode: false
allowReserved: true
schema:
type: string
example: VmwOO9HeTEj20kxM
- $ref: '../../../../shared/spec/parameters/limit.yml'
- $ref: '../../../../shared/spec/parameters/cursor.yml'
responses:

View file

@ -33,6 +33,8 @@ import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { EventService } from '@/eventbus/event.service';
import { z } from 'zod';
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
export = {
createWorkflow: [
@ -67,6 +69,20 @@ export = {
return res.json(createdWorkflow);
},
],
transferWorkflow: [
projectScope('workflow:move', 'workflow'),
async (req: WorkflowRequest.Transfer, res: express.Response) => {
const body = z.object({ destinationProjectId: z.string() }).parse(req.body);
await Container.get(EnterpriseWorkflowService).transferOne(
req.user,
req.params.workflowId,
body.destinationProjectId,
);
res.status(204).send();
},
],
deleteWorkflow: [
projectScope('workflow:delete', 'workflow'),
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
@ -112,7 +128,7 @@ export = {
getWorkflows: [
validCursor,
async (req: WorkflowRequest.GetAll, res: express.Response): Promise<express.Response> => {
const { offset = 0, limit = 100, active, tags, name } = req.query;
const { offset = 0, limit = 100, active, tags, name, projectId } = req.query;
const where: FindOptionsWhere<WorkflowEntity> = {
...(active !== undefined && { active }),
@ -145,6 +161,10 @@ export = {
workflows = workflows.filter((wf) => workflowIds.includes(wf.id));
}
if (projectId) {
workflows = workflows.filter((w) => w.projectId === projectId);
}
if (!workflows.length) {
return res.status(200).json({
data: [],

View file

@ -58,6 +58,8 @@ paths:
$ref: './handlers/workflows/spec/paths/workflows.id.activate.yml'
/workflows/{id}/deactivate:
$ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml'
/workflows/{id}/transfer:
$ref: './handlers/workflows/spec/paths/workflows.id.transfer.yml'
/workflows/{id}/tags:
$ref: './handlers/workflows/spec/paths/workflows.id.tags.yml'
/users:

View file

@ -175,7 +175,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
},
});
return sharedWorkflows.map((sw) => sw.workflow);
return sharedWorkflows.map((sw) => ({ ...sw.workflow, projectId: sw.projectId }));
}
/**

View file

@ -21,6 +21,8 @@ import { createTag } from '../shared/db/tags';
import { mockInstance } from '../../shared/mocking';
import type { SuperAgentTest } from '../shared/types';
import { Telemetry } from '@/telemetry';
import { ProjectService } from '@/services/project.service';
import { createTeamProject } from '@test-integration/db/projects';
mockInstance(Telemetry);
@ -265,6 +267,25 @@ describe('GET /workflows', () => {
}
});
test('should return all user-accessible workflows filtered by `projectId`', async () => {
license.setQuota('quota:maxTeamProjects', 2);
const otherProject = await Container.get(ProjectService).createTeamProject(
'Other project',
member,
);
await Promise.all([
createWorkflow({}, member),
createWorkflow({ name: 'Other workflow' }, otherProject),
]);
const response = await authMemberAgent.get(`/workflows?projectId=${otherProject.id}`);
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(1);
expect(response.body.data[0].name).toBe('Other workflow');
});
test('should return all owned workflows filtered by name', async () => {
const workflowName = 'Workflow 1';
@ -1465,3 +1486,44 @@ describe('PUT /workflows/:id/tags', () => {
}
});
});
describe('PUT /workflows/:id/transfer', () => {
test('should transfer workflow to project', async () => {
/**
* Arrange
*/
const firstProject = await createTeamProject('first-project', member);
const secondProject = await createTeamProject('secon-project', member);
const workflow = await createWorkflow({}, firstProject);
/**
* Act
*/
const response = await authMemberAgent.put(`/workflows/${workflow.id}/transfer`).send({
destinationProjectId: secondProject.id,
});
/**
* Assert
*/
expect(response.statusCode).toBe(204);
});
test('if no destination project, should reject', async () => {
/**
* Arrange
*/
const firstProject = await createTeamProject('first-project', member);
const workflow = await createWorkflow({}, firstProject);
/**
* Act
*/
const response = await authMemberAgent.put(`/workflows/${workflow.id}/transfer`).send({});
/**
* Assert
*/
expect(response.statusCode).toBe(400);
});
});