feat(review-workflow): add update route for stages on workflows

This commit is contained in:
nathan-pichon 2023-02-03 16:47:34 +01:00
parent fb4b40bde4
commit 8f622ae7e0
No known key found for this signature in database
7 changed files with 244 additions and 30 deletions

View File

@ -1,6 +1,7 @@
'use strict';
const { getService } = require('../../../utils');
const { validateUpdateStages } = require('../../../validation/review-workflows');
module.exports = {
/**
@ -39,4 +40,16 @@ module.exports = {
data,
};
},
async replace(ctx) {
const { workflow_id: workflowId } = ctx.params;
const stagesService = getService('stages');
const { body: stages } = ctx.request;
const stagesValidated = await validateUpdateStages(stages);
const data = await stagesService.replaceWorkflowStages(workflowId, stagesValidated);
ctx.body = { data };
},
};

View File

@ -177,6 +177,15 @@ module.exports = [
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'PUT',
path: '/review-workflows/workflows/:workflow_id/stages',
handler: 'stages.replace',
config: {
middlewares: [enableFeatureMiddleware('review-workflows')],
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'GET',
path: '/review-workflows/workflows/:workflow_id/stages/:id',

View File

@ -17,6 +17,8 @@ jest.mock('@strapi/strapi/lib/utils/ee', () => {
return eeModule;
});
const { cloneDeep } = require('lodash/fp');
const stageFactory = require('../review-workflows/stages');
const { STAGE_MODEL_UID } = require('../../constants/workflows');
@ -26,20 +28,50 @@ const stageMock = {
workflow: 1,
};
const workflowMock = {
id: 1,
stages: [
stageMock,
{
id: 2,
name: 'in progress',
workflow: 1,
},
{
id: 3,
name: 'done',
workflow: 1,
},
],
};
const entityServiceMock = {
findOne: jest.fn(() => stageMock),
findMany: jest.fn(() => [stageMock]),
create: jest.fn((uid, { data }) => data),
update: jest.fn((uid, id, { data }) => data),
delete: jest.fn(() => true),
};
const servicesMock = {
'admin::workflows': {
findById: jest.fn(() => workflowMock),
update: jest.fn((id, data) => data),
},
};
const strapiMock = {
entityService: entityServiceMock,
service: jest.fn((serviceName) => {
return servicesMock[serviceName];
}),
};
const stagesService = stageFactory({ strapi: strapiMock });
describe('Review workflows - Stages service', () => {
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
describe('find', () => {
@ -64,4 +96,50 @@ describe('Review workflows - Stages service', () => {
});
});
});
describe('replaceWorkflowStages', () => {
test('Should create a new stage and assign it to workflow', async () => {
await stagesService.replaceWorkflowStages(1, [
...workflowMock.stages,
{
name: 'to publish',
},
]);
expect(servicesMock['admin::workflows'].findById).toBeCalled();
expect(entityServiceMock.create).toBeCalled();
expect(entityServiceMock.update).not.toBeCalled();
expect(entityServiceMock.delete).not.toBeCalled();
});
test('Should update a stage contained in the workflow', async () => {
const updateStages = cloneDeep(workflowMock.stages);
updateStages[0].name = `${updateStages[0].name}(new value)`;
await stagesService.replaceWorkflowStages(1, updateStages);
expect(servicesMock['admin::workflows'].findById).toBeCalled();
expect(entityServiceMock.create).not.toBeCalled();
expect(entityServiceMock.update).toBeCalled();
expect(entityServiceMock.delete).not.toBeCalled();
});
test('Should delete a stage contained in the workflow', async () => {
await stagesService.replaceWorkflowStages(1, [workflowMock.stages[0]]);
expect(servicesMock['admin::workflows'].findById).toBeCalled();
expect(entityServiceMock.create).not.toBeCalled();
expect(entityServiceMock.update).not.toBeCalled();
expect(entityServiceMock.delete).toBeCalled();
});
test('New stage + updated + deleted', async () => {
await stagesService.replaceWorkflowStages(1, [
workflowMock.stages[0],
{ id: workflowMock.stages[1].id, name: 'new_name' },
{ name: 'new stage' },
]);
expect(servicesMock['admin::workflows'].findById).toBeCalled();
expect(entityServiceMock.create).toBeCalled();
expect(entityServiceMock.update).toBeCalled();
expect(entityServiceMock.delete).toBeCalled();
});
});
});

View File

@ -1,36 +1,88 @@
'use strict';
const { mapAsync } = require('@strapi/utils');
const { STAGE_MODEL_UID } = require('../../constants/workflows');
const { getService } = require('../../utils');
module.exports = ({ strapi }) => ({
find({ workflowId, populate }) {
const params = {
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
},
module.exports = ({ strapi }) => {
const workflowsService = getService('workflows', { strapi });
findById(id, { workflowId, populate }) {
const params = {
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
},
return {
find({ workflowId, populate }) {
const params = {
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
},
createMany(stagesList, { fields }) {
const params = {
select: fields,
};
return Promise.all(
stagesList.map((stage) =>
strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
)
);
},
findById(id, { workflowId, populate }) {
const params = {
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
},
count() {
return strapi.entityService.count(STAGE_MODEL_UID);
},
});
createMany(stagesList, { fields }) {
const params = {
select: fields,
};
return Promise.all(
stagesList.map((stage) =>
strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
)
);
},
update(stageId, stageData) {
return strapi.entityService.update(STAGE_MODEL_UID, stageId, { data: stageData });
},
delete(stageId) {
return strapi.entityService.delete(STAGE_MODEL_UID, stageId);
},
count() {
return strapi.entityService.count(STAGE_MODEL_UID);
},
async replaceWorkflowStages(workflowId, stages) {
const workflow = await workflowsService.findById(workflowId, { populate: ['stages'] });
const { created, updated, deleted } = getDiffBetweenStages(workflow.stages, stages);
const newStages = await this.createMany(created, { fields: ['id'] });
const assignedStagesToWorkflow = stages.map((stage) => stage.id ?? newStages.shift().id);
await mapAsync(updated, (stage) => this.update(stage.id, stage));
await mapAsync(deleted, (stage) => this.delete(stage.id));
return workflowsService.update(workflowId, {
stages: assignedStagesToWorkflow,
});
},
};
};
function getDiffBetweenStages(sourceStages, comparisonStages) {
const result = comparisonStages.reduce(
(acc, stageToCompare) => {
const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
if (!srcStage) {
acc.created.push(stageToCompare);
} else if (srcStage.name !== stageToCompare.name) {
acc.updated.push(stageToCompare);
}
return acc;
},
{ created: [], updated: [] }
);
result.deleted = sourceStages.filter(
(srcStage) => !comparisonStages.some((cmpStage) => cmpStage.id === srcStage.id)
);
return result;
}

View File

@ -18,4 +18,8 @@ module.exports = ({ strapi }) => ({
count() {
return strapi.entityService.count(WORKFLOW_MODEL_UID);
},
update(id, workflowData) {
return strapi.entityService.update(WORKFLOW_MODEL_UID, id, { data: workflowData });
},
});

View File

@ -182,4 +182,45 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
}
});
});
describe('Replace stages of a workflow', () => {
let stagesUpdateData;
beforeEach(() => {
stagesUpdateData = [
defaultStage,
{ id: secondStage.id, name: 'new_name' },
{ name: 'new stage' },
];
});
test("It shouldn't be available for public", async () => {
const res = await requests.public.put(
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`,
stagesUpdateData
);
if (hasRW) {
expect(res.status).toBe(401);
} else {
expect(res.status).toBe(404);
expect(res.body.data).toBeUndefined();
}
});
test('It should be available for every connected users (admin)', async () => {
const res = await requests.admin.put(
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`,
{ body: stagesUpdateData }
);
if (hasRW) {
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Object);
expect(res.body.data.id).toEqual(testWorkflow.id);
} else {
expect(res.status).toBe(404);
expect(res.body.data).toBeUndefined();
}
});
});
});

View File

@ -0,0 +1,17 @@
'use strict';
const { yup, validateYupSchema } = require('@strapi/utils');
const stageObject = yup.object().shape({
id: yup.number().integer().min(1),
name: yup.string().max(255).required(),
});
const validateUpdateStagesSchema = yup.array().of(stageObject).required();
module.exports = {
validateUpdateStages: validateYupSchema(validateUpdateStagesSchema, {
strict: false,
stripUnknown: true,
}),
};