Merge pull request #15637 from strapi/feature/review-worflow/default-workflow

feat(review-workflows): add default workflow
This commit is contained in:
Nathan Pichon 2023-02-06 10:48:31 +01:00 committed by GitHub
commit bbfdda2b14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 230 additions and 44 deletions

View File

@ -17,7 +17,12 @@ module.exports = async () => {
await actionProvider.registerMany(actions.auditLogs);
}
// TODO: check admin seats
if (features.isEnabled('review-workflows')) {
const { bootstrap: rwBootstrap } = getService('review-workflows');
await rwBootstrap();
}
// TODO: check admin seats
await executeCEBootstrap();
};

View File

@ -0,0 +1,14 @@
[
{
"name": "To do"
},
{
"name": "Ready to review"
},
{
"name": "In progress"
},
{
"name": "Reviewed"
}
]

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,83 @@
'use strict';
const reviewWorkflowsServiceFactory = require('../review-workflows/review-workflows');
const workflowMock = {
id: 1,
};
const stagesMock = [
{
id: 1,
name: 'stage 1',
},
{
id: 2,
name: 'stage 2',
},
{
id: 3,
name: 'stage 3',
},
];
const workflowsServiceMock = {
count: jest.fn(() => 0),
create: jest.fn(() => workflowMock),
};
const stagesServiceMock = {
count: jest.fn(() => 0),
createMany: jest.fn(() => stagesMock),
};
const strapiMock = {
service(serviceName) {
switch (serviceName) {
case 'admin::stages':
return stagesServiceMock;
case 'admin::workflows':
return workflowsServiceMock;
default:
return null;
}
},
};
const reviewWorkflowsService = reviewWorkflowsServiceFactory({ strapi: strapiMock });
describe('Review workflows service', () => {
afterEach(() => {
jest.resetAllMocks();
});
describe('bootstrap', () => {
test('Without stages or workflows in DB', async () => {
await reviewWorkflowsService.bootstrap();
expect(workflowsServiceMock.count).toBeCalled();
expect(stagesServiceMock.count).toBeCalled();
expect(stagesServiceMock.createMany).toBeCalled();
expect(workflowsServiceMock.create).toBeCalled();
});
test('With a workflow in DB', async () => {
workflowsServiceMock.count.mockResolvedValue(1);
await reviewWorkflowsService.bootstrap();
expect(workflowsServiceMock.count).toBeCalled();
expect(stagesServiceMock.count).toBeCalled();
expect(stagesServiceMock.createMany).not.toBeCalled();
expect(workflowsServiceMock.create).not.toBeCalled();
});
test('With stages in DB', async () => {
stagesServiceMock.count.mockResolvedValue(5);
await reviewWorkflowsService.bootstrap();
expect(workflowsServiceMock.count).toBeCalled();
expect(stagesServiceMock.count).toBeCalled();
expect(stagesServiceMock.createMany).not.toBeCalled();
expect(workflowsServiceMock.create).not.toBeCalled();
});
});
});

View File

@ -49,7 +49,7 @@ describe('Review workflows - Stages service', () => {
expect(entityServiceMock.findOne).not.toBeCalled();
expect(entityServiceMock.findMany).toBeCalled();
expect(entityServiceMock.findMany).toBeCalledWith(STAGE_MODEL_UID, {
filter: { workflow: 1 },
filters: { workflow: 1 },
});
});
});
@ -60,7 +60,7 @@ describe('Review workflows - Stages service', () => {
expect(entityServiceMock.findMany).not.toBeCalled();
expect(entityServiceMock.findOne).toBeCalled();
expect(entityServiceMock.findOne).toBeCalledWith(STAGE_MODEL_UID, 1, {
filter: { workflow: 1 },
filters: { workflow: 1 },
});
});
});

View File

@ -5,4 +5,5 @@ module.exports = {
role: require('./role'),
workflows: require('./review-workflows/workflows'),
stages: require('./review-workflows/stages'),
'review-workflows': require('./review-workflows/review-workflows'),
};

View File

@ -0,0 +1,54 @@
'use strict';
const { getService } = require('../../utils');
/**
* Map every stage in the array to be ordered in the relation
* @param {Object[]} stages
* @param {number} stages.id
* @return {Object[]}
*/
function buildStagesConnectArray(stages) {
return stages.map((stage, index) => {
const connect = {
id: stage.id,
position: {},
};
if (index === 0) {
connect.position.start = true;
} else {
connect.position.after = stages[index - 1].id;
}
return connect;
});
}
module.exports = ({ strapi }) => {
const workflowsService = getService('workflows', { strapi });
const stagesService = getService('stages', { strapi });
return {
async bootstrap() {
const wfCount = await workflowsService.count();
const stagesCount = await stagesService.count();
// Check if there is nothing about review-workflow in DB
// If any, the feature has already been initialized with a workflow and stages
if (wfCount === 0 && stagesCount === 0) {
const defaultStages = require('../../constants/default-stages.json');
const defaultWorkflow = require('../../constants/default-workflow.json');
const stages = await stagesService.createMany(defaultStages, { fields: ['id'] });
const workflow = {
...defaultWorkflow,
stages: {
connect: buildStagesConnectArray(stages),
},
};
await workflowsService.create(workflow);
}
},
};
};

View File

