mirror of
https://github.com/strapi/strapi.git
synced 2025-12-12 23:44:08 +00:00
Merge pull request #15637 from strapi/feature/review-worflow/default-workflow
feat(review-workflows): add default workflow
This commit is contained in:
commit
bbfdda2b14
7
packages/core/admin/ee/server/bootstrap.js
vendored
7
packages/core/admin/ee/server/bootstrap.js
vendored
@ -17,7 +17,12 @@ module.exports = async () => {
|
|||||||
await actionProvider.registerMany(actions.auditLogs);
|
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();
|
await executeCEBootstrap();
|
||||||
};
|
};
|
||||||
|
|||||||
14
packages/core/admin/ee/server/constants/default-stages.json
Normal file
14
packages/core/admin/ee/server/constants/default-stages.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "To do"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ready to review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "In progress"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reviewed"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -49,7 +49,7 @@ describe('Review workflows - Stages service', () => {
|
|||||||
expect(entityServiceMock.findOne).not.toBeCalled();
|
expect(entityServiceMock.findOne).not.toBeCalled();
|
||||||
expect(entityServiceMock.findMany).toBeCalled();
|
expect(entityServiceMock.findMany).toBeCalled();
|
||||||
expect(entityServiceMock.findMany).toBeCalledWith(STAGE_MODEL_UID, {
|
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.findMany).not.toBeCalled();
|
||||||
expect(entityServiceMock.findOne).toBeCalled();
|
expect(entityServiceMock.findOne).toBeCalled();
|
||||||
expect(entityServiceMock.findOne).toBeCalledWith(STAGE_MODEL_UID, 1, {
|
expect(entityServiceMock.findOne).toBeCalledWith(STAGE_MODEL_UID, 1, {
|
||||||
filter: { workflow: 1 },
|
filters: { workflow: 1 },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,4 +5,5 @@ module.exports = {
|
|||||||
role: require('./role'),
|
role: require('./role'),
|
||||||
workflows: require('./review-workflows/workflows'),
|
workflows: require('./review-workflows/workflows'),
|
||||||
stages: require('./review-workflows/stages'),
|
stages: require('./review-workflows/stages'),
|
||||||
|
'review-workflows': require('./review-workflows/review-workflows'),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -5,7 +5,7 @@ const { STAGE_MODEL_UID } = require('../../constants/workflows');
|
|||||||
module.exports = ({ strapi }) => ({
|
module.exports = ({ strapi }) => ({
|
||||||
find({ workflowId, populate }) {
|
find({ workflowId, populate }) {
|
||||||
const params = {
|
const params = {
|
||||||
filter: { workflow: workflowId },
|
filters: { workflow: workflowId },
|
||||||
populate,
|
populate,
|
||||||
};
|
};
|
||||||
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
|
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
|
||||||
@ -13,9 +13,24 @@ module.exports = ({ strapi }) => ({
|
|||||||
|
|
||||||
findById(id, { workflowId, populate }) {
|
findById(id, { workflowId, populate }) {
|
||||||
const params = {
|
const params = {
|
||||||
filter: { workflow: workflowId },
|
filters: { workflow: workflowId },
|
||||||
populate,
|
populate,
|
||||||
};
|
};
|
||||||
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
|
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);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,4 +10,12 @@ module.exports = ({ strapi }) => ({
|
|||||||
findById(id, opts) {
|
findById(id, opts) {
|
||||||
return strapi.entityService.findOne(WORKFLOW_MODEL_UID, 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);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -16,7 +16,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
let hasRW;
|
let hasRW;
|
||||||
let defaultStage;
|
let defaultStage;
|
||||||
let secondStage;
|
let secondStage;
|
||||||
let defaultWorkflow;
|
let testWorkflow;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
strapi = await createStrapiInstance();
|
strapi = await createStrapiInstance();
|
||||||
@ -32,7 +32,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
secondStage = await strapi.query(STAGE_MODEL_UID).create({
|
secondStage = await strapi.query(STAGE_MODEL_UID).create({
|
||||||
data: { name: 'Stage 2' },
|
data: { name: 'Stage 2' },
|
||||||
});
|
});
|
||||||
defaultWorkflow = await strapi.query(WORKFLOW_MODEL_UID).create({
|
testWorkflow = await strapi.query(WORKFLOW_MODEL_UID).create({
|
||||||
data: {
|
data: {
|
||||||
uid: 'workflow',
|
uid: 'workflow',
|
||||||
stages: [defaultStage.id, secondStage.id],
|
stages: [defaultStage.id, secondStage.id],
|
||||||
@ -61,7 +61,8 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
if (hasRW) {
|
if (hasRW) {
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(Array.isArray(res.body.data)).toBeTruthy();
|
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 {
|
} else {
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
expect(Array.isArray(res.body)).toBeFalsy();
|
expect(Array.isArray(res.body)).toBeFalsy();
|
||||||
@ -70,9 +71,34 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Get one workflow', () => {
|
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 () => {
|
test("It shouldn't be available for public", async () => {
|
||||||
const res = await requests.public.get(
|
const res = await requests.public.get(
|
||||||
`/admin/review-workflows/workflows/${defaultWorkflow.id}`
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasRW) {
|
if (hasRW) {
|
||||||
@ -84,40 +110,14 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
});
|
});
|
||||||
test('It should be available for every connected users (admin)', async () => {
|
test('It should be available for every connected users (admin)', async () => {
|
||||||
const res = await requests.admin.get(
|
const res = await requests.admin.get(
|
||||||
`/admin/review-workflows/workflows/${defaultWorkflow.id}`
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasRW) {
|
if (hasRW) {
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body.data).toBeInstanceOf(Object);
|
expect(res.body.data).toBeInstanceOf(Object);
|
||||||
expect(res.body.data).toEqual(defaultWorkflow);
|
expect(res.body.data.stages).toBeInstanceOf(Array);
|
||||||
} else {
|
expect(res.body.data.stages).toHaveLength(2);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
expect(Array.isArray(res.body)).toBeFalsy();
|
expect(Array.isArray(res.body)).toBeFalsy();
|
||||||
@ -128,7 +128,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
describe('Get stages', () => {
|
describe('Get stages', () => {
|
||||||
test("It shouldn't be available for public", async () => {
|
test("It shouldn't be available for public", async () => {
|
||||||
const res = await requests.public.get(
|
const res = await requests.public.get(
|
||||||
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages`
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasRW) {
|
if (hasRW) {
|
||||||
@ -140,7 +140,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
});
|
});
|
||||||
test('It should be available for every connected users (admin)', async () => {
|
test('It should be available for every connected users (admin)', async () => {
|
||||||
const res = await requests.admin.get(
|
const res = await requests.admin.get(
|
||||||
`/admin/review-workflows/workflows/${defaultWorkflow.id}/stages`
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasRW) {
|
if (hasRW) {
|
||||||
@ -157,7 +157,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
describe('Get stage by id', () => {
|
describe('Get stage by id', () => {
|
||||||
test("It shouldn't be available for public", async () => {
|
test("It shouldn't be available for public", async () => {
|
||||||
const res = await requests.public.get(
|
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) {
|
if (hasRW) {
|
||||||
@ -169,7 +169,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|||||||
});
|
});
|
||||||
test('It should be available for every connected users (admin)', async () => {
|
test('It should be available for every connected users (admin)', async () => {
|
||||||
const res = await requests.admin.get(
|
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) {
|
if (hasRW) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const getService = (name) => {
|
const getService = (name, { strapi } = { strapi: global.strapi }) => {
|
||||||
return strapi.service(`admin::${name}`);
|
return strapi.service(`admin::${name}`);
|
||||||
};
|
};
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
7
packages/core/utils/lib/async.d.ts
vendored
7
packages/core/utils/lib/async.d.ts
vendored
@ -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[]>
|
||||||
|
>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user