mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
feat(review-workflow): add update route for stages on workflows
This commit is contained in:
parent
fb4b40bde4
commit
8f622ae7e0
@ -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 };
|
||||
},
|
||||
};
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
},
|
||||
});
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
17
packages/core/admin/ee/server/validation/review-workflows.js
Normal file
17
packages/core/admin/ee/server/validation/review-workflows.js
Normal 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,
|
||||
}),
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user