@ -5,7 +5,7 @@ const { STAGE_MODEL_UID } = require('../../constants/workflows');
module.exports = ({ strapi }) => ({
find({ workflowId, populate }) {
const params = {
filter: { workflow: workflowId },
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
@ -13,9 +13,24 @@ module.exports = ({ strapi }) => ({
findById(id, { workflowId, populate }) {
const params = {
filter: { workflow: workflowId },
filters: { workflow: workflowId },
populate,
};
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
},
createMany(stagesList, { fields }) {
const params = {
select: fields,
};
return Promise.all(
stagesList.map((stage) =>
strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
)
);
},
count() {
return strapi.entityService.count(STAGE_MODEL_UID);
},
});

View File

@ -10,4 +10,12 @@ module.exports = ({ strapi }) => ({
findById(id, opts) {
return strapi.entityService.findOne(WORKFLOW_MODEL_UID, id, opts);
},
create(workflowData) {
return strapi.entityService.create(WORKFLOW_MODEL_UID, { data: workflowData });
},
count() {
return strapi.entityService.count(WORKFLOW_MODEL_UID);
},
});

View File

@ -16,7 +16,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
let hasRW;
let defaultStage;
let secondStage;
let defaultWorkflow;
let testWorkflow;
beforeAll(async () => {
strapi = await createStrapiInstance();
@ -32,7 +32,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
secondStage = await strapi.query(STAGE_MODEL_UID).create({
data: { name: 'Stage 2' },
});
defaultWorkflow = await strapi.query(WORKFLOW_MODEL_UID).create({
testWorkflow = await strapi.query(WORKFLOW_MODEL_UID).create({
data: {
uid: 'workflow',
stages: [defaultStage.id, secondStage.id],
@ -61,7 +61,8 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
if (hasRW) {
expect(res.status).toBe(200);
expect(Array.isArray(res.body.data)).toBeTruthy();
expect(res.body.data).toHaveLength(1);
// Why 2 workflows ? One added by the test, the other one should be the default workflow added in bootstrap
expect(res.body.data).toHaveLength(2);
} else {
expect(res.status).toBe(404);
expect(Array.isArray(res.body)).toBeFalsy();
@ -70,9 +71,34 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
});
describe('Get one workflow', () => {
test("It shouldn't be available for public", async () => {
const res = await requests.public.get(`/admin/review-workflows/workflows/${testWorkflow.id}`);
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.get(`/admin/review-workflows/workflows/${testWorkflow.id}`);
if (hasRW) {
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Object);
expect(res.body.data).toEqual(testWorkflow);
} else {
expect(res.status).toBe(404);
expect(res.body.data).toBeUndefined();
}
});
});
describe('Get workflow stages', () => {
test("It shouldn't be available for public", async () => {
const res = await requests.public.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}`
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
);
if (hasRW) {
@ -84,40 +110,14 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
});
test('It should be available for every connected users (admin)', async () => {
const res = await requests.admin.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}`
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
);
if (hasRW) {
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Object);
expect(res.body.data).toEqual(defaultWorkflow);
} else {
expect(res.status).toBe(404);
expect(res.body.data).toBeUndefined();
}
});
});
describe('Get workflow stages', () => {
test("It shouldn't be available for public", async () => {
const res = await requests.public.get('/admin/review-workflows/workflows?populate=stages');
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.get('/admin/review-workflows/workflows?populate=stages');
if (hasRW) {
expect(res.status).toBe(200);
expect(Array.isArray(res.body.data)).toBeTruthy();
expect(res.body.data).toHaveLength(1);
expect(res.body.data[0].stages).toHaveLength(2);
expect(res.body.data[0].stages[0]).toEqual(defaultStage);
expect(res.body.data.stages).toBeInstanceOf(Array);
expect(res.body.data.stages).toHaveLength(2);
} else {
expect(res.status).toBe(404);
expect(Array.isArray(res.body)).toBeFalsy();
@ -128,7 +128,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
describe('Get stages', () => {
test("It shouldn't be available for public", async () => {
const res = await requests.public.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages`
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
);
if (hasRW) {
@ -140,7 +140,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
});
test('It should be available for every connected users (admin)', async () => {
const res = await requests.admin.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages`
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
);
if (hasRW) {
@ -157,7 +157,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
describe('Get stage by id', () => {
test("It shouldn't be available for public", async () => {
const res = await requests.public.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages/${secondStage.id}`
`/admin/review-workflows/workflows/${testWorkflow.id}/stages/${secondStage.id}`
);
if (hasRW) {
@ -169,7 +169,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
});
test('It should be available for every connected users (admin)', async () => {
const res = await requests.admin.get(
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages/${secondStage.id}`
`/admin/review-workflows/workflows/${testWorkflow.id}/stages/${secondStage.id}`
);
if (hasRW) {

View File

@ -1,6 +1,6 @@
'use strict';
const getService = (name) => {
const getService = (name, { strapi } = { strapi: global.strapi }) => {
return strapi.service(`admin::${name}`);
};
module.exports = {

View File

@ -1 +1,6 @@
export type MapAsync<T = any, R = any> = lodash.CurriedFunction3<T[], (element: T, index: number) => R | Promise<R>, { concurrency?: number }, Promise<R[]>>;
export type MapAsync<T = any, R = any> = lodash.CurriedFunction3<
T[],
(element: T, index: number) => R | Promise<R>,
{ concurrency?: number },
Promise<R[]>
>;