@strapi/admin 4.13.6 → 4.14.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/content-manager/components/Wysiwyg/EditorLayout.js +1 -1
- package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/ConfirmBulkActionDialog/index.js +3 -0
- package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/index.js +2 -1
- package/admin/src/hooks/useAdminRoles/index.js +17 -7
- package/admin/src/hooks/useAdminUsers/useAdminUsers.js +16 -7
- package/admin/src/hooks/useContentTypes/useContentTypes.js +18 -7
- package/admin/src/index.js +1 -7
- package/build/1227.4f48119b.chunk.js +1 -0
- package/build/{3483.19381b40.chunk.js → 3483.f6b2439f.chunk.js} +1 -1
- package/build/4174.924ebd4c.chunk.js +1 -0
- package/build/6266.c652bdb1.chunk.js +146 -0
- package/build/7897.cf22d5fe.chunk.js +6 -0
- package/build/{Admin-authenticatedApp.69c7ea72.chunk.js → Admin-authenticatedApp.a687d9c6.chunk.js} +1 -1
- package/build/{admin-app.1fde8f7a.chunk.js → admin-app.4654dc77.chunk.js} +11 -11
- package/build/admin-edit-roles-page.6597d934.chunk.js +267 -0
- package/build/admin-edit-users.3014605e.chunk.js +10 -0
- package/build/admin-roles-list.ab6fcfb7.chunk.js +22 -0
- package/build/admin-users.81bf5f4d.chunk.js +11 -0
- package/build/audit-logs-settings-page.4eb6cdf8.chunk.js +1 -0
- package/build/{content-manager.f9abb63f.chunk.js → content-manager.9187db78.chunk.js} +37 -37
- package/build/index.html +1 -1
- package/build/{main.9ed36326.js → main.da000219.js} +17 -17
- package/build/review-workflows-settings-create-view.5d8806b2.chunk.js +1 -0
- package/build/review-workflows-settings-edit-view.634903ed.chunk.js +1 -0
- package/build/review-workflows-settings-list-view.d138c3b5.chunk.js +56 -0
- package/build/{runtime~main.562cc892.js → runtime~main.9589b498.js} +2 -2
- package/build/sso-settings-page.caa35f7b.chunk.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +65 -53
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +50 -5
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +257 -21
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +8 -23
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +17 -7
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowsStages.js +36 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +68 -19
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +105 -35
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +68 -27
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/selectors.js +45 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/validateWorkflow.js +20 -0
- package/ee/server/config/admin-actions.js +6 -0
- package/ee/server/constants/workflows.js +13 -0
- package/ee/server/content-types/workflow-stage/index.js +6 -0
- package/ee/server/controllers/workflows/index.js +41 -16
- package/ee/server/controllers/workflows/stages/index.js +93 -6
- package/ee/server/migrations/review-workflows-stages-roles.js +68 -0
- package/ee/server/migrations/review-workflows-workflow-name.js +7 -0
- package/ee/server/register.js +2 -0
- package/ee/server/routes/review-workflows.js +10 -9
- package/ee/server/services/index.js +1 -0
- package/ee/server/services/review-workflows/stage-permissions.js +60 -0
- package/ee/server/services/review-workflows/stages.js +91 -12
- package/ee/server/services/review-workflows/workflows/index.js +20 -7
- package/ee/server/validation/review-workflows.js +11 -0
- package/package.json +8 -8
- package/server/content-types/Permission.js +6 -0
- package/server/domain/permission/index.js +11 -2
- package/server/services/role.js +12 -4
- package/server/validation/action-provider.js +1 -1
- package/server/validation/common-validators.js +92 -100
- package/server/validation/permission.js +0 -3
- package/build/1227.c72e74e9.chunk.js +0 -1
- package/build/2237.a7992513.chunk.js +0 -114
- package/build/4174.6efb0dc6.chunk.js +0 -1
- package/build/4724.a0ce68f3.chunk.js +0 -6
- package/build/admin-edit-roles-page.f76cb0aa.chunk.js +0 -267
- package/build/admin-edit-users.186c9d90.chunk.js +0 -10
- package/build/admin-roles-list.3a4edfa2.chunk.js +0 -22
- package/build/admin-users.e276bd9a.chunk.js +0 -11
- package/build/audit-logs-settings-page.0d9bc0f6.chunk.js +0 -1
- package/build/review-workflows-settings-create-view.b96b7be1.chunk.js +0 -1
- package/build/review-workflows-settings-edit-view.21a682a0.chunk.js +0 -1
- package/build/review-workflows-settings-list-view.14212324.chunk.js +0 -56
- package/build/sso-settings-page.d93bd81f.chunk.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { update, map, property } = require('lodash/fp');
|
|
3
4
|
const { mapAsync } = require('@strapi/utils');
|
|
4
5
|
const { getService } = require('../../utils');
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ const {
|
|
|
7
8
|
validateWorkflowCreate,
|
|
8
9
|
validateWorkflowUpdate,
|
|
9
10
|
} = require('../../validation/review-workflows');
|
|
10
|
-
const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
|
|
11
|
+
const { WORKFLOW_MODEL_UID, WORKFLOW_POPULATE } = require('../../constants/workflows');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
*
|
|
@@ -22,6 +23,21 @@ function getWorkflowsPermissionChecker({ strapi }, userAbility) {
|
|
|
22
23
|
.create({ userAbility, model: WORKFLOW_MODEL_UID });
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Transforms workflow to an admin UI format.
|
|
28
|
+
* Some attributes (like permissions) are presented in a different format in the admin UI.
|
|
29
|
+
* @param {Workflow} workflow
|
|
30
|
+
*/
|
|
31
|
+
function formatWorkflowToAdmin(workflow) {
|
|
32
|
+
if (!workflow) return;
|
|
33
|
+
if (!workflow.stages) return workflow;
|
|
34
|
+
|
|
35
|
+
// Transform permissions roles to be the id string instead of an object
|
|
36
|
+
const transformPermissions = map(update('role', property('id')));
|
|
37
|
+
const transformStages = map(update('permissions', transformPermissions));
|
|
38
|
+
return update('stages', transformStages, workflow);
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
module.exports = {
|
|
26
42
|
/**
|
|
27
43
|
* Create a new workflow
|
|
@@ -38,10 +54,12 @@ module.exports = {
|
|
|
38
54
|
const workflowBody = await validateWorkflowCreate(body.data);
|
|
39
55
|
|
|
40
56
|
const workflowService = getService('workflows');
|
|
41
|
-
const createdWorkflow = await workflowService
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
const createdWorkflow = await workflowService
|
|
58
|
+
.create({
|
|
59
|
+
data: await sanitizeCreateInput(workflowBody),
|
|
60
|
+
populate,
|
|
61
|
+
})
|
|
62
|
+
.then(formatWorkflowToAdmin);
|
|
45
63
|
|
|
46
64
|
ctx.body = {
|
|
47
65
|
data: await sanitizeOutput(createdWorkflow),
|
|
@@ -61,22 +79,27 @@ module.exports = {
|
|
|
61
79
|
ctx.state.userAbility
|
|
62
80
|
);
|
|
63
81
|
const { populate } = await sanitizedQuery.update(query);
|
|
64
|
-
|
|
65
82
|
const workflowBody = await validateWorkflowUpdate(body.data);
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
// Find if workflow exists
|
|
85
|
+
const workflow = await workflowService.findById(id, { populate: WORKFLOW_POPULATE });
|
|
68
86
|
if (!workflow) {
|
|
69
87
|
return ctx.notFound();
|
|
70
88
|
}
|
|
71
|
-
const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
|
|
72
89
|
|
|
90
|
+
// Sanitize input data
|
|
91
|
+
const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
|
|
73
92
|
const dataToUpdate = await getPermittedFieldToUpdate(workflowBody);
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
// Update workflow
|
|
95
|
+
const updatedWorkflow = await workflowService
|
|
96
|
+
.update(workflow, {
|
|
97
|
+
data: dataToUpdate,
|
|
98
|
+
populate,
|
|
99
|
+
})
|
|
100
|
+
.then(formatWorkflowToAdmin);
|
|
79
101
|
|
|
102
|
+
// Send sanitized response
|
|
80
103
|
ctx.body = {
|
|
81
104
|
data: await sanitizeOutput(updatedWorkflow),
|
|
82
105
|
};
|
|
@@ -96,12 +119,14 @@ module.exports = {
|
|
|
96
119
|
);
|
|
97
120
|
const { populate } = await sanitizedQuery.delete(query);
|
|
98
121
|
|
|
99
|
-
const workflow = await workflowService.findById(id, { populate:
|
|
122
|
+
const workflow = await workflowService.findById(id, { populate: WORKFLOW_POPULATE });
|
|
100
123
|
if (!workflow) {
|
|
101
124
|
return ctx.notFound("Workflow doesn't exist");
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
const deletedWorkflow = await workflowService
|
|
127
|
+
const deletedWorkflow = await workflowService
|
|
128
|
+
.delete(workflow, { populate })
|
|
129
|
+
.then(formatWorkflowToAdmin);
|
|
105
130
|
|
|
106
131
|
ctx.body = {
|
|
107
132
|
data: await sanitizeOutput(deletedWorkflow),
|
|
@@ -122,7 +147,7 @@ module.exports = {
|
|
|
122
147
|
const { populate, filters, sort } = await sanitizedQuery.read(query);
|
|
123
148
|
|
|
124
149
|
const [workflows, workflowCount] = await Promise.all([
|
|
125
|
-
workflowService.find({ populate, filters, sort }),
|
|
150
|
+
workflowService.find({ populate, filters, sort }).then(map(formatWorkflowToAdmin)),
|
|
126
151
|
workflowService.count(),
|
|
127
152
|
]);
|
|
128
153
|
|
|
@@ -151,7 +176,7 @@ module.exports = {
|
|
|
151
176
|
const workflowService = getService('workflows');
|
|
152
177
|
|
|
153
178
|
const [workflow, workflowCount] = await Promise.all([
|
|
154
|
-
workflowService.findById(id, { populate }),
|
|
179
|
+
workflowService.findById(id, { populate }).then(formatWorkflowToAdmin),
|
|
155
180
|
workflowService.count(),
|
|
156
181
|
]);
|
|
157
182
|
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
const { mapAsync } = require('@strapi/utils');
|
|
4
4
|
const { getService } = require('../../../utils');
|
|
5
5
|
const { validateUpdateStageOnEntity } = require('../../../validation/review-workflows');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
STAGE_MODEL_UID,
|
|
8
|
+
ENTITY_STAGE_ATTRIBUTE,
|
|
9
|
+
STAGE_TRANSITION_UID,
|
|
10
|
+
} = require('../../../constants/workflows');
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
*
|
|
@@ -75,18 +79,36 @@ module.exports = {
|
|
|
75
79
|
*/
|
|
76
80
|
async updateEntity(ctx) {
|
|
77
81
|
const stagesService = getService('stages');
|
|
82
|
+
const stagePermissions = getService('stage-permissions');
|
|
78
83
|
const workflowService = getService('workflows');
|
|
79
84
|
|
|
80
|
-
const { model_uid: modelUID, id
|
|
85
|
+
const { model_uid: modelUID, id } = ctx.params;
|
|
81
86
|
const { body } = ctx.request;
|
|
82
87
|
|
|
83
|
-
const entityId = Number(entityIdString);
|
|
84
|
-
|
|
85
88
|
const { sanitizeOutput } = strapi
|
|
86
89
|
.plugin('content-manager')
|
|
87
90
|
.service('permission-checker')
|
|
88
91
|
.create({ userAbility: ctx.state.userAbility, model: modelUID });
|
|
89
92
|
|
|
93
|
+
// Load entity
|
|
94
|
+
const entity = await strapi.entityService.findOne(modelUID, Number(id), {
|
|
95
|
+
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!entity) {
|
|
99
|
+
ctx.throw(404, 'Entity not found');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate if entity stage can be updated
|
|
103
|
+
const canTransition = stagePermissions.can(
|
|
104
|
+
STAGE_TRANSITION_UID,
|
|
105
|
+
entity[ENTITY_STAGE_ATTRIBUTE]?.id
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!canTransition) {
|
|
109
|
+
ctx.throw(403, 'Forbidden stage transition');
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
const { id: stageId } = await validateUpdateStageOnEntity(
|
|
91
113
|
{ id: Number(body?.data?.id) },
|
|
92
114
|
'You should pass an id to the body of the put request.'
|
|
@@ -95,8 +117,73 @@ module.exports = {
|
|
|
95
117
|
const workflow = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
|
|
96
118
|
workflowService.assertStageBelongsToWorkflow(stageId, workflow);
|
|
97
119
|
|
|
98
|
-
const
|
|
120
|
+
const updatedEntity = await stagesService.updateEntity({ id: entity.id, modelUID }, stageId);
|
|
99
121
|
|
|
100
|
-
ctx.body = { data: await sanitizeOutput(
|
|
122
|
+
ctx.body = { data: await sanitizeOutput(updatedEntity) };
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* List all the stages that are available for a user to transition an entity to.
|
|
127
|
+
* If the user has permission to change the current stage of the entity every other stage in the workflow is returned
|
|
128
|
+
* @async
|
|
129
|
+
* @param {*} ctx
|
|
130
|
+
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
131
|
+
* @param {string} ctx.params.id - The ID of the entity.
|
|
132
|
+
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
133
|
+
*/
|
|
134
|
+
async listAvailableStages(ctx) {
|
|
135
|
+
const stagePermissions = getService('stage-permissions');
|
|
136
|
+
const workflowService = getService('workflows');
|
|
137
|
+
|
|
138
|
+
const { model_uid: modelUID, id } = ctx.params;
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
strapi
|
|
142
|
+
.plugin('content-manager')
|
|
143
|
+
.service('permission-checker')
|
|
144
|
+
.create({ userAbility: ctx.state.userAbility, model: modelUID })
|
|
145
|
+
.cannot.read()
|
|
146
|
+
) {
|
|
147
|
+
return ctx.forbidden();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Load entity
|
|
151
|
+
const entity = await strapi.entityService.findOne(modelUID, Number(id), {
|
|
152
|
+
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!entity) {
|
|
156
|
+
ctx.throw(404, 'Entity not found');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entityStageId = entity[ENTITY_STAGE_ATTRIBUTE]?.id;
|
|
160
|
+
const canTransition = stagePermissions.can(STAGE_TRANSITION_UID, entityStageId);
|
|
161
|
+
|
|
162
|
+
const [workflowCount, { stages: workflowStages }] = await Promise.all([
|
|
163
|
+
workflowService.count(),
|
|
164
|
+
workflowService.getAssignedWorkflow(modelUID, {
|
|
165
|
+
populate: 'stages',
|
|
166
|
+
}),
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const meta = {
|
|
170
|
+
stageCount: workflowStages.length,
|
|
171
|
+
workflowCount,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (!canTransition) {
|
|
175
|
+
ctx.body = {
|
|
176
|
+
data: [],
|
|
177
|
+
meta,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const data = workflowStages.filter((stage) => stage.id !== entityStageId);
|
|
184
|
+
ctx.body = {
|
|
185
|
+
data,
|
|
186
|
+
meta,
|
|
187
|
+
};
|
|
101
188
|
},
|
|
102
189
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { STAGE_TRANSITION_UID, STAGE_MODEL_UID } = require('../constants/workflows');
|
|
4
|
+
const { getService } = require('../utils');
|
|
5
|
+
|
|
6
|
+
async function migrateReviewWorkflowStagesRoles({ oldContentTypes, contentTypes }) {
|
|
7
|
+
const stageUID = 'admin::workflow-stage';
|
|
8
|
+
const hadRolePermissions = !!oldContentTypes?.[stageUID]?.attributes?.permissions;
|
|
9
|
+
const hasRolePermissions = !!contentTypes?.[stageUID]?.attributes?.permissions;
|
|
10
|
+
|
|
11
|
+
// If the stage content type did not have permissions in the previous version
|
|
12
|
+
// then we set the permissions of every stage to be every current role in the app.
|
|
13
|
+
// This ensures consistent behaviour when upgrading to a strapi version with review workflows RBAC.
|
|
14
|
+
if (!hadRolePermissions && hasRolePermissions) {
|
|
15
|
+
const roleUID = 'admin::role';
|
|
16
|
+
strapi.log.info(
|
|
17
|
+
`Migrating all existing review workflow stages to have RBAC permissions for all ${roleUID}.`
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const stagePermissionsService = getService('stage-permissions');
|
|
21
|
+
|
|
22
|
+
const stages = await strapi.query(stageUID).findMany();
|
|
23
|
+
const roles = await strapi.query(roleUID).findMany();
|
|
24
|
+
|
|
25
|
+
// Collect the permissions to add and group them by stage id.
|
|
26
|
+
const groupedPermissions = {};
|
|
27
|
+
roles
|
|
28
|
+
.map((role) => role.id)
|
|
29
|
+
.forEach((roleId) => {
|
|
30
|
+
stages
|
|
31
|
+
.map((stage) => stage.id)
|
|
32
|
+
.forEach((stageId) => {
|
|
33
|
+
if (!groupedPermissions[stageId]) {
|
|
34
|
+
groupedPermissions[stageId] = [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
groupedPermissions[stageId].push({
|
|
38
|
+
roleId,
|
|
39
|
+
fromStage: stageId,
|
|
40
|
+
action: STAGE_TRANSITION_UID,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
for (const [stageId, permissions] of Object.entries(groupedPermissions)) {
|
|
46
|
+
const numericalStageId = Number(stageId);
|
|
47
|
+
|
|
48
|
+
if (Number.isNaN(numericalStageId)) {
|
|
49
|
+
strapi.log.warn(
|
|
50
|
+
`Unable to apply ${roleUID} migration for ${stageUID} with id ${stageId}. The stage does not have a numerical id.`
|
|
51
|
+
);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Register the permissions for this stage
|
|
56
|
+
const stagePermissions = await stagePermissionsService.registerMany(permissions);
|
|
57
|
+
|
|
58
|
+
// Update the stage with its new permissions
|
|
59
|
+
await strapi.entityService.update(STAGE_MODEL_UID, numericalStageId, {
|
|
60
|
+
data: {
|
|
61
|
+
permissions: stagePermissions.flat().map((permission) => permission.id),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = migrateReviewWorkflowStagesRoles;
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
|
|
4
4
|
const defaultWorkflow = require('../constants/default-workflow.json');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Multiple workflows introduced the ability to name a workflow.
|
|
8
|
+
* This migration adds the default workflow name if the name attribute was added.
|
|
9
|
+
*/
|
|
6
10
|
async function migrateReviewWorkflowName({ oldContentTypes, contentTypes }) {
|
|
7
11
|
// Look for RW name attribute
|
|
8
12
|
const hadName = !!oldContentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.name;
|
|
@@ -11,6 +15,9 @@ async function migrateReviewWorkflowName({ oldContentTypes, contentTypes }) {
|
|
|
11
15
|
// Add the default workflow name if name attribute was added
|
|
12
16
|
if (!hadName && hasName) {
|
|
13
17
|
await strapi.query(WORKFLOW_MODEL_UID).updateMany({
|
|
18
|
+
where: {
|
|
19
|
+
name: { $null: true },
|
|
20
|
+
},
|
|
14
21
|
data: {
|
|
15
22
|
name: defaultWorkflow.name,
|
|
16
23
|
},
|
package/ee/server/register.js
CHANGED
|
@@ -4,6 +4,7 @@ const { features } = require('@strapi/strapi/lib/utils/ee');
|
|
|
4
4
|
const executeCERegister = require('../../server/register');
|
|
5
5
|
const migrateAuditLogsTable = require('./migrations/audit-logs-table');
|
|
6
6
|
const migrateReviewWorkflowStagesColor = require('./migrations/review-workflows-stages-color');
|
|
7
|
+
const migrateReviewWorkflowStagesRoles = require('./migrations/review-workflows-stages-roles');
|
|
7
8
|
const migrateReviewWorkflowName = require('./migrations/review-workflows-workflow-name');
|
|
8
9
|
const migrateWorkflowsContentTypes = require('./migrations/review-workflows-content-types');
|
|
9
10
|
const migrateStageAttribute = require('./migrations/review-workflows-stage-attribute');
|
|
@@ -26,6 +27,7 @@ module.exports = async ({ strapi }) => {
|
|
|
26
27
|
strapi
|
|
27
28
|
.hook('strapi::content-types.afterSync')
|
|
28
29
|
.register(migrateReviewWorkflowStagesColor)
|
|
30
|
+
.register(migrateReviewWorkflowStagesRoles)
|
|
29
31
|
.register(migrateReviewWorkflowName)
|
|
30
32
|
.register(migrateWorkflowsContentTypes)
|
|
31
33
|
.register(migrateDeletedCTInWorkflows);
|
|
@@ -131,15 +131,16 @@ module.exports = {
|
|
|
131
131
|
handler: 'stages.updateEntity',
|
|
132
132
|
config: {
|
|
133
133
|
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
134
|
-
policies: [
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
],
|
|
134
|
+
policies: ['admin::isAuthenticatedAdmin'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
method: 'GET',
|
|
139
|
+
path: '/content-manager/(collection|single)-types/:model_uid/:id/stages',
|
|
140
|
+
handler: 'stages.listAvailableStages',
|
|
141
|
+
config: {
|
|
142
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
143
|
+
policies: ['admin::isAuthenticatedAdmin'],
|
|
143
144
|
},
|
|
144
145
|
},
|
|
145
146
|
{
|
|
@@ -8,6 +8,7 @@ module.exports = {
|
|
|
8
8
|
'seat-enforcement': require('./seat-enforcement'),
|
|
9
9
|
workflows: require('./review-workflows/workflows'),
|
|
10
10
|
stages: require('./review-workflows/stages'),
|
|
11
|
+
'stage-permissions': require('./review-workflows/stage-permissions'),
|
|
11
12
|
assignees: require('./review-workflows/assignees'),
|
|
12
13
|
'review-workflows': require('./review-workflows/review-workflows'),
|
|
13
14
|
'review-workflows-validation': require('./review-workflows/validation'),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { prop } = require('lodash/fp');
|
|
4
|
+
const {
|
|
5
|
+
mapAsync,
|
|
6
|
+
errors: { ApplicationError },
|
|
7
|
+
} = require('@strapi/utils');
|
|
8
|
+
const { getService } = require('../../utils');
|
|
9
|
+
const { STAGE_TRANSITION_UID } = require('../../constants/workflows');
|
|
10
|
+
|
|
11
|
+
const validActions = [STAGE_TRANSITION_UID];
|
|
12
|
+
|
|
13
|
+
module.exports = ({ strapi }) => {
|
|
14
|
+
const roleService = getService('role');
|
|
15
|
+
const permissionService = getService('permission');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
async register({ roleId, action, fromStage }) {
|
|
19
|
+
if (!validActions.includes(action)) {
|
|
20
|
+
throw new ApplicationError(`Invalid action ${action}`);
|
|
21
|
+
}
|
|
22
|
+
const permissions = await roleService.addPermissions(roleId, [
|
|
23
|
+
{
|
|
24
|
+
action,
|
|
25
|
+
actionParameters: {
|
|
26
|
+
from: fromStage,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// TODO: Filter response
|
|
32
|
+
return permissions;
|
|
33
|
+
},
|
|
34
|
+
async registerMany(permissions) {
|
|
35
|
+
return mapAsync(permissions, this.register);
|
|
36
|
+
},
|
|
37
|
+
async unregister(permissions) {
|
|
38
|
+
const permissionIds = permissions.map(prop('id'));
|
|
39
|
+
await permissionService.deleteByIds(permissionIds);
|
|
40
|
+
},
|
|
41
|
+
can(action, fromStage) {
|
|
42
|
+
const requestState = strapi.requestContext.get()?.state;
|
|
43
|
+
|
|
44
|
+
if (!requestState) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Override permissions for super admin
|
|
49
|
+
const userRoles = requestState.user?.roles;
|
|
50
|
+
if (userRoles?.some((role) => role.code === 'strapi-super-admin')) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return requestState.userAbility.can({
|
|
55
|
+
name: action,
|
|
56
|
+
params: { from: fromStage },
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
mapAsync,
|
|
5
|
+
reduceAsync,
|
|
5
6
|
errors: { ApplicationError, ValidationError },
|
|
6
7
|
} = require('@strapi/utils');
|
|
7
|
-
const { map } = require('lodash/fp');
|
|
8
|
+
const { map, pick, isEqual } = require('lodash/fp');
|
|
8
9
|
|
|
9
10
|
const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE, ERRORS } = require('../../constants/workflows');
|
|
10
11
|
const { getService } = require('../../utils');
|
|
11
12
|
|
|
13
|
+
const sanitizedStageFields = ['id', 'name', 'workflow', 'color'];
|
|
14
|
+
const sanitizeStageFields = pick(sanitizedStageFields);
|
|
15
|
+
|
|
12
16
|
module.exports = ({ strapi }) => {
|
|
13
17
|
const metrics = getService('review-workflows-metrics', { strapi });
|
|
18
|
+
const stagePermissionsService = getService('stage-permissions', { strapi });
|
|
14
19
|
const workflowsValidationService = getService('review-workflows-validation', { strapi });
|
|
15
20
|
|
|
16
21
|
return {
|
|
@@ -32,20 +37,72 @@ module.exports = ({ strapi }) => {
|
|
|
32
37
|
async createMany(stagesList, { fields } = {}) {
|
|
33
38
|
const params = { select: fields ?? '*' };
|
|
34
39
|
|
|
40
|
+
// TODO: pick the fields from the stage
|
|
35
41
|
const stages = await Promise.all(
|
|
36
42
|
stagesList.map((stage) =>
|
|
37
|
-
strapi.entityService.create(STAGE_MODEL_UID, {
|
|
43
|
+
strapi.entityService.create(STAGE_MODEL_UID, {
|
|
44
|
+
data: sanitizeStageFields(stage),
|
|
45
|
+
...params,
|
|
46
|
+
})
|
|
38
47
|
)
|
|
39
48
|
);
|
|
40
49
|
|
|
50
|
+
// Create stage permissions
|
|
51
|
+
await reduceAsync(stagesList)(async (_, stage, idx) => {
|
|
52
|
+
// Ignore stages without permissions
|
|
53
|
+
if (!stage.permissions || stage.permissions.length === 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stagePermissions = stage.permissions;
|
|
58
|
+
const stageId = stages[idx].id;
|
|
59
|
+
|
|
60
|
+
const permissions = await mapAsync(
|
|
61
|
+
stagePermissions,
|
|
62
|
+
// Register each stage permission
|
|
63
|
+
(permission) =>
|
|
64
|
+
stagePermissionsService.register({
|
|
65
|
+
roleId: permission.role,
|
|
66
|
+
action: permission.action,
|
|
67
|
+
fromStage: stageId,
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Update stage with the new permissions
|
|
72
|
+
await strapi.entityService.update(STAGE_MODEL_UID, stageId, {
|
|
73
|
+
data: {
|
|
74
|
+
permissions: permissions.flat().map((p) => p.id),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
41
79
|
metrics.sendDidCreateStage();
|
|
42
80
|
|
|
43
81
|
return stages;
|
|
44
82
|
},
|
|
45
83
|
|
|
46
|
-
async update(
|
|
84
|
+
async update(srcStage, destStage) {
|
|
85
|
+
let stagePermissions = srcStage?.permissions ?? [];
|
|
86
|
+
const stageId = destStage.id;
|
|
87
|
+
|
|
88
|
+
if (destStage.permissions) {
|
|
89
|
+
await this.deleteStagePermissions([srcStage]);
|
|
90
|
+
|
|
91
|
+
const permissions = await mapAsync(destStage.permissions, (permission) =>
|
|
92
|
+
stagePermissionsService.register({
|
|
93
|
+
roleId: permission.role,
|
|
94
|
+
action: permission.action,
|
|
95
|
+
fromStage: stageId,
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
stagePermissions = permissions.flat().map((p) => p.id);
|
|
99
|
+
}
|
|
100
|
+
|
|
47
101
|
const stage = await strapi.entityService.update(STAGE_MODEL_UID, stageId, {
|
|
48
|
-
data:
|
|
102
|
+
data: {
|
|
103
|
+
...destStage,
|
|
104
|
+
permissions: stagePermissions,
|
|
105
|
+
},
|
|
49
106
|
});
|
|
50
107
|
|
|
51
108
|
metrics.sendDidEditStage();
|
|
@@ -53,20 +110,31 @@ module.exports = ({ strapi }) => {
|
|
|
53
110
|
return stage;
|
|
54
111
|
},
|
|
55
112
|
|
|
56
|
-
async delete(
|
|
57
|
-
|
|
113
|
+
async delete(stage) {
|
|
114
|
+
// Unregister all permissions related to this stage id
|
|
115
|
+
await this.deleteStagePermissions([stage]);
|
|
116
|
+
|
|
117
|
+
const deletedStage = await strapi.entityService.delete(STAGE_MODEL_UID, stage.id);
|
|
58
118
|
|
|
59
119
|
metrics.sendDidDeleteStage();
|
|
60
120
|
|
|
61
|
-
return
|
|
121
|
+
return deletedStage;
|
|
62
122
|
},
|
|
63
123
|
|
|
64
|
-
async deleteMany(
|
|
124
|
+
async deleteMany(stages) {
|
|
125
|
+
await this.deleteStagePermissions(stages);
|
|
126
|
+
|
|
65
127
|
return strapi.entityService.deleteMany(STAGE_MODEL_UID, {
|
|
66
|
-
filters: { id: { $in:
|
|
128
|
+
filters: { id: { $in: stages.map((s) => s.id) } },
|
|
67
129
|
});
|
|
68
130
|
},
|
|
69
131
|
|
|
132
|
+
async deleteStagePermissions(stages) {
|
|
133
|
+
// TODO: Find another way to do this for when we use the "to" parameter.
|
|
134
|
+
const permissions = stages.map((s) => s.permissions || []).flat();
|
|
135
|
+
await stagePermissionsService.unregister(permissions || []);
|
|
136
|
+
},
|
|
137
|
+
|
|
70
138
|
count({ workflowId } = {}) {
|
|
71
139
|
const opts = {};
|
|
72
140
|
|
|
@@ -91,7 +159,11 @@ module.exports = ({ strapi }) => {
|
|
|
91
159
|
const createdStagesIds = map('id', createdStages);
|
|
92
160
|
|
|
93
161
|
// Update the workflow stages
|
|
94
|
-
await mapAsync(updated, (
|
|
162
|
+
await mapAsync(updated, (destStage) => {
|
|
163
|
+
const srcStage = srcStages.find((s) => s.id === destStage.id);
|
|
164
|
+
|
|
165
|
+
return this.update(srcStage, destStage);
|
|
166
|
+
});
|
|
95
167
|
|
|
96
168
|
// Delete the stages that are not in the new stages list
|
|
97
169
|
await mapAsync(deleted, async (stage) => {
|
|
@@ -114,7 +186,7 @@ module.exports = ({ strapi }) => {
|
|
|
114
186
|
});
|
|
115
187
|
});
|
|
116
188
|
|
|
117
|
-
return this.delete(stage
|
|
189
|
+
return this.delete(stage);
|
|
118
190
|
});
|
|
119
191
|
|
|
120
192
|
return destStages.map((stage) => ({ ...stage, id: stage.id ?? createdStagesIds.shift() }));
|
|
@@ -241,12 +313,19 @@ module.exports = ({ strapi }) => {
|
|
|
241
313
|
*/
|
|
242
314
|
function getDiffBetweenStages(sourceStages, comparisonStages) {
|
|
243
315
|
const result = comparisonStages.reduce(
|
|
316
|
+
// ...
|
|
317
|
+
|
|
244
318
|
(acc, stageToCompare) => {
|
|
245
319
|
const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
|
|
246
320
|
|
|
247
321
|
if (!srcStage) {
|
|
248
322
|
acc.created.push(stageToCompare);
|
|
249
|
-
} else if (
|
|
323
|
+
} else if (
|
|
324
|
+
!isEqual(
|
|
325
|
+
pick(['name', 'color', 'permissions'], srcStage),
|
|
326
|
+
pick(['name', 'color', 'permissions'], stageToCompare)
|
|
327
|
+
)
|
|
328
|
+
) {
|
|
250
329
|
acc.updated.push(stageToCompare);
|
|
251
330
|
}
|
|
252
331
|
return acc;
|