@strapi/review-workflows 5.12.1 → 5.12.3
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/dist/admin/{chunks/index-DcEF47R4.mjs → assets/balloon.png.js} +3 -1078
- package/dist/admin/assets/balloon.png.js.map +1 -0
- package/dist/admin/{chunks/index-CzdEqFOm.js → assets/balloon.png.mjs} +2 -1113
- package/dist/admin/assets/balloon.png.mjs.map +1 -0
- package/dist/admin/assets/purchase-page-illustration-dark.svg.js +6 -0
- package/dist/admin/assets/purchase-page-illustration-dark.svg.js.map +1 -0
- package/dist/admin/assets/purchase-page-illustration-dark.svg.mjs +4 -0
- package/dist/admin/assets/purchase-page-illustration-dark.svg.mjs.map +1 -0
- package/dist/admin/{chunks/purchase-review-workflows-4n0KXAeo.mjs → assets/purchase-page-illustration-light.svg.js} +3 -197
- package/dist/admin/assets/purchase-page-illustration-light.svg.js.map +1 -0
- package/dist/admin/{chunks/purchase-review-workflows-BDLncDcz.js → assets/purchase-page-illustration-light.svg.mjs} +2 -200
- package/dist/admin/assets/purchase-page-illustration-light.svg.mjs.map +1 -0
- package/dist/admin/components/LimitsModal.js +122 -0
- package/dist/admin/components/LimitsModal.js.map +1 -0
- package/dist/admin/components/LimitsModal.mjs +120 -0
- package/dist/admin/components/LimitsModal.mjs.map +1 -0
- package/dist/admin/constants.js +18 -0
- package/dist/admin/constants.js.map +1 -0
- package/dist/admin/constants.mjs +12 -0
- package/dist/admin/constants.mjs.map +1 -0
- package/dist/admin/index.js +87 -13
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +88 -12
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/modules/hooks.js +8 -0
- package/dist/admin/modules/hooks.js.map +1 -0
- package/dist/admin/modules/hooks.mjs +6 -0
- package/dist/admin/modules/hooks.mjs.map +1 -0
- package/dist/admin/{chunks/router-ChVwf8TN.js → router.js} +3 -3
- package/dist/admin/router.js.map +1 -0
- package/dist/admin/{chunks/router-D-YCUzYy.mjs → router.mjs} +3 -3
- package/dist/admin/router.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/components/TableColumns.js +44 -0
- package/dist/admin/routes/content-manager/model/components/TableColumns.js.map +1 -0
- package/dist/admin/routes/content-manager/model/components/TableColumns.mjs +41 -0
- package/dist/admin/routes/content-manager/model/components/TableColumns.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/constants.js +60 -0
- package/dist/admin/routes/content-manager/model/constants.js.map +1 -0
- package/dist/admin/routes/content-manager/model/constants.mjs +58 -0
- package/dist/admin/routes/content-manager/model/constants.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/AssigneeSelect.js +169 -0
- package/dist/admin/routes/content-manager/model/id/components/AssigneeSelect.js.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/AssigneeSelect.mjs +148 -0
- package/dist/admin/routes/content-manager/model/id/components/AssigneeSelect.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/Header.js +31 -0
- package/dist/admin/routes/content-manager/model/id/components/Header.js.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/Header.mjs +29 -0
- package/dist/admin/routes/content-manager/model/id/components/Header.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/Panel.js +39 -0
- package/dist/admin/routes/content-manager/model/id/components/Panel.js.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/Panel.mjs +37 -0
- package/dist/admin/routes/content-manager/model/id/components/Panel.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/StageSelect.js +329 -0
- package/dist/admin/routes/content-manager/model/id/components/StageSelect.js.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/StageSelect.mjs +308 -0
- package/dist/admin/routes/content-manager/model/id/components/StageSelect.mjs.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/constants.js +8 -0
- package/dist/admin/routes/content-manager/model/id/components/constants.js.map +1 -0
- package/dist/admin/routes/content-manager/model/id/components/constants.mjs +5 -0
- package/dist/admin/routes/content-manager/model/id/components/constants.mjs.map +1 -0
- package/dist/admin/routes/purchase-review-workflows.js +194 -0
- package/dist/admin/routes/purchase-review-workflows.js.map +1 -0
- package/dist/admin/routes/purchase-review-workflows.mjs +192 -0
- package/dist/admin/routes/purchase-review-workflows.mjs.map +1 -0
- package/dist/admin/routes/settings/components/AddStage.js +51 -0
- package/dist/admin/routes/settings/components/AddStage.js.map +1 -0
- package/dist/admin/routes/settings/components/AddStage.mjs +49 -0
- package/dist/admin/routes/settings/components/AddStage.mjs.map +1 -0
- package/dist/admin/routes/settings/components/Layout.js +86 -0
- package/dist/admin/routes/settings/components/Layout.js.map +1 -0
- package/dist/admin/routes/settings/components/Layout.mjs +82 -0
- package/dist/admin/routes/settings/components/Layout.mjs.map +1 -0
- package/dist/admin/routes/settings/components/StageDragPreview.js +40 -0
- package/dist/admin/routes/settings/components/StageDragPreview.js.map +1 -0
- package/dist/admin/routes/settings/components/StageDragPreview.mjs +38 -0
- package/dist/admin/routes/settings/components/StageDragPreview.mjs.map +1 -0
- package/dist/admin/routes/settings/components/Stages.js +593 -0
- package/dist/admin/routes/settings/components/Stages.js.map +1 -0
- package/dist/admin/routes/settings/components/Stages.mjs +572 -0
- package/dist/admin/routes/settings/components/Stages.mjs.map +1 -0
- package/dist/admin/routes/settings/components/WorkflowAttributes.js +203 -0
- package/dist/admin/routes/settings/components/WorkflowAttributes.js.map +1 -0
- package/dist/admin/routes/settings/components/WorkflowAttributes.mjs +201 -0
- package/dist/admin/routes/settings/components/WorkflowAttributes.mjs.map +1 -0
- package/dist/admin/routes/settings/constants.js +8 -0
- package/dist/admin/routes/settings/constants.js.map +1 -0
- package/dist/admin/routes/settings/constants.mjs +6 -0
- package/dist/admin/routes/settings/constants.mjs.map +1 -0
- package/dist/admin/routes/settings/hooks/useDragAndDrop.js +193 -0
- package/dist/admin/routes/settings/hooks/useDragAndDrop.js.map +1 -0
- package/dist/admin/routes/settings/hooks/useDragAndDrop.mjs +170 -0
- package/dist/admin/routes/settings/hooks/useDragAndDrop.mjs.map +1 -0
- package/dist/admin/routes/settings/hooks/useKeyboardDragAndDrop.js +94 -0
- package/dist/admin/routes/settings/hooks/useKeyboardDragAndDrop.js.map +1 -0
- package/dist/admin/routes/settings/hooks/useKeyboardDragAndDrop.mjs +73 -0
- package/dist/admin/routes/settings/hooks/useKeyboardDragAndDrop.mjs.map +1 -0
- package/dist/admin/{chunks/Layout-C4ri_ldC.js → routes/settings/hooks/useReviewWorkflows.js} +6 -121
- package/dist/admin/routes/settings/hooks/useReviewWorkflows.js.map +1 -0
- package/dist/admin/{chunks/Layout-CF497D6H.mjs → routes/settings/hooks/useReviewWorkflows.mjs} +4 -115
- package/dist/admin/routes/settings/hooks/useReviewWorkflows.mjs.map +1 -0
- package/dist/admin/routes/settings/id.js +404 -0
- package/dist/admin/routes/settings/id.js.map +1 -0
- package/dist/admin/routes/settings/id.mjs +382 -0
- package/dist/admin/routes/settings/id.mjs.map +1 -0
- package/dist/admin/{chunks/index-CCx4kT-t.js → routes/settings/index.js} +15 -15
- package/dist/admin/routes/settings/index.js.map +1 -0
- package/dist/admin/{chunks/index-iChY7MsG.mjs → routes/settings/index.mjs} +7 -7
- package/dist/admin/routes/settings/index.mjs.map +1 -0
- package/dist/admin/services/admin.js +23 -0
- package/dist/admin/services/admin.js.map +1 -0
- package/dist/admin/services/admin.mjs +21 -0
- package/dist/admin/services/admin.mjs.map +1 -0
- package/dist/admin/services/api.js +15 -0
- package/dist/admin/services/api.js.map +1 -0
- package/dist/admin/services/api.mjs +13 -0
- package/dist/admin/services/api.mjs.map +1 -0
- package/dist/admin/services/content-manager.js +101 -0
- package/dist/admin/services/content-manager.js.map +1 -0
- package/dist/admin/services/content-manager.mjs +96 -0
- package/dist/admin/services/content-manager.mjs.map +1 -0
- package/dist/admin/services/settings.js +123 -0
- package/dist/admin/services/settings.js.map +1 -0
- package/dist/admin/services/settings.mjs +118 -0
- package/dist/admin/services/settings.mjs.map +1 -0
- package/dist/admin/{chunks/en-BNGiWajd.js → translations/en.json.js} +2 -2
- package/dist/admin/translations/en.json.js.map +1 -0
- package/dist/admin/{chunks/en-BrZXFtVv.mjs → translations/en.json.mjs} +1 -1
- package/dist/admin/translations/en.json.mjs.map +1 -0
- package/dist/admin/{chunks/uk-CbRUr1I7.js → translations/uk.json.js} +2 -2
- package/dist/admin/translations/uk.json.js.map +1 -0
- package/dist/admin/{chunks/uk-DLlzEBUF.mjs → translations/uk.json.mjs} +1 -1
- package/dist/admin/translations/uk.json.mjs.map +1 -0
- package/dist/admin/utils/api.js +22 -0
- package/dist/admin/utils/api.js.map +1 -0
- package/dist/admin/utils/api.mjs +19 -0
- package/dist/admin/utils/api.mjs.map +1 -0
- package/dist/admin/utils/cm-hooks.js +23 -0
- package/dist/admin/utils/cm-hooks.js.map +1 -0
- package/dist/admin/utils/cm-hooks.mjs +21 -0
- package/dist/admin/utils/cm-hooks.mjs.map +1 -0
- package/dist/admin/utils/colors.js +50 -0
- package/dist/admin/utils/colors.js.map +1 -0
- package/dist/admin/utils/colors.mjs +47 -0
- package/dist/admin/utils/colors.mjs.map +1 -0
- package/dist/admin/utils/translations.js +11 -0
- package/dist/admin/utils/translations.js.map +1 -0
- package/dist/admin/utils/translations.mjs +9 -0
- package/dist/admin/utils/translations.mjs.map +1 -0
- package/dist/admin/utils/users.js +17 -0
- package/dist/admin/utils/users.js.map +1 -0
- package/dist/admin/utils/users.mjs +15 -0
- package/dist/admin/utils/users.mjs.map +1 -0
- package/dist/server/bootstrap.js +54 -0
- package/dist/server/bootstrap.js.map +1 -0
- package/dist/server/bootstrap.mjs +52 -0
- package/dist/server/bootstrap.mjs.map +1 -0
- package/dist/server/config/actions.js +47 -0
- package/dist/server/config/actions.js.map +1 -0
- package/dist/server/config/actions.mjs +45 -0
- package/dist/server/config/actions.mjs.map +1 -0
- package/dist/server/constants/default-stages.json.js +23 -0
- package/dist/server/constants/default-stages.json.js.map +1 -0
- package/dist/server/constants/default-stages.json.mjs +21 -0
- package/dist/server/constants/default-stages.json.mjs.map +1 -0
- package/dist/server/constants/default-workflow.json.js +12 -0
- package/dist/server/constants/default-workflow.json.js.map +1 -0
- package/dist/server/constants/default-workflow.json.mjs +7 -0
- package/dist/server/constants/default-workflow.json.mjs.map +1 -0
- package/dist/server/constants/webhook-events.js +12 -0
- package/dist/server/constants/webhook-events.js.map +1 -0
- package/dist/server/constants/webhook-events.mjs +7 -0
- package/dist/server/constants/webhook-events.mjs.map +1 -0
- package/dist/server/constants/workflows.js +53 -0
- package/dist/server/constants/workflows.js.map +1 -0
- package/dist/server/constants/workflows.mjs +42 -0
- package/dist/server/constants/workflows.mjs.map +1 -0
- package/dist/server/content-types/index.js +12 -0
- package/dist/server/content-types/index.js.map +1 -0
- package/dist/server/content-types/index.mjs +10 -0
- package/dist/server/content-types/index.mjs.map +1 -0
- package/dist/server/content-types/workflow/index.js +50 -0
- package/dist/server/content-types/workflow/index.js.map +1 -0
- package/dist/server/content-types/workflow/index.mjs +48 -0
- package/dist/server/content-types/workflow/index.mjs.map +1 -0
- package/dist/server/content-types/workflow-stage/index.js +54 -0
- package/dist/server/content-types/workflow-stage/index.js.map +1 -0
- package/dist/server/content-types/workflow-stage/index.mjs +52 -0
- package/dist/server/content-types/workflow-stage/index.mjs.map +1 -0
- package/dist/server/controllers/assignees.js +57 -0
- package/dist/server/controllers/assignees.js.map +1 -0
- package/dist/server/controllers/assignees.mjs +55 -0
- package/dist/server/controllers/assignees.mjs.map +1 -0
- package/dist/server/controllers/index.js +14 -0
- package/dist/server/controllers/index.js.map +1 -0
- package/dist/server/controllers/index.mjs +12 -0
- package/dist/server/controllers/index.mjs.map +1 -0
- package/dist/server/controllers/stages.js +167 -0
- package/dist/server/controllers/stages.js.map +1 -0
- package/dist/server/controllers/stages.mjs +165 -0
- package/dist/server/controllers/stages.mjs.map +1 -0
- package/dist/server/controllers/workflows.js +136 -0
- package/dist/server/controllers/workflows.js.map +1 -0
- package/dist/server/controllers/workflows.mjs +134 -0
- package/dist/server/controllers/workflows.mjs.map +1 -0
- package/dist/server/destroy.js +6 -0
- package/dist/server/destroy.js.map +1 -0
- package/dist/server/destroy.mjs +4 -0
- package/dist/server/destroy.mjs.map +1 -0
- package/dist/server/index.js +12 -2333
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +7 -2328
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/middlewares/review-workflows.js +42 -0
- package/dist/server/middlewares/review-workflows.js.map +1 -0
- package/dist/server/middlewares/review-workflows.mjs +37 -0
- package/dist/server/middlewares/review-workflows.mjs.map +1 -0
- package/dist/server/migrations/handle-deleted-ct-in-workflows.js +40 -0
- package/dist/server/migrations/handle-deleted-ct-in-workflows.js.map +1 -0
- package/dist/server/migrations/handle-deleted-ct-in-workflows.mjs +38 -0
- package/dist/server/migrations/handle-deleted-ct-in-workflows.mjs.map +1 -0
- package/dist/server/migrations/multiple-workflows.js +41 -0
- package/dist/server/migrations/multiple-workflows.js.map +1 -0
- package/dist/server/migrations/multiple-workflows.mjs +39 -0
- package/dist/server/migrations/multiple-workflows.mjs.map +1 -0
- package/dist/server/migrations/set-stages-default-color.js +22 -0
- package/dist/server/migrations/set-stages-default-color.js.map +1 -0
- package/dist/server/migrations/set-stages-default-color.mjs +20 -0
- package/dist/server/migrations/set-stages-default-color.mjs.map +1 -0
- package/dist/server/migrations/set-stages-roles.js +56 -0
- package/dist/server/migrations/set-stages-roles.js.map +1 -0
- package/dist/server/migrations/set-stages-roles.mjs +54 -0
- package/dist/server/migrations/set-stages-roles.mjs.map +1 -0
- package/dist/server/migrations/set-workflow-default-name.js +29 -0
- package/dist/server/migrations/set-workflow-default-name.js.map +1 -0
- package/dist/server/migrations/set-workflow-default-name.mjs +27 -0
- package/dist/server/migrations/set-workflow-default-name.mjs.map +1 -0
- package/dist/server/migrations/shorten-stage-attribute.js +45 -0
- package/dist/server/migrations/shorten-stage-attribute.js.map +1 -0
- package/dist/server/migrations/shorten-stage-attribute.mjs +43 -0
- package/dist/server/migrations/shorten-stage-attribute.mjs.map +1 -0
- package/dist/server/register.js +116 -0
- package/dist/server/register.js.map +1 -0
- package/dist/server/register.mjs +114 -0
- package/dist/server/register.mjs.map +1 -0
- package/dist/server/routes/index.js +10 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/index.mjs +8 -0
- package/dist/server/routes/index.mjs.map +1 -0
- package/dist/server/routes/review-workflows.js +186 -0
- package/dist/server/routes/review-workflows.js.map +1 -0
- package/dist/server/routes/review-workflows.mjs +184 -0
- package/dist/server/routes/review-workflows.mjs.map +1 -0
- package/dist/server/routes/utils.js +11 -0
- package/dist/server/routes/utils.js.map +1 -0
- package/dist/server/routes/utils.mjs +9 -0
- package/dist/server/routes/utils.mjs.map +1 -0
- package/dist/server/services/assignees.js +68 -0
- package/dist/server/services/assignees.js.map +1 -0
- package/dist/server/services/assignees.mjs +66 -0
- package/dist/server/services/assignees.mjs.map +1 -0
- package/dist/server/services/document-service-middleware.js +130 -0
- package/dist/server/services/document-service-middleware.js.map +1 -0
- package/dist/server/services/document-service-middleware.mjs +128 -0
- package/dist/server/services/document-service-middleware.mjs.map +1 -0
- package/dist/server/services/index.js +24 -0
- package/dist/server/services/index.js.map +1 -0
- package/dist/server/services/index.mjs +22 -0
- package/dist/server/services/index.mjs.map +1 -0
- package/dist/server/services/metrics/index.js +67 -0
- package/dist/server/services/metrics/index.js.map +1 -0
- package/dist/server/services/metrics/index.mjs +55 -0
- package/dist/server/services/metrics/index.mjs.map +1 -0
- package/dist/server/services/metrics/weekly-metrics.js +84 -0
- package/dist/server/services/metrics/weekly-metrics.js.map +1 -0
- package/dist/server/services/metrics/weekly-metrics.mjs +82 -0
- package/dist/server/services/metrics/weekly-metrics.mjs.map +1 -0
- package/dist/server/services/stage-permissions.js +59 -0
- package/dist/server/services/stage-permissions.js.map +1 -0
- package/dist/server/services/stage-permissions.mjs +57 -0
- package/dist/server/services/stage-permissions.mjs.map +1 -0
- package/dist/server/services/stages.js +353 -0
- package/dist/server/services/stages.js.map +1 -0
- package/dist/server/services/stages.mjs +351 -0
- package/dist/server/services/stages.mjs.map +1 -0
- package/dist/server/services/validation.js +69 -0
- package/dist/server/services/validation.js.map +1 -0
- package/dist/server/services/validation.mjs +67 -0
- package/dist/server/services/validation.mjs.map +1 -0
- package/dist/server/services/workflow-content-types.js +90 -0
- package/dist/server/services/workflow-content-types.js.map +1 -0
- package/dist/server/services/workflow-content-types.mjs +88 -0
- package/dist/server/services/workflow-content-types.mjs.map +1 -0
- package/dist/server/services/workflows.js +279 -0
- package/dist/server/services/workflows.js.map +1 -0
- package/dist/server/services/workflows.mjs +277 -0
- package/dist/server/services/workflows.mjs.map +1 -0
- package/dist/server/utils/index.js +16 -0
- package/dist/server/utils/index.js.map +1 -0
- package/dist/server/utils/index.mjs +13 -0
- package/dist/server/utils/index.mjs.map +1 -0
- package/dist/server/utils/review-workflows.js +36 -0
- package/dist/server/utils/review-workflows.js.map +1 -0
- package/dist/server/utils/review-workflows.mjs +30 -0
- package/dist/server/utils/review-workflows.mjs.map +1 -0
- package/dist/server/validation/review-workflows.js +71 -0
- package/dist/server/validation/review-workflows.js.map +1 -0
- package/dist/server/validation/review-workflows.mjs +65 -0
- package/dist/server/validation/review-workflows.mjs.map +1 -0
- package/package.json +5 -5
- package/dist/admin/chunks/Layout-C4ri_ldC.js.map +0 -1
- package/dist/admin/chunks/Layout-CF497D6H.mjs.map +0 -1
- package/dist/admin/chunks/en-BNGiWajd.js.map +0 -1
- package/dist/admin/chunks/en-BrZXFtVv.mjs.map +0 -1
- package/dist/admin/chunks/id-DVOtqJqn.js +0 -1442
- package/dist/admin/chunks/id-DVOtqJqn.js.map +0 -1
- package/dist/admin/chunks/id-QD0V9dME.mjs +0 -1420
- package/dist/admin/chunks/id-QD0V9dME.mjs.map +0 -1
- package/dist/admin/chunks/index-CCx4kT-t.js.map +0 -1
- package/dist/admin/chunks/index-CzdEqFOm.js.map +0 -1
- package/dist/admin/chunks/index-DcEF47R4.mjs.map +0 -1
- package/dist/admin/chunks/index-iChY7MsG.mjs.map +0 -1
- package/dist/admin/chunks/purchase-review-workflows-4n0KXAeo.mjs.map +0 -1
- package/dist/admin/chunks/purchase-review-workflows-BDLncDcz.js.map +0 -1
- package/dist/admin/chunks/router-ChVwf8TN.js.map +0 -1
- package/dist/admin/chunks/router-D-YCUzYy.mjs.map +0 -1
- package/dist/admin/chunks/uk-CbRUr1I7.js.map +0 -1
- package/dist/admin/chunks/uk-DLlzEBUF.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,2331 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import '
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
strapi: global.strapi
|
|
9
|
-
})=>{
|
|
10
|
-
return strapi.service(`admin::${name}`);
|
|
11
|
-
};
|
|
12
|
-
const getService = (name, { strapi } = {
|
|
13
|
-
strapi: global.strapi
|
|
14
|
-
})=>{
|
|
15
|
-
return strapi.plugin('review-workflows').service(name);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const WORKFLOW_MODEL_UID = 'plugin::review-workflows.workflow';
|
|
19
|
-
const STAGE_MODEL_UID = 'plugin::review-workflows.workflow-stage';
|
|
20
|
-
/**
|
|
21
|
-
* TODO: For V4 compatibility, the old UID was kept, when review workflows was in the admin package
|
|
22
|
-
*
|
|
23
|
-
* NOTE!: if you change this string you need to change it here too: strapi/packages/core/review-workflows/admin/src/routes/settings/components/Stages.tsx
|
|
24
|
-
*/ const STAGE_TRANSITION_UID = 'admin::review-workflows.stage.transition';
|
|
25
|
-
const STAGE_DEFAULT_COLOR = '#4945FF';
|
|
26
|
-
const ENTITY_STAGE_ATTRIBUTE = 'strapi_stage';
|
|
27
|
-
const ENTITY_ASSIGNEE_ATTRIBUTE = 'strapi_assignee';
|
|
28
|
-
const MAX_WORKFLOWS = 200;
|
|
29
|
-
const MAX_STAGES_PER_WORKFLOW = 200;
|
|
30
|
-
const ERRORS = {
|
|
31
|
-
WORKFLOW_WITHOUT_STAGES: 'A workflow must have at least one stage.',
|
|
32
|
-
WORKFLOWS_LIMIT: 'You’ve reached the limit of workflows in your plan. Delete a workflow or contact Sales to enable more workflows.',
|
|
33
|
-
STAGES_LIMIT: 'You’ve reached the limit of stages for this workflow in your plan. Try deleting some stages or contact Sales to enable more stages.',
|
|
34
|
-
DUPLICATED_STAGE_NAME: 'Stage names must be unique.'
|
|
35
|
-
};
|
|
36
|
-
const WORKFLOW_POPULATE = {
|
|
37
|
-
stages: {
|
|
38
|
-
populate: {
|
|
39
|
-
permissions: {
|
|
40
|
-
fields: [
|
|
41
|
-
'action',
|
|
42
|
-
'actionParameters'
|
|
43
|
-
],
|
|
44
|
-
populate: {
|
|
45
|
-
role: {
|
|
46
|
-
fields: [
|
|
47
|
-
'id',
|
|
48
|
-
'name'
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
stageRequiredToPublish: true
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
function checkVersionThreshold(startVersion, currentVersion, thresholdVersion) {
|
|
59
|
-
return semver.gte(currentVersion, thresholdVersion) && semver.lt(startVersion, thresholdVersion);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Shorten strapi stage name
|
|
63
|
-
*/ async function migrateStageAttribute({ oldContentTypes, contentTypes }) {
|
|
64
|
-
const getRWVersion = getOr('0.0.0', `${STAGE_MODEL_UID}.options.version`);
|
|
65
|
-
const oldRWVersion = getRWVersion(oldContentTypes);
|
|
66
|
-
const currentRWVersion = getRWVersion(contentTypes);
|
|
67
|
-
checkVersionThreshold(oldRWVersion, currentRWVersion, '1.1.0');
|
|
68
|
-
// TODO: Find tables with something else than `findTables` function
|
|
69
|
-
// if (migrationNeeded) {
|
|
70
|
-
// const oldAttributeTableName = 'strapi_review_workflows_stage';
|
|
71
|
-
// const newAttributeTableName = 'strapi_stage';
|
|
72
|
-
// // const tables = await findTables({ strapi }, new RegExp(oldAttributeTableName));
|
|
73
|
-
// await async.map(tables, async (tableName: string) => {
|
|
74
|
-
// const newTableName = tableName.replace(oldAttributeTableName, newAttributeTableName);
|
|
75
|
-
// const alreadyHasNextTable = await strapi.db.connection.schema.hasTable(newTableName);
|
|
76
|
-
// // The table can be already created but empty. In order to rename the old one, we need to drop the previously created empty one.
|
|
77
|
-
// if (alreadyHasNextTable) {
|
|
78
|
-
// const dataInTable = await strapi.db.connection(newTableName).select().limit(1);
|
|
79
|
-
// if (!dataInTable.length) {
|
|
80
|
-
// await strapi.db.connection.schema.dropTable(newTableName);
|
|
81
|
-
// }
|
|
82
|
-
// }
|
|
83
|
-
// try {
|
|
84
|
-
// await strapi.db.connection.schema.renameTable(tableName, newTableName);
|
|
85
|
-
// } catch (e: any) {
|
|
86
|
-
// strapi.log.warn(
|
|
87
|
-
// `An error occurred during the migration of ${tableName} table to ${newTableName}.\nIf ${newTableName} already exists, migration can't be done automatically.`
|
|
88
|
-
// );
|
|
89
|
-
// strapi.log.warn(e.message);
|
|
90
|
-
// }
|
|
91
|
-
// });
|
|
92
|
-
// }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Set the default color for stages if the color attribute was added
|
|
97
|
-
*/ async function migrateReviewWorkflowStagesColor({ oldContentTypes, contentTypes }) {
|
|
98
|
-
// Look for CT's color attribute
|
|
99
|
-
const hadColor = !!oldContentTypes?.[STAGE_MODEL_UID]?.attributes?.color;
|
|
100
|
-
const hasColor = !!contentTypes?.[STAGE_MODEL_UID]?.attributes?.color;
|
|
101
|
-
// Add the default stage color if color attribute was added
|
|
102
|
-
if (!hadColor && hasColor) {
|
|
103
|
-
await strapi.db.query(STAGE_MODEL_UID).updateMany({
|
|
104
|
-
data: {
|
|
105
|
-
color: STAGE_DEFAULT_COLOR
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Migrate review workflow stages to have RBAC permissions for all roles.
|
|
113
|
-
*/ async function migrateReviewWorkflowStagesRoles({ oldContentTypes, contentTypes }) {
|
|
114
|
-
const hadRolePermissions = !!oldContentTypes?.[STAGE_MODEL_UID]?.attributes?.permissions;
|
|
115
|
-
const hasRolePermissions = !!contentTypes?.[STAGE_MODEL_UID]?.attributes?.permissions;
|
|
116
|
-
// If the stage content type did not have permissions in the previous version
|
|
117
|
-
// then we set the permissions of every stage to be every current role in the app.
|
|
118
|
-
// This ensures consistent behaviour when upgrading to a strapi version with review workflows RBAC.
|
|
119
|
-
if (!hadRolePermissions && hasRolePermissions) {
|
|
120
|
-
const roleUID = 'admin::role';
|
|
121
|
-
strapi.log.info(`Migrating all existing review workflow stages to have RBAC permissions for all ${roleUID}.`);
|
|
122
|
-
const stagePermissionsService = getService('stage-permissions');
|
|
123
|
-
const stages = await strapi.db.query(STAGE_MODEL_UID).findMany();
|
|
124
|
-
const roles = await strapi.db.query(roleUID).findMany();
|
|
125
|
-
// Collect the permissions to add and group them by stage id.
|
|
126
|
-
const groupedPermissions = {};
|
|
127
|
-
roles.map((role)=>role.id).forEach((roleId)=>{
|
|
128
|
-
stages.map((stage)=>stage.id).forEach((stageId)=>{
|
|
129
|
-
if (!groupedPermissions[stageId]) {
|
|
130
|
-
groupedPermissions[stageId] = [];
|
|
131
|
-
}
|
|
132
|
-
groupedPermissions[stageId].push({
|
|
133
|
-
roleId,
|
|
134
|
-
fromStage: stageId,
|
|
135
|
-
action: STAGE_TRANSITION_UID
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
for (const [stageId, permissions] of Object.entries(groupedPermissions)){
|
|
140
|
-
const numericalStageId = Number(stageId);
|
|
141
|
-
if (Number.isNaN(numericalStageId)) {
|
|
142
|
-
strapi.log.warn(`Unable to apply ${roleUID} migration for ${STAGE_MODEL_UID} with id ${stageId}. The stage does not have a numerical id.`);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
// Register the permissions for this stage
|
|
146
|
-
const stagePermissions = await stagePermissionsService.registerMany(permissions);
|
|
147
|
-
// Update the stage with its new permissions
|
|
148
|
-
await strapi.db.query(STAGE_MODEL_UID).update({
|
|
149
|
-
where: {
|
|
150
|
-
id: numericalStageId
|
|
151
|
-
},
|
|
152
|
-
data: {
|
|
153
|
-
permissions: stagePermissions.flat().map((permission)=>permission.id)
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
var name = "Default";
|
|
161
|
-
var defaultWorkflow = {
|
|
162
|
-
name: name
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Multiple workflows introduced the ability to name a workflow.
|
|
167
|
-
* This migration adds the default workflow name if the name attribute was added.
|
|
168
|
-
*/ async function migrateReviewWorkflowName({ oldContentTypes, contentTypes }) {
|
|
169
|
-
// Look for RW name attribute
|
|
170
|
-
const hadName = !!oldContentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.name;
|
|
171
|
-
const hasName = !!contentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.name;
|
|
172
|
-
// Add the default workflow name if name attribute was added
|
|
173
|
-
if (!hadName && hasName) {
|
|
174
|
-
await strapi.db.query(WORKFLOW_MODEL_UID).updateMany({
|
|
175
|
-
where: {
|
|
176
|
-
name: {
|
|
177
|
-
$null: true
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
data: {
|
|
181
|
-
name: defaultWorkflow.name
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function migrateWorkflowsContentTypes({ oldContentTypes, contentTypes }) {
|
|
188
|
-
// Look for RW contentTypes attribute
|
|
189
|
-
const hadContentTypes = !!oldContentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.contentTypes;
|
|
190
|
-
const hasContentTypes = !!contentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.contentTypes;
|
|
191
|
-
if (!hadContentTypes && hasContentTypes) {
|
|
192
|
-
// Initialize contentTypes with an empty array and assign only to one
|
|
193
|
-
// workflow the Content Types which were using Review Workflow before.
|
|
194
|
-
await strapi.db.query(WORKFLOW_MODEL_UID).updateMany({
|
|
195
|
-
data: {
|
|
196
|
-
contentTypes: []
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
// Find Content Types which were using Review Workflow before
|
|
200
|
-
const contentTypes = pipe([
|
|
201
|
-
pickBy(get('options.reviewWorkflows')),
|
|
202
|
-
keys
|
|
203
|
-
])(oldContentTypes);
|
|
204
|
-
if (contentTypes.length) {
|
|
205
|
-
// Update only one workflow with the contentTypes
|
|
206
|
-
// Before this release there was only one workflow, so this operation is safe.
|
|
207
|
-
await strapi.db.query(WORKFLOW_MODEL_UID).update({
|
|
208
|
-
where: {
|
|
209
|
-
id: {
|
|
210
|
-
$notNull: true
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
data: {
|
|
214
|
-
contentTypes
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const getVisibleContentTypesUID = pipe([
|
|
222
|
-
// Pick only content-types visible in the content-manager and option is not false
|
|
223
|
-
pickBy((value)=>getOr(true, 'pluginOptions.content-manager.visible', value) && !getOr(false, 'options.noStageAttribute', value)),
|
|
224
|
-
// Get UIDs
|
|
225
|
-
keys
|
|
226
|
-
]);
|
|
227
|
-
const hasStageAttribute = has([
|
|
228
|
-
'attributes',
|
|
229
|
-
ENTITY_STAGE_ATTRIBUTE
|
|
230
|
-
]);
|
|
231
|
-
const getWorkflowContentTypeFilter = ({ strapi }, contentType)=>{
|
|
232
|
-
if (strapi.db.dialect.supportsOperator('$jsonSupersetOf')) {
|
|
233
|
-
return {
|
|
234
|
-
$jsonSupersetOf: JSON.stringify([
|
|
235
|
-
contentType
|
|
236
|
-
])
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
$contains: `"${contentType}"`
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
|
-
const clampMaxWorkflows = clamp(1, MAX_WORKFLOWS);
|
|
244
|
-
const clampMaxStagesPerWorkflow = clamp(1, MAX_STAGES_PER_WORKFLOW);
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Remove CT references from workflows if the CT is deleted
|
|
248
|
-
*/ async function migrateDeletedCTInWorkflows({ oldContentTypes, contentTypes }) {
|
|
249
|
-
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes)) ?? [];
|
|
250
|
-
if (deletedContentTypes.length) {
|
|
251
|
-
await async.map(deletedContentTypes, async (deletedContentTypeUID)=>{
|
|
252
|
-
const workflow = await strapi.db.query(WORKFLOW_MODEL_UID).findOne({
|
|
253
|
-
select: [
|
|
254
|
-
'id',
|
|
255
|
-
'contentTypes'
|
|
256
|
-
],
|
|
257
|
-
where: {
|
|
258
|
-
contentTypes: getWorkflowContentTypeFilter({
|
|
259
|
-
strapi
|
|
260
|
-
}, deletedContentTypeUID)
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
if (workflow) {
|
|
264
|
-
await strapi.db.query(WORKFLOW_MODEL_UID).update({
|
|
265
|
-
where: {
|
|
266
|
-
id: workflow.id
|
|
267
|
-
},
|
|
268
|
-
data: {
|
|
269
|
-
contentTypes: workflow.contentTypes.filter((contentTypeUID)=>contentTypeUID !== deletedContentTypeUID)
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* A Strapi middleware function that adds support for review workflows.
|
|
279
|
-
*
|
|
280
|
-
* Why is it needed ?
|
|
281
|
-
* For now, the admin panel cannot have anything but top-level attributes in the content-type for options.
|
|
282
|
-
* But we need the CE part to be agnostics from Review Workflow (which is an EE feature).
|
|
283
|
-
* CE handle the `options` object, that's why we move the reviewWorkflows boolean to the options object.
|
|
284
|
-
*
|
|
285
|
-
* @param {object} strapi - The Strapi instance.
|
|
286
|
-
*/ function contentTypeMiddleware(strapi) {
|
|
287
|
-
/**
|
|
288
|
-
* A middleware function that moves the `reviewWorkflows` attribute from the top level of
|
|
289
|
-
* the request body to the `options` object within the request body.
|
|
290
|
-
*
|
|
291
|
-
* @param {object} ctx - The Koa context object.
|
|
292
|
-
*/ const moveReviewWorkflowOption = (ctx)=>{
|
|
293
|
-
// Move reviewWorkflows to options.reviewWorkflows
|
|
294
|
-
const { reviewWorkflows, ...contentType } = ctx.request.body.contentType;
|
|
295
|
-
if (typeof reviewWorkflows === 'boolean') {
|
|
296
|
-
ctx.request.body.contentType = set('options.reviewWorkflows', reviewWorkflows, contentType);
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
strapi.server.router.use('/content-type-builder/content-types/:uid?', (ctx, next)=>{
|
|
300
|
-
if (ctx.method === 'PUT' || ctx.method === 'POST') {
|
|
301
|
-
moveReviewWorkflowOption(ctx);
|
|
302
|
-
}
|
|
303
|
-
return next();
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
var reviewWorkflowsMiddlewares = {
|
|
307
|
-
contentTypeMiddleware
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const setRelation = (attributeName, target, contentType)=>{
|
|
311
|
-
Object.assign(contentType.attributes, {
|
|
312
|
-
[attributeName]: {
|
|
313
|
-
writable: true,
|
|
314
|
-
private: false,
|
|
315
|
-
configurable: false,
|
|
316
|
-
visible: false,
|
|
317
|
-
useJoinTable: true,
|
|
318
|
-
type: 'relation',
|
|
319
|
-
relation: 'oneToOne',
|
|
320
|
-
target
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
return contentType;
|
|
324
|
-
};
|
|
325
|
-
/**
|
|
326
|
-
* Add the stage and assignee attributes to content types
|
|
327
|
-
*/ function extendReviewWorkflowContentTypes({ strapi }) {
|
|
328
|
-
const contentTypeToExtend = getVisibleContentTypesUID(strapi.contentTypes);
|
|
329
|
-
for (const contentTypeUID of contentTypeToExtend){
|
|
330
|
-
strapi.get('content-types').extend(contentTypeUID, (contentType)=>{
|
|
331
|
-
// Set Stage attribute
|
|
332
|
-
setRelation(ENTITY_STAGE_ATTRIBUTE, STAGE_MODEL_UID, contentType);
|
|
333
|
-
// Set Assignee attribute
|
|
334
|
-
setRelation(ENTITY_ASSIGNEE_ATTRIBUTE, 'admin::user', contentType);
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Persist the stage & assignee attributes so they are not removed when downgrading to CE.
|
|
340
|
-
*
|
|
341
|
-
* TODO: V6 - Instead of persisting the join tables, always create the stage & assignee attributes, even in CE mode
|
|
342
|
-
* It was decided in V4 & V5 to not expose them in CE (as they pollute the CTs) but it's not worth given the complexity this needs
|
|
343
|
-
*/ function persistRWOnDowngrade({ strapi }) {
|
|
344
|
-
const { removePersistedTablesWithSuffix, persistTables } = getAdminService('persist-tables');
|
|
345
|
-
return async ({ contentTypes })=>{
|
|
346
|
-
const getStageTableToPersist = (contentTypeUID)=>{
|
|
347
|
-
// Persist the stage join table
|
|
348
|
-
const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
|
|
349
|
-
const joinTableName = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable.name;
|
|
350
|
-
return {
|
|
351
|
-
name: joinTableName,
|
|
352
|
-
dependsOn: [
|
|
353
|
-
{
|
|
354
|
-
name: tableName
|
|
355
|
-
}
|
|
356
|
-
]
|
|
357
|
-
};
|
|
358
|
-
};
|
|
359
|
-
const getAssigneeTableToPersist = (contentTypeUID)=>{
|
|
360
|
-
// Persist the assignee join table
|
|
361
|
-
const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
|
|
362
|
-
const joinTableName = attributes[ENTITY_ASSIGNEE_ATTRIBUTE].joinTable.name;
|
|
363
|
-
return {
|
|
364
|
-
name: joinTableName,
|
|
365
|
-
dependsOn: [
|
|
366
|
-
{
|
|
367
|
-
name: tableName
|
|
368
|
-
}
|
|
369
|
-
]
|
|
370
|
-
};
|
|
371
|
-
};
|
|
372
|
-
const enabledRWContentTypes = pipe([
|
|
373
|
-
getVisibleContentTypesUID,
|
|
374
|
-
filter((uid)=>hasStageAttribute(contentTypes[uid]))
|
|
375
|
-
])(contentTypes);
|
|
376
|
-
// Remove previously created join tables and persist the new ones
|
|
377
|
-
const stageJoinTablesToPersist = enabledRWContentTypes.map(getStageTableToPersist);
|
|
378
|
-
await removePersistedTablesWithSuffix('_strapi_stage_lnk');
|
|
379
|
-
await persistTables(stageJoinTablesToPersist);
|
|
380
|
-
// Remove previously created join tables and persist the new ones
|
|
381
|
-
const assigneeJoinTablesToPersist = enabledRWContentTypes.map(getAssigneeTableToPersist);
|
|
382
|
-
await removePersistedTablesWithSuffix('_strapi_assignee_lnk');
|
|
383
|
-
await persistTables(assigneeJoinTablesToPersist);
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
var register = (async ({ strapi })=>{
|
|
387
|
-
// Data Migrations
|
|
388
|
-
strapi.hook('strapi::content-types.beforeSync').register(migrateStageAttribute);
|
|
389
|
-
strapi.hook('strapi::content-types.afterSync').register(persistRWOnDowngrade({
|
|
390
|
-
strapi
|
|
391
|
-
}));
|
|
392
|
-
strapi.hook('strapi::content-types.afterSync').register(migrateReviewWorkflowStagesColor).register(migrateReviewWorkflowStagesRoles).register(migrateReviewWorkflowName).register(migrateWorkflowsContentTypes).register(migrateDeletedCTInWorkflows);
|
|
393
|
-
// Middlewares
|
|
394
|
-
reviewWorkflowsMiddlewares.contentTypeMiddleware(strapi);
|
|
395
|
-
// Schema customization
|
|
396
|
-
extendReviewWorkflowContentTypes({
|
|
397
|
-
strapi
|
|
398
|
-
});
|
|
399
|
-
// License limits
|
|
400
|
-
const reviewWorkflowsOptions = defaultsDeep({
|
|
401
|
-
numberOfWorkflows: MAX_WORKFLOWS,
|
|
402
|
-
stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW
|
|
403
|
-
}, strapi.ee.features.get('review-workflows'));
|
|
404
|
-
const workflowsValidationService = getService('validation', {
|
|
405
|
-
strapi
|
|
406
|
-
});
|
|
407
|
-
workflowsValidationService.register(reviewWorkflowsOptions);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
var workflow = {
|
|
411
|
-
schema: {
|
|
412
|
-
collectionName: 'strapi_workflows',
|
|
413
|
-
info: {
|
|
414
|
-
name: 'Workflow',
|
|
415
|
-
description: '',
|
|
416
|
-
singularName: 'workflow',
|
|
417
|
-
pluralName: 'workflows',
|
|
418
|
-
displayName: 'Workflow'
|
|
419
|
-
},
|
|
420
|
-
options: {},
|
|
421
|
-
pluginOptions: {
|
|
422
|
-
'content-manager': {
|
|
423
|
-
visible: false
|
|
424
|
-
},
|
|
425
|
-
'content-type-builder': {
|
|
426
|
-
visible: false
|
|
427
|
-
}
|
|
428
|
-
},
|
|
429
|
-
attributes: {
|
|
430
|
-
name: {
|
|
431
|
-
type: 'string',
|
|
432
|
-
required: true,
|
|
433
|
-
unique: true
|
|
434
|
-
},
|
|
435
|
-
stages: {
|
|
436
|
-
type: 'relation',
|
|
437
|
-
target: 'plugin::review-workflows.workflow-stage',
|
|
438
|
-
relation: 'oneToMany',
|
|
439
|
-
mappedBy: 'workflow'
|
|
440
|
-
},
|
|
441
|
-
stageRequiredToPublish: {
|
|
442
|
-
type: 'relation',
|
|
443
|
-
target: 'plugin::review-workflows.workflow-stage',
|
|
444
|
-
relation: 'oneToOne',
|
|
445
|
-
required: false
|
|
446
|
-
},
|
|
447
|
-
contentTypes: {
|
|
448
|
-
type: 'json',
|
|
449
|
-
required: true,
|
|
450
|
-
default: '[]'
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
var workflowStage = {
|
|
457
|
-
schema: {
|
|
458
|
-
collectionName: 'strapi_workflows_stages',
|
|
459
|
-
info: {
|
|
460
|
-
name: 'Workflow Stage',
|
|
461
|
-
description: '',
|
|
462
|
-
singularName: 'workflow-stage',
|
|
463
|
-
pluralName: 'workflow-stages',
|
|
464
|
-
displayName: 'Stages'
|
|
465
|
-
},
|
|
466
|
-
options: {
|
|
467
|
-
version: '1.1.0'
|
|
468
|
-
},
|
|
469
|
-
pluginOptions: {
|
|
470
|
-
'content-manager': {
|
|
471
|
-
visible: false
|
|
472
|
-
},
|
|
473
|
-
'content-type-builder': {
|
|
474
|
-
visible: false
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
attributes: {
|
|
478
|
-
name: {
|
|
479
|
-
type: 'string',
|
|
480
|
-
configurable: false
|
|
481
|
-
},
|
|
482
|
-
color: {
|
|
483
|
-
type: 'string',
|
|
484
|
-
configurable: false,
|
|
485
|
-
default: STAGE_DEFAULT_COLOR
|
|
486
|
-
},
|
|
487
|
-
workflow: {
|
|
488
|
-
type: 'relation',
|
|
489
|
-
target: 'plugin::review-workflows.workflow',
|
|
490
|
-
relation: 'manyToOne',
|
|
491
|
-
inversedBy: 'stages',
|
|
492
|
-
configurable: false
|
|
493
|
-
},
|
|
494
|
-
permissions: {
|
|
495
|
-
type: 'relation',
|
|
496
|
-
target: 'admin::permission',
|
|
497
|
-
relation: 'manyToMany',
|
|
498
|
-
configurable: false
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
var contentTypes = {
|
|
505
|
-
workflow,
|
|
506
|
-
'workflow-stage': workflowStage
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
var actions = {
|
|
510
|
-
reviewWorkflows: [
|
|
511
|
-
{
|
|
512
|
-
uid: 'review-workflows.create',
|
|
513
|
-
displayName: 'Create',
|
|
514
|
-
pluginName: 'admin',
|
|
515
|
-
section: 'settings',
|
|
516
|
-
category: 'review workflows',
|
|
517
|
-
subCategory: 'options'
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
uid: 'review-workflows.read',
|
|
521
|
-
displayName: 'Read',
|
|
522
|
-
pluginName: 'admin',
|
|
523
|
-
section: 'settings',
|
|
524
|
-
category: 'review workflows',
|
|
525
|
-
subCategory: 'options'
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
uid: 'review-workflows.update',
|
|
529
|
-
displayName: 'Update',
|
|
530
|
-
pluginName: 'admin',
|
|
531
|
-
section: 'settings',
|
|
532
|
-
category: 'review workflows',
|
|
533
|
-
subCategory: 'options'
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
uid: 'review-workflows.delete',
|
|
537
|
-
displayName: 'Delete',
|
|
538
|
-
pluginName: 'admin',
|
|
539
|
-
section: 'settings',
|
|
540
|
-
category: 'review workflows',
|
|
541
|
-
subCategory: 'options'
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
uid: 'review-workflows.stage.transition',
|
|
545
|
-
displayName: 'Change stage',
|
|
546
|
-
pluginName: 'admin',
|
|
547
|
-
section: 'internal'
|
|
548
|
-
}
|
|
549
|
-
]
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
var defaultStages = [
|
|
553
|
-
{
|
|
554
|
-
name: "To do",
|
|
555
|
-
color: "#4945FF"
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
name: "Ready to review",
|
|
559
|
-
color: "#9736E8"
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
name: "In progress",
|
|
563
|
-
color: "#EE5E52"
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: "Reviewed",
|
|
567
|
-
color: "#328048"
|
|
568
|
-
}
|
|
569
|
-
];
|
|
570
|
-
|
|
571
|
-
const WORKFLOW_UPDATE_STAGE = 'review-workflows.updateEntryStage';
|
|
572
|
-
var webhookEvents = {
|
|
573
|
-
WORKFLOW_UPDATE_STAGE
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Initialize the default workflow if there is no workflow in the database
|
|
578
|
-
*/ async function initDefaultWorkflow() {
|
|
579
|
-
const workflowsService = getService('workflows', {
|
|
580
|
-
strapi
|
|
581
|
-
});
|
|
582
|
-
const stagesService = getService('stages', {
|
|
583
|
-
strapi
|
|
584
|
-
});
|
|
585
|
-
const wfCount = await workflowsService.count();
|
|
586
|
-
const stagesCount = await stagesService.count();
|
|
587
|
-
// Check if there is nothing about review-workflow in DB
|
|
588
|
-
// If any, the feature has already been initialized with a workflow and stages
|
|
589
|
-
if (wfCount === 0 && stagesCount === 0) {
|
|
590
|
-
const workflow = {
|
|
591
|
-
...defaultWorkflow,
|
|
592
|
-
contentTypes: [],
|
|
593
|
-
stages: defaultStages
|
|
594
|
-
};
|
|
595
|
-
await workflowsService.create({
|
|
596
|
-
data: workflow
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Webhook store limits the events that can be triggered,
|
|
602
|
-
* this function extends it with the events review workflows can trigger
|
|
603
|
-
*/ const registerWebhookEvents = async ()=>Object.entries(webhookEvents).forEach(([eventKey, event])=>strapi.get('webhookStore').addAllowedEvent(eventKey, event));
|
|
604
|
-
var bootstrap = (async (args)=>{
|
|
605
|
-
// Permissions
|
|
606
|
-
const { actionProvider } = getAdminService('permission');
|
|
607
|
-
await actionProvider.registerMany(actions.reviewWorkflows);
|
|
608
|
-
// Webhooks and events
|
|
609
|
-
await registerWebhookEvents();
|
|
610
|
-
await getService('workflow-weekly-metrics').registerCron();
|
|
611
|
-
// Data initialization
|
|
612
|
-
await initDefaultWorkflow();
|
|
613
|
-
// Document service middleware
|
|
614
|
-
const docsMiddlewares = getService('document-service-middlewares');
|
|
615
|
-
strapi.documents.use(docsMiddlewares.assignStageOnCreate);
|
|
616
|
-
strapi.documents.use(docsMiddlewares.handleStageOnUpdate);
|
|
617
|
-
strapi.documents.use(docsMiddlewares.checkStageBeforePublish);
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
var destroy = (async ({ strapi })=>{});
|
|
621
|
-
|
|
622
|
-
const enableFeatureMiddleware = (featureName)=>(ctx, next)=>{
|
|
623
|
-
if (strapi.ee.features.isEnabled(featureName)) {
|
|
624
|
-
return next();
|
|
625
|
-
}
|
|
626
|
-
ctx.status = 404;
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
var reviewWorkflows = {
|
|
630
|
-
type: 'admin',
|
|
631
|
-
routes: [
|
|
632
|
-
// Review workflow
|
|
633
|
-
{
|
|
634
|
-
method: 'POST',
|
|
635
|
-
path: '/workflows',
|
|
636
|
-
handler: 'workflows.create',
|
|
637
|
-
config: {
|
|
638
|
-
middlewares: [
|
|
639
|
-
enableFeatureMiddleware('review-workflows')
|
|
640
|
-
],
|
|
641
|
-
policies: [
|
|
642
|
-
'admin::isAuthenticatedAdmin',
|
|
643
|
-
{
|
|
644
|
-
name: 'admin::hasPermissions',
|
|
645
|
-
config: {
|
|
646
|
-
actions: [
|
|
647
|
-
'admin::review-workflows.create'
|
|
648
|
-
]
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
]
|
|
652
|
-
}
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
method: 'PUT',
|
|
656
|
-
path: '/workflows/:id',
|
|
657
|
-
handler: 'workflows.update',
|
|
658
|
-
config: {
|
|
659
|
-
middlewares: [
|
|
660
|
-
enableFeatureMiddleware('review-workflows')
|
|
661
|
-
],
|
|
662
|
-
policies: [
|
|
663
|
-
'admin::isAuthenticatedAdmin',
|
|
664
|
-
{
|
|
665
|
-
name: 'admin::hasPermissions',
|
|
666
|
-
config: {
|
|
667
|
-
actions: [
|
|
668
|
-
'admin::review-workflows.update'
|
|
669
|
-
]
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
]
|
|
673
|
-
}
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
method: 'DELETE',
|
|
677
|
-
path: '/workflows/:id',
|
|
678
|
-
handler: 'workflows.delete',
|
|
679
|
-
config: {
|
|
680
|
-
middlewares: [
|
|
681
|
-
enableFeatureMiddleware('review-workflows')
|
|
682
|
-
],
|
|
683
|
-
policies: [
|
|
684
|
-
'admin::isAuthenticatedAdmin',
|
|
685
|
-
{
|
|
686
|
-
name: 'admin::hasPermissions',
|
|
687
|
-
config: {
|
|
688
|
-
actions: [
|
|
689
|
-
'admin::review-workflows.delete'
|
|
690
|
-
]
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
]
|
|
694
|
-
}
|
|
695
|
-
},
|
|
696
|
-
{
|
|
697
|
-
method: 'GET',
|
|
698
|
-
path: '/workflows',
|
|
699
|
-
handler: 'workflows.find',
|
|
700
|
-
config: {
|
|
701
|
-
middlewares: [
|
|
702
|
-
enableFeatureMiddleware('review-workflows')
|
|
703
|
-
],
|
|
704
|
-
policies: [
|
|
705
|
-
'admin::isAuthenticatedAdmin',
|
|
706
|
-
{
|
|
707
|
-
name: 'admin::hasPermissions',
|
|
708
|
-
config: {
|
|
709
|
-
actions: [
|
|
710
|
-
'admin::review-workflows.read'
|
|
711
|
-
]
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
]
|
|
715
|
-
}
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
method: 'GET',
|
|
719
|
-
path: '/workflows/:workflow_id/stages',
|
|
720
|
-
handler: 'stages.find',
|
|
721
|
-
config: {
|
|
722
|
-
middlewares: [
|
|
723
|
-
enableFeatureMiddleware('review-workflows')
|
|
724
|
-
],
|
|
725
|
-
policies: [
|
|
726
|
-
'admin::isAuthenticatedAdmin',
|
|
727
|
-
{
|
|
728
|
-
name: 'admin::hasPermissions',
|
|
729
|
-
config: {
|
|
730
|
-
actions: [
|
|
731
|
-
'admin::review-workflows.read'
|
|
732
|
-
]
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
]
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
-
{
|
|
739
|
-
method: 'GET',
|
|
740
|
-
path: '/workflows/:workflow_id/stages/:id',
|
|
741
|
-
handler: 'stages.findById',
|
|
742
|
-
config: {
|
|
743
|
-
middlewares: [
|
|
744
|
-
enableFeatureMiddleware('review-workflows')
|
|
745
|
-
],
|
|
746
|
-
policies: [
|
|
747
|
-
'admin::isAuthenticatedAdmin',
|
|
748
|
-
{
|
|
749
|
-
name: 'admin::hasPermissions',
|
|
750
|
-
config: {
|
|
751
|
-
actions: [
|
|
752
|
-
'admin::review-workflows.read'
|
|
753
|
-
]
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
]
|
|
757
|
-
}
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
method: 'PUT',
|
|
761
|
-
path: '/content-manager/(collection|single)-types/:model_uid/:id/stage',
|
|
762
|
-
handler: 'stages.updateEntity',
|
|
763
|
-
config: {
|
|
764
|
-
middlewares: [
|
|
765
|
-
enableFeatureMiddleware('review-workflows')
|
|
766
|
-
],
|
|
767
|
-
policies: [
|
|
768
|
-
'admin::isAuthenticatedAdmin'
|
|
769
|
-
]
|
|
770
|
-
}
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
method: 'GET',
|
|
774
|
-
path: '/content-manager/(collection|single)-types/:model_uid/:id/stages',
|
|
775
|
-
handler: 'stages.listAvailableStages',
|
|
776
|
-
config: {
|
|
777
|
-
middlewares: [
|
|
778
|
-
enableFeatureMiddleware('review-workflows')
|
|
779
|
-
],
|
|
780
|
-
policies: [
|
|
781
|
-
'admin::isAuthenticatedAdmin'
|
|
782
|
-
]
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
method: 'PUT',
|
|
787
|
-
path: '/content-manager/(collection|single)-types/:model_uid/:id/assignee',
|
|
788
|
-
handler: 'assignees.updateEntity',
|
|
789
|
-
config: {
|
|
790
|
-
middlewares: [
|
|
791
|
-
enableFeatureMiddleware('review-workflows')
|
|
792
|
-
],
|
|
793
|
-
policies: [
|
|
794
|
-
'admin::isAuthenticatedAdmin',
|
|
795
|
-
{
|
|
796
|
-
name: 'admin::hasPermissions',
|
|
797
|
-
config: {
|
|
798
|
-
actions: [
|
|
799
|
-
'admin::users.read'
|
|
800
|
-
]
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
]
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
]
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
var routes = {
|
|
810
|
-
'review-workflows': reviewWorkflows
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
var workflowsContentTypesFactory = (({ strapi })=>{
|
|
814
|
-
const contentManagerContentTypeService = strapi.plugin('content-manager').service('content-types');
|
|
815
|
-
const stagesService = getService('stages', {
|
|
816
|
-
strapi
|
|
817
|
-
});
|
|
818
|
-
const updateContentTypeConfig = async (uid, reviewWorkflowOption)=>{
|
|
819
|
-
// Merge options in the configuration as the configuration service use a destructuration merge which doesn't include nested objects
|
|
820
|
-
const modelConfig = await contentManagerContentTypeService.findConfiguration(uid);
|
|
821
|
-
await contentManagerContentTypeService.updateConfiguration({
|
|
822
|
-
uid
|
|
823
|
-
}, {
|
|
824
|
-
options: merge(modelConfig.options, {
|
|
825
|
-
reviewWorkflows: reviewWorkflowOption
|
|
826
|
-
})
|
|
827
|
-
});
|
|
828
|
-
};
|
|
829
|
-
return {
|
|
830
|
-
/**
|
|
831
|
-
* Migrates entities stages. Used when a content type is assigned to a workflow.
|
|
832
|
-
* @param {*} options
|
|
833
|
-
* @param {Array<string>} options.srcContentTypes - The content types assigned to the previous workflow
|
|
834
|
-
* @param {Array<string>} options.destContentTypes - The content types assigned to the new workflow
|
|
835
|
-
* @param {Workflow.Stage} options.stageId - The new stage to assign the entities to
|
|
836
|
-
*/ async migrate ({ srcContentTypes = [], destContentTypes, stageId }) {
|
|
837
|
-
const workflowsService = getService('workflows', {
|
|
838
|
-
strapi
|
|
839
|
-
});
|
|
840
|
-
const { created, deleted } = diffContentTypes(srcContentTypes, destContentTypes);
|
|
841
|
-
await async.map(created, async (uid)=>{
|
|
842
|
-
// Content Types should only be assigned to one workflow
|
|
843
|
-
// However, edge cases can happen, and this handles them
|
|
844
|
-
const srcWorkflows = await workflowsService._getAssignedWorkflows(uid, {});
|
|
845
|
-
if (srcWorkflows.length) {
|
|
846
|
-
// Updates all existing entities stages links to the new stage
|
|
847
|
-
await stagesService.updateEntitiesStage(uid, {
|
|
848
|
-
toStageId: stageId
|
|
849
|
-
});
|
|
850
|
-
// Transfer content types from the previous workflow(s)
|
|
851
|
-
await async.map(srcWorkflows, (srcWorkflow)=>this.transferContentTypes(srcWorkflow, uid));
|
|
852
|
-
}
|
|
853
|
-
await updateContentTypeConfig(uid, true);
|
|
854
|
-
// Create new stages links to the new stage
|
|
855
|
-
return stagesService.updateEntitiesStage(uid, {
|
|
856
|
-
fromStageId: null,
|
|
857
|
-
toStageId: stageId
|
|
858
|
-
});
|
|
859
|
-
}, // transferContentTypes can cause race conditions if called in parallel when updating the same workflow
|
|
860
|
-
{
|
|
861
|
-
concurrency: 1
|
|
862
|
-
});
|
|
863
|
-
await async.map(deleted, async (uid)=>{
|
|
864
|
-
await updateContentTypeConfig(uid, false);
|
|
865
|
-
await stagesService.deleteAllEntitiesStage(uid, {});
|
|
866
|
-
});
|
|
867
|
-
},
|
|
868
|
-
/**
|
|
869
|
-
* Filters the content types assigned to a workflow
|
|
870
|
-
* @param {Workflow} srcWorkflow - The workflow to transfer from
|
|
871
|
-
* @param {string} uid - The content type uid
|
|
872
|
-
*/ async transferContentTypes (srcWorkflow, uid) {
|
|
873
|
-
// Update assignedContentTypes of the previous workflow
|
|
874
|
-
await strapi.db.query(WORKFLOW_MODEL_UID).update({
|
|
875
|
-
where: {
|
|
876
|
-
id: srcWorkflow.id
|
|
877
|
-
},
|
|
878
|
-
data: {
|
|
879
|
-
contentTypes: srcWorkflow.contentTypes.filter((contentType)=>contentType !== uid)
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
});
|
|
885
|
-
const diffContentTypes = (srcContentTypes, destContentTypes)=>{
|
|
886
|
-
const created = difference(destContentTypes, srcContentTypes);
|
|
887
|
-
const deleted = difference(srcContentTypes, destContentTypes);
|
|
888
|
-
return {
|
|
889
|
-
created,
|
|
890
|
-
deleted
|
|
891
|
-
};
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
const processFilters = ({ strapi }, filters = {})=>{
|
|
895
|
-
const processedFilters = {
|
|
896
|
-
...filters
|
|
897
|
-
};
|
|
898
|
-
if (isString(filters.contentTypes)) {
|
|
899
|
-
processedFilters.contentTypes = getWorkflowContentTypeFilter({
|
|
900
|
-
strapi
|
|
901
|
-
}, filters.contentTypes);
|
|
902
|
-
}
|
|
903
|
-
return processedFilters;
|
|
904
|
-
};
|
|
905
|
-
// TODO: How can we improve this? Maybe using traversePopulate?
|
|
906
|
-
const processPopulate = (populate)=>{
|
|
907
|
-
// If it does not exist or it's not an object (like an array) return the default populate
|
|
908
|
-
if (!populate) {
|
|
909
|
-
return WORKFLOW_POPULATE;
|
|
910
|
-
}
|
|
911
|
-
return populate;
|
|
912
|
-
};
|
|
913
|
-
var workflows$1 = (({ strapi })=>{
|
|
914
|
-
const workflowsContentTypes = workflowsContentTypesFactory({
|
|
915
|
-
strapi
|
|
916
|
-
});
|
|
917
|
-
const workflowValidator = getService('validation', {
|
|
918
|
-
strapi
|
|
919
|
-
});
|
|
920
|
-
const metrics = getService('workflow-metrics', {
|
|
921
|
-
strapi
|
|
922
|
-
});
|
|
923
|
-
return {
|
|
924
|
-
/**
|
|
925
|
-
* Returns all the workflows matching the user-defined filters.
|
|
926
|
-
* @param {object} opts - Options for the query.
|
|
927
|
-
* @param {object} opts.filters - Filters object.
|
|
928
|
-
* @returns {Promise<object[]>} - List of workflows that match the user's filters.
|
|
929
|
-
*/ async find (opts = {}) {
|
|
930
|
-
const filters = processFilters({
|
|
931
|
-
strapi
|
|
932
|
-
}, opts.filters);
|
|
933
|
-
const populate = processPopulate(opts.populate);
|
|
934
|
-
const query = strapi.get('query-params').transform(WORKFLOW_MODEL_UID, {
|
|
935
|
-
...opts,
|
|
936
|
-
filters,
|
|
937
|
-
populate
|
|
938
|
-
});
|
|
939
|
-
return strapi.db.query(WORKFLOW_MODEL_UID).findMany(query);
|
|
940
|
-
},
|
|
941
|
-
/**
|
|
942
|
-
* Returns the workflow with the specified ID.
|
|
943
|
-
* @param {string} id - ID of the requested workflow.
|
|
944
|
-
* @param {object} opts - Options for the query.
|
|
945
|
-
* @returns {Promise<object>} - Workflow object matching the requested ID.
|
|
946
|
-
*/ findById (id, opts = {}) {
|
|
947
|
-
const populate = processPopulate(opts.populate);
|
|
948
|
-
const query = strapi.get('query-params').transform(WORKFLOW_MODEL_UID, {
|
|
949
|
-
populate
|
|
950
|
-
});
|
|
951
|
-
return strapi.db.query(WORKFLOW_MODEL_UID).findOne({
|
|
952
|
-
...query,
|
|
953
|
-
where: {
|
|
954
|
-
id
|
|
955
|
-
}
|
|
956
|
-
});
|
|
957
|
-
},
|
|
958
|
-
/**
|
|
959
|
-
* Creates a new workflow.
|
|
960
|
-
* @param {object} opts - Options for creating the new workflow.
|
|
961
|
-
* @returns {Promise<object>} - Workflow object that was just created.
|
|
962
|
-
* @throws {ValidationError} - If the workflow has no stages.
|
|
963
|
-
*/ async create (opts) {
|
|
964
|
-
let createOpts = {
|
|
965
|
-
...opts,
|
|
966
|
-
populate: WORKFLOW_POPULATE
|
|
967
|
-
};
|
|
968
|
-
workflowValidator.validateWorkflowStages(opts.data.stages);
|
|
969
|
-
await workflowValidator.validateWorkflowCount(1);
|
|
970
|
-
return strapi.db.transaction(async ()=>{
|
|
971
|
-
// Create stages
|
|
972
|
-
const stages = await getService('stages', {
|
|
973
|
-
strapi
|
|
974
|
-
}).createMany(opts.data.stages);
|
|
975
|
-
const mapIds = map(get('id'));
|
|
976
|
-
createOpts = set('data.stages', mapIds(stages), createOpts);
|
|
977
|
-
if (opts.data.stageRequiredToPublishName) {
|
|
978
|
-
const stageRequiredToPublish = stages.find((stage)=>stage.name === opts.data.stageRequiredToPublishName);
|
|
979
|
-
if (!stageRequiredToPublish) {
|
|
980
|
-
throw new errors.ApplicationError('Stage required to publish does not exist');
|
|
981
|
-
}
|
|
982
|
-
createOpts = set('data.stageRequiredToPublish', stageRequiredToPublish.id, createOpts);
|
|
983
|
-
}
|
|
984
|
-
// Update (un)assigned Content Types
|
|
985
|
-
if (opts.data.contentTypes) {
|
|
986
|
-
await workflowsContentTypes.migrate({
|
|
987
|
-
destContentTypes: opts.data.contentTypes,
|
|
988
|
-
stageId: stages[0].id
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
// Create Workflow
|
|
992
|
-
const createdWorkflow = await strapi.db.query(WORKFLOW_MODEL_UID).create(strapi.get('query-params').transform(WORKFLOW_MODEL_UID, createOpts));
|
|
993
|
-
metrics.sendDidCreateWorkflow(createdWorkflow.id, !!opts.data.stageRequiredToPublishName);
|
|
994
|
-
if (opts.data.stageRequiredToPublishName) {
|
|
995
|
-
await strapi.plugin('content-releases').service('release-action').validateActionsByContentTypes(opts.data.contentTypes);
|
|
996
|
-
}
|
|
997
|
-
return createdWorkflow;
|
|
998
|
-
});
|
|
999
|
-
},
|
|
1000
|
-
/**
|
|
1001
|
-
* Updates an existing workflow.
|
|
1002
|
-
* @param {object} workflow - The existing workflow to update.
|
|
1003
|
-
* @param {object} opts - Options for updating the workflow.
|
|
1004
|
-
* @returns {Promise<object>} - Workflow object that was just updated.
|
|
1005
|
-
* @throws {ApplicationError} - If the supplied stage ID does not belong to the workflow.
|
|
1006
|
-
*/ async update (workflow, opts) {
|
|
1007
|
-
const stageService = getService('stages', {
|
|
1008
|
-
strapi
|
|
1009
|
-
});
|
|
1010
|
-
let updateOpts = {
|
|
1011
|
-
...opts,
|
|
1012
|
-
populate: {
|
|
1013
|
-
...WORKFLOW_POPULATE
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
let updatedStages = [];
|
|
1017
|
-
let updatedStageIds;
|
|
1018
|
-
await workflowValidator.validateWorkflowCount();
|
|
1019
|
-
return strapi.db.transaction(async ()=>{
|
|
1020
|
-
// Update stages
|
|
1021
|
-
if (opts.data.stages) {
|
|
1022
|
-
workflowValidator.validateWorkflowStages(opts.data.stages);
|
|
1023
|
-
opts.data.stages.forEach((stage)=>this.assertStageBelongsToWorkflow(stage.id, workflow));
|
|
1024
|
-
updatedStages = await stageService.replaceStages(workflow.stages, opts.data.stages, workflow.contentTypes);
|
|
1025
|
-
updatedStageIds = updatedStages.map((stage)=>stage.id);
|
|
1026
|
-
updateOpts = set('data.stages', updatedStageIds, updateOpts);
|
|
1027
|
-
}
|
|
1028
|
-
if (opts.data.stageRequiredToPublishName !== undefined) {
|
|
1029
|
-
const stages = updatedStages ?? workflow.stages;
|
|
1030
|
-
if (opts.data.stageRequiredToPublishName === null) {
|
|
1031
|
-
updateOpts = set('data.stageRequiredToPublish', null, updateOpts);
|
|
1032
|
-
} else {
|
|
1033
|
-
const stageRequiredToPublish = stages.find((stage)=>stage.name === opts.data.stageRequiredToPublishName);
|
|
1034
|
-
if (!stageRequiredToPublish) {
|
|
1035
|
-
throw new errors.ApplicationError('Stage required to publish does not exist');
|
|
1036
|
-
}
|
|
1037
|
-
updateOpts = set('data.stageRequiredToPublish', stageRequiredToPublish.id, updateOpts);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
// Update (un)assigned Content Types
|
|
1041
|
-
if (opts.data.contentTypes) {
|
|
1042
|
-
await workflowsContentTypes.migrate({
|
|
1043
|
-
srcContentTypes: workflow.contentTypes,
|
|
1044
|
-
destContentTypes: opts.data.contentTypes,
|
|
1045
|
-
stageId: updatedStageIds ? updatedStageIds[0] : workflow.stages[0].id
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
metrics.sendDidEditWorkflow(workflow.id, !!opts.data.stageRequiredToPublishName);
|
|
1049
|
-
const query = strapi.get('query-params').transform(WORKFLOW_MODEL_UID, updateOpts);
|
|
1050
|
-
// Update Workflow
|
|
1051
|
-
const updatedWorkflow = await strapi.db.query(WORKFLOW_MODEL_UID).update({
|
|
1052
|
-
...query,
|
|
1053
|
-
where: {
|
|
1054
|
-
id: workflow.id
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
await strapi.plugin('content-releases').service('release-action').validateActionsByContentTypes([
|
|
1058
|
-
...workflow.contentTypes,
|
|
1059
|
-
...opts.data.contentTypes || []
|
|
1060
|
-
]);
|
|
1061
|
-
return updatedWorkflow;
|
|
1062
|
-
});
|
|
1063
|
-
},
|
|
1064
|
-
/**
|
|
1065
|
-
* Deletes an existing workflow.
|
|
1066
|
-
* Also deletes all the workflow stages and migrate all assigned the content types.
|
|
1067
|
-
* @param {*} workflow
|
|
1068
|
-
* @param {*} opts
|
|
1069
|
-
* @returns
|
|
1070
|
-
*/ async delete (workflow, opts) {
|
|
1071
|
-
const stageService = getService('stages', {
|
|
1072
|
-
strapi
|
|
1073
|
-
});
|
|
1074
|
-
const workflowCount = await this.count();
|
|
1075
|
-
if (workflowCount <= 1) {
|
|
1076
|
-
throw new errors.ApplicationError('Can not delete the last workflow');
|
|
1077
|
-
}
|
|
1078
|
-
return strapi.db.transaction(async ()=>{
|
|
1079
|
-
// Delete stages
|
|
1080
|
-
await stageService.deleteMany(workflow.stages);
|
|
1081
|
-
// Unassign all content types, this will migrate the content types to null
|
|
1082
|
-
await workflowsContentTypes.migrate({
|
|
1083
|
-
srcContentTypes: workflow.contentTypes,
|
|
1084
|
-
destContentTypes: []
|
|
1085
|
-
});
|
|
1086
|
-
const query = strapi.get('query-params').transform(WORKFLOW_MODEL_UID, opts);
|
|
1087
|
-
// Delete Workflow
|
|
1088
|
-
const deletedWorkflow = await strapi.db.query(WORKFLOW_MODEL_UID).delete({
|
|
1089
|
-
...query,
|
|
1090
|
-
where: {
|
|
1091
|
-
id: workflow.id
|
|
1092
|
-
}
|
|
1093
|
-
});
|
|
1094
|
-
await strapi.plugin('content-releases').service('release-action').validateActionsByContentTypes(workflow.contentTypes);
|
|
1095
|
-
return deletedWorkflow;
|
|
1096
|
-
});
|
|
1097
|
-
},
|
|
1098
|
-
/**
|
|
1099
|
-
* Returns the total count of workflows.
|
|
1100
|
-
* @returns {Promise<number>} - Total count of workflows.
|
|
1101
|
-
*/ count () {
|
|
1102
|
-
return strapi.db.query(WORKFLOW_MODEL_UID).count();
|
|
1103
|
-
},
|
|
1104
|
-
/**
|
|
1105
|
-
* Finds the assigned workflow for a given content type ID.
|
|
1106
|
-
* @param {string} uid - Content type ID to find the assigned workflow for.
|
|
1107
|
-
* @param {object} opts - Options for the query.
|
|
1108
|
-
* @returns {Promise<object|null>} - Assigned workflow object if found, or null.
|
|
1109
|
-
*/ async getAssignedWorkflow (uid, opts = {}) {
|
|
1110
|
-
const workflows = await this._getAssignedWorkflows(uid, opts);
|
|
1111
|
-
return workflows.length > 0 ? workflows[0] : null;
|
|
1112
|
-
},
|
|
1113
|
-
/**
|
|
1114
|
-
* Finds all the assigned workflows for a given content type ID.
|
|
1115
|
-
* Normally, there should only be one workflow assigned to a content type.
|
|
1116
|
-
* However, edge cases can occur where a content type is assigned to multiple workflows.
|
|
1117
|
-
* @param {string} uid - Content type ID to find the assigned workflows for.
|
|
1118
|
-
* @param {object} opts - Options for the query.
|
|
1119
|
-
* @returns {Promise<object[]>} - List of assigned workflow objects.
|
|
1120
|
-
*/ async _getAssignedWorkflows (uid, opts = {}) {
|
|
1121
|
-
return this.find({
|
|
1122
|
-
...opts,
|
|
1123
|
-
filters: {
|
|
1124
|
-
contentTypes: getWorkflowContentTypeFilter({
|
|
1125
|
-
strapi
|
|
1126
|
-
}, uid)
|
|
1127
|
-
}
|
|
1128
|
-
});
|
|
1129
|
-
},
|
|
1130
|
-
/**
|
|
1131
|
-
* Asserts that a content type has an assigned workflow.
|
|
1132
|
-
* @param {string} uid - Content type ID to verify the assignment of.
|
|
1133
|
-
* @returns {Promise<object>} - Workflow object associated with the content type ID.
|
|
1134
|
-
* @throws {ApplicationError} - If no assigned workflow is found for the content type ID.
|
|
1135
|
-
*/ async assertContentTypeBelongsToWorkflow (uid) {
|
|
1136
|
-
const workflow = await this.getAssignedWorkflow(uid, {
|
|
1137
|
-
populate: 'stages'
|
|
1138
|
-
});
|
|
1139
|
-
if (!workflow) {
|
|
1140
|
-
throw new errors.ApplicationError(`Review workflows is not activated on Content Type ${uid}.`);
|
|
1141
|
-
}
|
|
1142
|
-
return workflow;
|
|
1143
|
-
},
|
|
1144
|
-
/**
|
|
1145
|
-
* Asserts that a stage belongs to a given workflow.
|
|
1146
|
-
* @param {string} stageId - ID of stage to check.
|
|
1147
|
-
* @param {object} workflow - Workflow object to check against.
|
|
1148
|
-
* @returns
|
|
1149
|
-
* @throws {ApplicationError} - If the stage does not belong to the specified workflow.
|
|
1150
|
-
*/ assertStageBelongsToWorkflow (stageId, workflow) {
|
|
1151
|
-
if (!stageId) {
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
const belongs = workflow.stages.some((stage)=>stage.id === stageId);
|
|
1155
|
-
if (!belongs) {
|
|
1156
|
-
throw new errors.ApplicationError(`Stage does not belong to workflow "${workflow.name}"`);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
};
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
const { ApplicationError: ApplicationError$2, ValidationError: ValidationError$1 } = errors;
|
|
1163
|
-
const sanitizedStageFields = [
|
|
1164
|
-
'id',
|
|
1165
|
-
'name',
|
|
1166
|
-
'workflow',
|
|
1167
|
-
'color'
|
|
1168
|
-
];
|
|
1169
|
-
const sanitizeStageFields = pick(sanitizedStageFields);
|
|
1170
|
-
var stages$1 = (({ strapi })=>{
|
|
1171
|
-
const metrics = getService('workflow-metrics', {
|
|
1172
|
-
strapi
|
|
1173
|
-
});
|
|
1174
|
-
const stagePermissionsService = getService('stage-permissions', {
|
|
1175
|
-
strapi
|
|
1176
|
-
});
|
|
1177
|
-
const workflowValidator = getService('validation', {
|
|
1178
|
-
strapi
|
|
1179
|
-
});
|
|
1180
|
-
return {
|
|
1181
|
-
find ({ workflowId, populate }) {
|
|
1182
|
-
return strapi.db.query(STAGE_MODEL_UID).findMany({
|
|
1183
|
-
where: {
|
|
1184
|
-
workflow: workflowId
|
|
1185
|
-
},
|
|
1186
|
-
populate
|
|
1187
|
-
});
|
|
1188
|
-
},
|
|
1189
|
-
findById (id, { populate } = {}) {
|
|
1190
|
-
return strapi.db.query(STAGE_MODEL_UID).findOne({
|
|
1191
|
-
where: {
|
|
1192
|
-
id
|
|
1193
|
-
},
|
|
1194
|
-
populate
|
|
1195
|
-
});
|
|
1196
|
-
},
|
|
1197
|
-
async createMany (stagesList, { fields } = {}) {
|
|
1198
|
-
const params = {
|
|
1199
|
-
select: fields ?? '*'
|
|
1200
|
-
};
|
|
1201
|
-
const stages = await Promise.all(stagesList.map((stage)=>strapi.db.query(STAGE_MODEL_UID).create({
|
|
1202
|
-
data: sanitizeStageFields(stage),
|
|
1203
|
-
...params
|
|
1204
|
-
})));
|
|
1205
|
-
// Create stage permissions
|
|
1206
|
-
await async.reduce(stagesList)(async (_, stage, idx)=>{
|
|
1207
|
-
// Ignore stages without permissions
|
|
1208
|
-
if (!stage.permissions || stage.permissions.length === 0) {
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
const stagePermissions = stage.permissions;
|
|
1212
|
-
const stageId = stages[idx].id;
|
|
1213
|
-
const permissions = await async.map(stagePermissions, // Register each stage permission
|
|
1214
|
-
(permission)=>stagePermissionsService.register({
|
|
1215
|
-
roleId: permission.role,
|
|
1216
|
-
action: permission.action,
|
|
1217
|
-
fromStage: stageId
|
|
1218
|
-
}));
|
|
1219
|
-
// Update stage with the new permissions
|
|
1220
|
-
await strapi.db.query(STAGE_MODEL_UID).update({
|
|
1221
|
-
where: {
|
|
1222
|
-
id: stageId
|
|
1223
|
-
},
|
|
1224
|
-
data: {
|
|
1225
|
-
permissions: permissions.flat().map((p)=>p.id)
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
|
-
}, []);
|
|
1229
|
-
metrics.sendDidCreateStage();
|
|
1230
|
-
return stages;
|
|
1231
|
-
},
|
|
1232
|
-
async update (srcStage, destStage) {
|
|
1233
|
-
let stagePermissions = srcStage?.permissions ?? [];
|
|
1234
|
-
const stageId = destStage.id;
|
|
1235
|
-
if (destStage.permissions) {
|
|
1236
|
-
await this.deleteStagePermissions([
|
|
1237
|
-
srcStage
|
|
1238
|
-
]);
|
|
1239
|
-
const permissions = await async.map(destStage.permissions, (permission)=>stagePermissionsService.register({
|
|
1240
|
-
roleId: permission.role,
|
|
1241
|
-
action: permission.action,
|
|
1242
|
-
fromStage: stageId
|
|
1243
|
-
}));
|
|
1244
|
-
stagePermissions = permissions.flat().map((p)=>p.id);
|
|
1245
|
-
}
|
|
1246
|
-
const stage = await strapi.db.query(STAGE_MODEL_UID).update({
|
|
1247
|
-
where: {
|
|
1248
|
-
id: stageId
|
|
1249
|
-
},
|
|
1250
|
-
data: {
|
|
1251
|
-
...destStage,
|
|
1252
|
-
permissions: stagePermissions
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
metrics.sendDidEditStage();
|
|
1256
|
-
return stage;
|
|
1257
|
-
},
|
|
1258
|
-
async delete (stage) {
|
|
1259
|
-
// Unregister all permissions related to this stage id
|
|
1260
|
-
await this.deleteStagePermissions([
|
|
1261
|
-
stage
|
|
1262
|
-
]);
|
|
1263
|
-
const deletedStage = await strapi.db.query(STAGE_MODEL_UID).delete({
|
|
1264
|
-
where: {
|
|
1265
|
-
id: stage.id
|
|
1266
|
-
}
|
|
1267
|
-
});
|
|
1268
|
-
metrics.sendDidDeleteStage();
|
|
1269
|
-
return deletedStage;
|
|
1270
|
-
},
|
|
1271
|
-
async deleteMany (stages) {
|
|
1272
|
-
await this.deleteStagePermissions(stages);
|
|
1273
|
-
return strapi.db.query(STAGE_MODEL_UID).deleteMany({
|
|
1274
|
-
where: {
|
|
1275
|
-
id: {
|
|
1276
|
-
$in: stages.map((s)=>s.id)
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
},
|
|
1281
|
-
async deleteStagePermissions (stages) {
|
|
1282
|
-
// TODO: Find another way to do this for when we use the "to" parameter.
|
|
1283
|
-
const permissions = stages.map((s)=>s.permissions || []).flat();
|
|
1284
|
-
await stagePermissionsService.unregister(permissions || []);
|
|
1285
|
-
},
|
|
1286
|
-
count ({ workflowId } = {}) {
|
|
1287
|
-
const opts = {};
|
|
1288
|
-
if (workflowId) {
|
|
1289
|
-
opts.where = {
|
|
1290
|
-
workflow: workflowId
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
return strapi.db.query(STAGE_MODEL_UID).count(opts);
|
|
1294
|
-
},
|
|
1295
|
-
async replaceStages (srcStages, destStages, contentTypesToMigrate = []) {
|
|
1296
|
-
const { created, updated, deleted } = getDiffBetweenStages(srcStages, destStages);
|
|
1297
|
-
assertAtLeastOneStageRemain(srcStages || [], {
|
|
1298
|
-
created,
|
|
1299
|
-
deleted
|
|
1300
|
-
});
|
|
1301
|
-
// Update stages and assign entity stages
|
|
1302
|
-
return strapi.db.transaction(async ({ trx })=>{
|
|
1303
|
-
// Create the new stages
|
|
1304
|
-
const createdStages = await this.createMany(created, {
|
|
1305
|
-
fields: [
|
|
1306
|
-
'id'
|
|
1307
|
-
]
|
|
1308
|
-
});
|
|
1309
|
-
// Put all the newly created stages ids
|
|
1310
|
-
const createdStagesIds = map('id', createdStages);
|
|
1311
|
-
// Update the workflow stages
|
|
1312
|
-
await async.map(updated, (destStage)=>{
|
|
1313
|
-
const srcStage = srcStages.find((s)=>s.id === destStage.id);
|
|
1314
|
-
return this.update(srcStage, destStage);
|
|
1315
|
-
});
|
|
1316
|
-
// Delete the stages that are not in the new stages list
|
|
1317
|
-
await async.map(deleted, async (stage)=>{
|
|
1318
|
-
// Find the nearest stage in the workflow and newly created stages
|
|
1319
|
-
// that is not deleted, prioritizing the previous stages
|
|
1320
|
-
const nearestStage = findNearestMatchingStage([
|
|
1321
|
-
...srcStages,
|
|
1322
|
-
...createdStages
|
|
1323
|
-
], srcStages.findIndex((s)=>s.id === stage.id), (targetStage)=>{
|
|
1324
|
-
return !deleted.find((s)=>s.id === targetStage.id);
|
|
1325
|
-
});
|
|
1326
|
-
// Assign the new stage to entities that had the deleted stage
|
|
1327
|
-
await async.map(contentTypesToMigrate, (contentTypeUID)=>{
|
|
1328
|
-
this.updateEntitiesStage(contentTypeUID, {
|
|
1329
|
-
fromStageId: stage.id,
|
|
1330
|
-
toStageId: nearestStage.id,
|
|
1331
|
-
trx
|
|
1332
|
-
});
|
|
1333
|
-
});
|
|
1334
|
-
return this.delete(stage);
|
|
1335
|
-
});
|
|
1336
|
-
return destStages.map((stage)=>({
|
|
1337
|
-
...stage,
|
|
1338
|
-
id: stage.id ?? createdStagesIds.shift()
|
|
1339
|
-
}));
|
|
1340
|
-
});
|
|
1341
|
-
},
|
|
1342
|
-
/**
|
|
1343
|
-
* Update the stage of an entity
|
|
1344
|
-
*/ async updateEntity (entityToUpdate, model, stageId) {
|
|
1345
|
-
const stage = await this.findById(stageId);
|
|
1346
|
-
const { documentId, locale } = entityToUpdate;
|
|
1347
|
-
await workflowValidator.validateWorkflowCount();
|
|
1348
|
-
if (!stage) {
|
|
1349
|
-
throw new ApplicationError$2(`Selected stage does not exist`);
|
|
1350
|
-
}
|
|
1351
|
-
const entity = await strapi.documents(model).update({
|
|
1352
|
-
documentId,
|
|
1353
|
-
locale,
|
|
1354
|
-
// Stage doesn't have DP or i18n enabled, connecting it through the `id`
|
|
1355
|
-
// will be safer than relying on the `documentId` + `locale` + `status` transformation
|
|
1356
|
-
data: {
|
|
1357
|
-
[ENTITY_STAGE_ATTRIBUTE]: pick([
|
|
1358
|
-
'id'
|
|
1359
|
-
], stage)
|
|
1360
|
-
},
|
|
1361
|
-
populate: [
|
|
1362
|
-
ENTITY_STAGE_ATTRIBUTE
|
|
1363
|
-
]
|
|
1364
|
-
});
|
|
1365
|
-
// Update the `updated_at` field of the entity, so that the `status` is not considered `Modified`
|
|
1366
|
-
// NOTE: `updatedAt` is a protected attribute that can not be modified directly from the query layer
|
|
1367
|
-
// hence the knex query builder is used here.
|
|
1368
|
-
const { tableName } = strapi.db.metadata.get(model);
|
|
1369
|
-
await strapi.db.connection(tableName).where({
|
|
1370
|
-
id: entityToUpdate.id
|
|
1371
|
-
}).update({
|
|
1372
|
-
updated_at: new Date(entityToUpdate.updatedAt)
|
|
1373
|
-
});
|
|
1374
|
-
metrics.sendDidChangeEntryStage();
|
|
1375
|
-
return entity;
|
|
1376
|
-
},
|
|
1377
|
-
/**
|
|
1378
|
-
* Updates entity stages of a content type:
|
|
1379
|
-
* - If fromStageId is undefined, all entities with an existing stage will be assigned the new stage
|
|
1380
|
-
* - If fromStageId is null, all entities without a stage will be assigned the new stage
|
|
1381
|
-
* - If fromStageId is a number, all entities with that stage will be assigned the new stage
|
|
1382
|
-
*
|
|
1383
|
-
* For performance reasons we use knex queries directly.
|
|
1384
|
-
*
|
|
1385
|
-
* @param {string} contentTypeUID
|
|
1386
|
-
* @param {number | undefined | null} fromStageId
|
|
1387
|
-
* @param {number} toStageId
|
|
1388
|
-
* @param {import('knex').Knex.Transaction} trx
|
|
1389
|
-
* @returns
|
|
1390
|
-
*/ async updateEntitiesStage (contentTypeUID, { fromStageId, toStageId }) {
|
|
1391
|
-
const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
|
|
1392
|
-
const joinTable = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable;
|
|
1393
|
-
const joinColumn = joinTable.joinColumn.name;
|
|
1394
|
-
const invJoinColumn = joinTable.inverseJoinColumn.name;
|
|
1395
|
-
await workflowValidator.validateWorkflowCount();
|
|
1396
|
-
return strapi.db.transaction(async ({ trx })=>{
|
|
1397
|
-
// Update all already existing links to the new stage
|
|
1398
|
-
if (fromStageId === undefined) {
|
|
1399
|
-
return strapi.db.getConnection().from(joinTable.name).update({
|
|
1400
|
-
[invJoinColumn]: toStageId
|
|
1401
|
-
}).transacting(trx);
|
|
1402
|
-
}
|
|
1403
|
-
// Update all links from the specified stage to the new stage
|
|
1404
|
-
const selectStatement = strapi.db.getConnection().select({
|
|
1405
|
-
[joinColumn]: 't1.id',
|
|
1406
|
-
[invJoinColumn]: toStageId
|
|
1407
|
-
}).from(`${tableName} as t1`).leftJoin(`${joinTable.name} as t2`, `t1.id`, `t2.${joinColumn}`).where(`t2.${invJoinColumn}`, fromStageId).toSQL();
|
|
1408
|
-
// Insert rows for all entries of the content type that have the specified stage
|
|
1409
|
-
return strapi.db.getConnection(joinTable.name).insert(strapi.db.connection.raw(`(${joinColumn}, ${invJoinColumn}) ${selectStatement.sql}`, selectStatement.bindings)).transacting(trx);
|
|
1410
|
-
});
|
|
1411
|
-
},
|
|
1412
|
-
/**
|
|
1413
|
-
* Deletes all entity stages of a content type
|
|
1414
|
-
* @param {string} contentTypeUID
|
|
1415
|
-
* @returns
|
|
1416
|
-
*/ async deleteAllEntitiesStage (contentTypeUID) {
|
|
1417
|
-
const { attributes } = strapi.db.metadata.get(contentTypeUID);
|
|
1418
|
-
const joinTable = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable;
|
|
1419
|
-
// Delete all stage links for the content type
|
|
1420
|
-
return strapi.db.transaction(async ({ trx })=>strapi.db.getConnection().from(joinTable.name).delete().transacting(trx));
|
|
1421
|
-
}
|
|
1422
|
-
};
|
|
1423
|
-
});
|
|
1424
|
-
/**
|
|
1425
|
-
* Compares two arrays of stages and returns an object indicating the differences.
|
|
1426
|
-
*
|
|
1427
|
-
* The function compares the `id` properties of each stage in `sourceStages` and `comparisonStages` to determine if the stage is present in both arrays.
|
|
1428
|
-
* If a stage with the same `id` is found in both arrays but the `name` property is different, the stage is considered updated.
|
|
1429
|
-
* If a stage with a particular `id` is only found in `comparisonStages`, it is considered created.
|
|
1430
|
-
* If a stage with a particular `id` is only found in `sourceStages`, it is considered deleted.
|
|
1431
|
-
*
|
|
1432
|
-
* @typedef {{id: Number, name: String, workflow: Number}} Stage
|
|
1433
|
-
* @typedef {{created: Stage[], updated: Stage[], deleted: Stage[]}} DiffStages
|
|
1434
|
-
*
|
|
1435
|
-
* The DiffStages object has three properties: `created`, `updated`, and `deleted`.
|
|
1436
|
-
* `created` is an array of stages that are in `comparisonStages` but not in `sourceStages`.
|
|
1437
|
-
* `updated` is an array of stages that have different names in `comparisonStages` and `sourceStages`.
|
|
1438
|
-
* `deleted` is an array of stages that are in `sourceStages` but not in `comparisonStages`.
|
|
1439
|
-
*
|
|
1440
|
-
* @param {Stage[]} sourceStages
|
|
1441
|
-
* @param {Stage[]} comparisonStages
|
|
1442
|
-
* @returns { DiffStages }
|
|
1443
|
-
*/ function getDiffBetweenStages(sourceStages, comparisonStages) {
|
|
1444
|
-
const result = comparisonStages.reduce(// ...
|
|
1445
|
-
(acc, stageToCompare)=>{
|
|
1446
|
-
const srcStage = sourceStages.find((stage)=>stage.id === stageToCompare.id);
|
|
1447
|
-
if (!srcStage) {
|
|
1448
|
-
acc.created.push(stageToCompare);
|
|
1449
|
-
} else if (!isEqual(pick([
|
|
1450
|
-
'name',
|
|
1451
|
-
'color',
|
|
1452
|
-
'permissions'
|
|
1453
|
-
], srcStage), pick([
|
|
1454
|
-
'name',
|
|
1455
|
-
'color',
|
|
1456
|
-
'permissions'
|
|
1457
|
-
], stageToCompare))) {
|
|
1458
|
-
acc.updated.push(stageToCompare);
|
|
1459
|
-
}
|
|
1460
|
-
return acc;
|
|
1461
|
-
}, {
|
|
1462
|
-
created: [],
|
|
1463
|
-
updated: []
|
|
1464
|
-
});
|
|
1465
|
-
result.deleted = sourceStages.filter((srcStage)=>!comparisonStages.some((cmpStage)=>cmpStage.id === srcStage.id));
|
|
1466
|
-
return result;
|
|
1467
|
-
}
|
|
1468
|
-
/**
|
|
1469
|
-
* Asserts that at least one stage remains in the workflow after applying deletions and additions.
|
|
1470
|
-
*
|
|
1471
|
-
* @param {Array} workflowStages - An array of stages in the current workflow.
|
|
1472
|
-
* @param {Object} diffStages - An object containing the stages to be deleted and created.
|
|
1473
|
-
* @param {Array} diffStages.deleted - An array of stages that are planned to be deleted from the workflow.
|
|
1474
|
-
* @param {Array} diffStages.created - An array of stages that are planned to be created in the workflow.
|
|
1475
|
-
*
|
|
1476
|
-
* @throws {ValidationError} If the number of remaining stages in the workflow after applying deletions and additions is less than 1.
|
|
1477
|
-
*/ function assertAtLeastOneStageRemain(workflowStages, diffStages) {
|
|
1478
|
-
const remainingStagesCount = workflowStages.length - diffStages.deleted.length + diffStages.created.length;
|
|
1479
|
-
if (remainingStagesCount < 1) {
|
|
1480
|
-
throw new ValidationError$1(ERRORS.WORKFLOW_WITHOUT_STAGES);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Find the id of the nearest object in an array that matches a condition.
|
|
1485
|
-
* Used for searching for the nearest stage that is not deleted.
|
|
1486
|
-
* Starts by searching the elements before the index, then the remaining elements in the array.
|
|
1487
|
-
*
|
|
1488
|
-
* @param {Array} stages
|
|
1489
|
-
* @param {Number} startIndex the index to start searching from
|
|
1490
|
-
* @param {Function} condition must evaluate to true for the object to be considered a match
|
|
1491
|
-
* @returns {Object} stage
|
|
1492
|
-
*/ function findNearestMatchingStage(stages, startIndex, condition) {
|
|
1493
|
-
// Start by searching the elements before the startIndex
|
|
1494
|
-
for(let i = startIndex; i >= 0; i -= 1){
|
|
1495
|
-
if (condition(stages[i])) {
|
|
1496
|
-
return stages[i];
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
// If no matching element is found before the startIndex,
|
|
1500
|
-
// search the remaining elements in the array
|
|
1501
|
-
const remainingArray = stages.slice(startIndex + 1);
|
|
1502
|
-
const nearestObject = remainingArray.filter(condition)[0];
|
|
1503
|
-
return nearestObject;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
const { ApplicationError: ApplicationError$1 } = errors;
|
|
1507
|
-
const validActions = [
|
|
1508
|
-
STAGE_TRANSITION_UID
|
|
1509
|
-
];
|
|
1510
|
-
var stagePermissions = (({ strapi })=>{
|
|
1511
|
-
const roleService = getAdminService('role');
|
|
1512
|
-
const permissionService = getAdminService('permission');
|
|
1513
|
-
return {
|
|
1514
|
-
async register ({ roleId, action, fromStage }) {
|
|
1515
|
-
if (!validActions.includes(action)) {
|
|
1516
|
-
throw new ApplicationError$1(`Invalid action ${action}`);
|
|
1517
|
-
}
|
|
1518
|
-
const permissions = await roleService.addPermissions(roleId, [
|
|
1519
|
-
{
|
|
1520
|
-
action,
|
|
1521
|
-
actionParameters: {
|
|
1522
|
-
from: fromStage
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
]);
|
|
1526
|
-
// TODO: Filter response
|
|
1527
|
-
return permissions;
|
|
1528
|
-
},
|
|
1529
|
-
async registerMany (permissions) {
|
|
1530
|
-
return async.map(permissions, this.register);
|
|
1531
|
-
},
|
|
1532
|
-
async unregister (permissions) {
|
|
1533
|
-
const permissionIds = permissions.map(prop('id'));
|
|
1534
|
-
await permissionService.deleteByIds(permissionIds);
|
|
1535
|
-
},
|
|
1536
|
-
can (action, fromStage) {
|
|
1537
|
-
const requestState = strapi.requestContext.get()?.state;
|
|
1538
|
-
if (!requestState) {
|
|
1539
|
-
return false;
|
|
1540
|
-
}
|
|
1541
|
-
// Override permissions for super admin
|
|
1542
|
-
const userRoles = requestState.user?.roles;
|
|
1543
|
-
if (userRoles?.some((role)=>role.code === 'strapi-super-admin')) {
|
|
1544
|
-
return true;
|
|
1545
|
-
}
|
|
1546
|
-
return requestState.userAbility.can({
|
|
1547
|
-
name: action,
|
|
1548
|
-
params: {
|
|
1549
|
-
from: fromStage
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
};
|
|
1554
|
-
});
|
|
1555
|
-
|
|
1556
|
-
const { ApplicationError } = errors;
|
|
1557
|
-
var assignees$1 = (({ strapi })=>{
|
|
1558
|
-
const metrics = getService('workflow-metrics', {
|
|
1559
|
-
strapi
|
|
1560
|
-
});
|
|
1561
|
-
return {
|
|
1562
|
-
async findEntityAssigneeId (id, model) {
|
|
1563
|
-
const entity = await strapi.db.query(model).findOne({
|
|
1564
|
-
where: {
|
|
1565
|
-
id
|
|
1566
|
-
},
|
|
1567
|
-
populate: [
|
|
1568
|
-
ENTITY_ASSIGNEE_ATTRIBUTE
|
|
1569
|
-
],
|
|
1570
|
-
select: []
|
|
1571
|
-
});
|
|
1572
|
-
return entity?.[ENTITY_ASSIGNEE_ATTRIBUTE]?.id ?? null;
|
|
1573
|
-
},
|
|
1574
|
-
/**
|
|
1575
|
-
* Update the assignee of an entity
|
|
1576
|
-
*/ async updateEntityAssignee (entityToUpdate, model, assigneeId) {
|
|
1577
|
-
const { documentId, locale } = entityToUpdate;
|
|
1578
|
-
if (!isNil(assigneeId)) {
|
|
1579
|
-
const userExists = await getAdminService('user', {
|
|
1580
|
-
strapi
|
|
1581
|
-
}).exists({
|
|
1582
|
-
id: assigneeId
|
|
1583
|
-
});
|
|
1584
|
-
if (!userExists) {
|
|
1585
|
-
throw new ApplicationError(`Selected user does not exist`);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
const oldAssigneeId = await this.findEntityAssigneeId(entityToUpdate.id, model);
|
|
1589
|
-
metrics.sendDidEditAssignee(oldAssigneeId, assigneeId || null);
|
|
1590
|
-
const entity = await strapi.documents(model).update({
|
|
1591
|
-
documentId,
|
|
1592
|
-
locale,
|
|
1593
|
-
data: {
|
|
1594
|
-
[ENTITY_ASSIGNEE_ATTRIBUTE]: assigneeId || null
|
|
1595
|
-
},
|
|
1596
|
-
populate: [
|
|
1597
|
-
ENTITY_ASSIGNEE_ATTRIBUTE
|
|
1598
|
-
],
|
|
1599
|
-
fields: []
|
|
1600
|
-
});
|
|
1601
|
-
// Update the `updated_at` field of the entity, so that the `status` is not considered `Modified`
|
|
1602
|
-
// NOTE: `updatedAt` is a protected attribute that can not be modified directly from the query layer
|
|
1603
|
-
// hence the knex query builder is used here.
|
|
1604
|
-
const { tableName } = strapi.db.metadata.get(model);
|
|
1605
|
-
await strapi.db.connection(tableName).where({
|
|
1606
|
-
id: entityToUpdate.id
|
|
1607
|
-
}).update({
|
|
1608
|
-
updated_at: new Date(entityToUpdate.updatedAt)
|
|
1609
|
-
});
|
|
1610
|
-
return entity;
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
});
|
|
1614
|
-
|
|
1615
|
-
const { ValidationError } = errors;
|
|
1616
|
-
var reviewWorkflowsValidation = (({ strapi })=>{
|
|
1617
|
-
return {
|
|
1618
|
-
limits: {
|
|
1619
|
-
numberOfWorkflows: MAX_WORKFLOWS,
|
|
1620
|
-
stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW
|
|
1621
|
-
},
|
|
1622
|
-
register ({ numberOfWorkflows, stagesPerWorkflow }) {
|
|
1623
|
-
if (!Object.isFrozen(this.limits)) {
|
|
1624
|
-
this.limits.numberOfWorkflows = clampMaxWorkflows(numberOfWorkflows || this.limits.numberOfWorkflows);
|
|
1625
|
-
this.limits.stagesPerWorkflow = clampMaxStagesPerWorkflow(stagesPerWorkflow || this.limits.stagesPerWorkflow);
|
|
1626
|
-
Object.freeze(this.limits);
|
|
1627
|
-
}
|
|
1628
|
-
},
|
|
1629
|
-
/**
|
|
1630
|
-
* Validates the stages of a workflow.
|
|
1631
|
-
* @param {Array} stages - Array of stages to be validated.
|
|
1632
|
-
* @throws {ValidationError} - If the workflow has no stages or exceeds the limit.
|
|
1633
|
-
*/ validateWorkflowStages (stages) {
|
|
1634
|
-
if (!stages || stages.length === 0) {
|
|
1635
|
-
throw new ValidationError(ERRORS.WORKFLOW_WITHOUT_STAGES);
|
|
1636
|
-
}
|
|
1637
|
-
if (stages.length > this.limits.stagesPerWorkflow) {
|
|
1638
|
-
throw new ValidationError(ERRORS.STAGES_LIMIT);
|
|
1639
|
-
}
|
|
1640
|
-
// Validate stage names are not duplicated
|
|
1641
|
-
const stageNames = stages.map((stage)=>stage.name);
|
|
1642
|
-
if (uniq(stageNames).length !== stageNames.length) {
|
|
1643
|
-
throw new ValidationError(ERRORS.DUPLICATED_STAGE_NAME);
|
|
1644
|
-
}
|
|
1645
|
-
},
|
|
1646
|
-
async validateWorkflowCountStages (workflowId, countAddedStages = 0) {
|
|
1647
|
-
const stagesService = getService('stages', {
|
|
1648
|
-
strapi
|
|
1649
|
-
});
|
|
1650
|
-
const countWorkflowStages = await stagesService.count({
|
|
1651
|
-
workflowId
|
|
1652
|
-
});
|
|
1653
|
-
if (countWorkflowStages + countAddedStages > this.limits.stagesPerWorkflow) {
|
|
1654
|
-
throw new ValidationError(ERRORS.STAGES_LIMIT);
|
|
1655
|
-
}
|
|
1656
|
-
},
|
|
1657
|
-
/**
|
|
1658
|
-
* Validates the count of existing and added workflows.
|
|
1659
|
-
* @param {number} [countAddedWorkflows=0] - The count of workflows to be added.
|
|
1660
|
-
* @throws {ValidationError} - If the total count of workflows exceeds the limit.
|
|
1661
|
-
* @returns {Promise<void>} - A Promise that resolves when the validation is completed.
|
|
1662
|
-
*/ async validateWorkflowCount (countAddedWorkflows = 0) {
|
|
1663
|
-
const workflowsService = getService('workflows', {
|
|
1664
|
-
strapi
|
|
1665
|
-
});
|
|
1666
|
-
const countWorkflows = await workflowsService.count();
|
|
1667
|
-
if (countWorkflows + countAddedWorkflows > this.limits.numberOfWorkflows) {
|
|
1668
|
-
throw new ValidationError(ERRORS.WORKFLOWS_LIMIT);
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
};
|
|
1672
|
-
});
|
|
1673
|
-
|
|
1674
|
-
const sendDidCreateStage = async ()=>{
|
|
1675
|
-
strapi.telemetry.send('didCreateStage', {});
|
|
1676
|
-
};
|
|
1677
|
-
const sendDidEditStage = async ()=>{
|
|
1678
|
-
strapi.telemetry.send('didEditStage', {});
|
|
1679
|
-
};
|
|
1680
|
-
const sendDidDeleteStage = async ()=>{
|
|
1681
|
-
strapi.telemetry.send('didDeleteStage', {});
|
|
1682
|
-
};
|
|
1683
|
-
const sendDidChangeEntryStage = async ()=>{
|
|
1684
|
-
strapi.telemetry.send('didChangeEntryStage', {});
|
|
1685
|
-
};
|
|
1686
|
-
const sendDidCreateWorkflow = async (workflowId, hasRequiredStageToPublish)=>{
|
|
1687
|
-
strapi.telemetry.send('didCreateWorkflow', {
|
|
1688
|
-
workflowId,
|
|
1689
|
-
hasRequiredStageToPublish
|
|
1690
|
-
});
|
|
1691
|
-
};
|
|
1692
|
-
const sendDidEditWorkflow = async (workflowId, hasRequiredStageToPublish)=>{
|
|
1693
|
-
strapi.telemetry.send('didEditWorkflow', {
|
|
1694
|
-
workflowId,
|
|
1695
|
-
hasRequiredStageToPublish
|
|
1696
|
-
});
|
|
1697
|
-
};
|
|
1698
|
-
const sendDidEditAssignee = async (fromId, toId)=>{
|
|
1699
|
-
strapi.telemetry.send('didEditAssignee', {
|
|
1700
|
-
from: fromId,
|
|
1701
|
-
to: toId
|
|
1702
|
-
});
|
|
1703
|
-
};
|
|
1704
|
-
const sendDidSendReviewWorkflowPropertiesOnceAWeek = async (numberOfActiveWorkflows, avgStagesCount, maxStagesCount, activatedContentTypes)=>{
|
|
1705
|
-
strapi.telemetry.send('didSendReviewWorkflowPropertiesOnceAWeek', {
|
|
1706
|
-
groupProperties: {
|
|
1707
|
-
numberOfActiveWorkflows,
|
|
1708
|
-
avgStagesCount,
|
|
1709
|
-
maxStagesCount,
|
|
1710
|
-
activatedContentTypes
|
|
1711
|
-
}
|
|
1712
|
-
});
|
|
1713
|
-
};
|
|
1714
|
-
var reviewWorkflowsMetrics = {
|
|
1715
|
-
sendDidCreateStage,
|
|
1716
|
-
sendDidEditStage,
|
|
1717
|
-
sendDidDeleteStage,
|
|
1718
|
-
sendDidChangeEntryStage,
|
|
1719
|
-
sendDidCreateWorkflow,
|
|
1720
|
-
sendDidEditWorkflow,
|
|
1721
|
-
sendDidSendReviewWorkflowPropertiesOnceAWeek,
|
|
1722
|
-
sendDidEditAssignee
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
|
-
const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
1726
|
-
const getWeeklyCronScheduleAt = (date)=>`${date.getSeconds()} ${date.getMinutes()} ${date.getHours()} * * ${date.getDay()}`;
|
|
1727
|
-
var reviewWorkflowsWeeklyMetrics = (({ strapi })=>{
|
|
1728
|
-
const metrics = getService('workflow-metrics', {
|
|
1729
|
-
strapi
|
|
1730
|
-
});
|
|
1731
|
-
const workflowsService = getService('workflows', {
|
|
1732
|
-
strapi
|
|
1733
|
-
});
|
|
1734
|
-
const getMetricsStoreValue = async ()=>{
|
|
1735
|
-
const value = await strapi.store.get({
|
|
1736
|
-
type: 'plugin',
|
|
1737
|
-
name: 'ee',
|
|
1738
|
-
key: 'metrics'
|
|
1739
|
-
});
|
|
1740
|
-
return defaultTo({}, value);
|
|
1741
|
-
};
|
|
1742
|
-
const setMetricsStoreValue = (value)=>strapi.store.set({
|
|
1743
|
-
type: 'plugin',
|
|
1744
|
-
name: 'ee',
|
|
1745
|
-
key: 'metrics',
|
|
1746
|
-
value
|
|
1747
|
-
});
|
|
1748
|
-
return {
|
|
1749
|
-
async computeMetrics () {
|
|
1750
|
-
// There will never be more than 200 workflow, so we can safely fetch them all
|
|
1751
|
-
const workflows = await workflowsService.find({
|
|
1752
|
-
populate: 'stages'
|
|
1753
|
-
});
|
|
1754
|
-
const stagesCount = flow(map('stages'), map(size))(workflows);
|
|
1755
|
-
const contentTypesCount = flow(map('contentTypes'), map(size))(workflows);
|
|
1756
|
-
return {
|
|
1757
|
-
numberOfActiveWorkflows: size(workflows),
|
|
1758
|
-
avgStagesCount: mean(stagesCount),
|
|
1759
|
-
maxStagesCount: max(stagesCount),
|
|
1760
|
-
activatedContentTypes: sum(contentTypesCount)
|
|
1761
|
-
};
|
|
1762
|
-
},
|
|
1763
|
-
async sendMetrics () {
|
|
1764
|
-
const computedMetrics = await this.computeMetrics();
|
|
1765
|
-
metrics.sendDidSendReviewWorkflowPropertiesOnceAWeek(computedMetrics);
|
|
1766
|
-
const metricsInfoStored = await getMetricsStoreValue();
|
|
1767
|
-
// @ts-expect-error metricsInfoStored can use spread
|
|
1768
|
-
await setMetricsStoreValue({
|
|
1769
|
-
...metricsInfoStored,
|
|
1770
|
-
lastWeeklyUpdate: new Date().getTime()
|
|
1771
|
-
});
|
|
1772
|
-
},
|
|
1773
|
-
async ensureWeeklyStoredCronSchedule () {
|
|
1774
|
-
const metricsInfoStored = await getMetricsStoreValue();
|
|
1775
|
-
const { weeklySchedule: currentSchedule, lastWeeklyUpdate } = metricsInfoStored;
|
|
1776
|
-
const now = new Date();
|
|
1777
|
-
let weeklySchedule = currentSchedule;
|
|
1778
|
-
if (!currentSchedule || !lastWeeklyUpdate || lastWeeklyUpdate + ONE_WEEK < now.getTime()) {
|
|
1779
|
-
weeklySchedule = getWeeklyCronScheduleAt(add(now, {
|
|
1780
|
-
seconds: 15
|
|
1781
|
-
}));
|
|
1782
|
-
await setMetricsStoreValue({
|
|
1783
|
-
...metricsInfoStored,
|
|
1784
|
-
weeklySchedule
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
return weeklySchedule;
|
|
1788
|
-
},
|
|
1789
|
-
async registerCron () {
|
|
1790
|
-
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
|
|
1791
|
-
strapi.cron.add({
|
|
1792
|
-
reviewWorkflowsWeekly: {
|
|
1793
|
-
task: this.sendMetrics.bind(this),
|
|
1794
|
-
options: weeklySchedule
|
|
1795
|
-
}
|
|
1796
|
-
});
|
|
1797
|
-
}
|
|
1798
|
-
};
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
/**
|
|
1802
|
-
* Get the stage information of an entity
|
|
1803
|
-
* @param {String} uid
|
|
1804
|
-
* @param {Number} id
|
|
1805
|
-
* @returns {Object}
|
|
1806
|
-
*/ const getEntityStage = async (uid, id, params)=>{
|
|
1807
|
-
const entity = await strapi.documents(uid).findOne({
|
|
1808
|
-
...params,
|
|
1809
|
-
documentId: id,
|
|
1810
|
-
status: 'draft',
|
|
1811
|
-
populate: {
|
|
1812
|
-
[ENTITY_STAGE_ATTRIBUTE]: {
|
|
1813
|
-
populate: {
|
|
1814
|
-
workflow: true
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
1819
|
-
return entity?.[ENTITY_STAGE_ATTRIBUTE] ?? {};
|
|
1820
|
-
};
|
|
1821
|
-
/**
|
|
1822
|
-
* Ensures the entity is assigned to the default workflow stage
|
|
1823
|
-
*/ const assignStageOnCreate = async (ctx, next)=>{
|
|
1824
|
-
if (ctx.action !== 'create' && ctx.action !== 'clone') {
|
|
1825
|
-
return next();
|
|
1826
|
-
}
|
|
1827
|
-
/**
|
|
1828
|
-
* Content types can have assigned workflows,
|
|
1829
|
-
* if the CT has one, assign a default value to the stage attribute if it's not present
|
|
1830
|
-
*/ const workflow = await getService('workflows').getAssignedWorkflow(ctx.contentType.uid, {
|
|
1831
|
-
populate: 'stages'
|
|
1832
|
-
});
|
|
1833
|
-
if (!workflow) {
|
|
1834
|
-
return next();
|
|
1835
|
-
}
|
|
1836
|
-
const data = ctx.params.data;
|
|
1837
|
-
// Assign the default stage if the entity doesn't have one
|
|
1838
|
-
if (ctx.params?.data && isNil(data[ENTITY_STAGE_ATTRIBUTE])) {
|
|
1839
|
-
data[ENTITY_STAGE_ATTRIBUTE] = {
|
|
1840
|
-
id: workflow.stages[0].id
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
return next();
|
|
1844
|
-
};
|
|
1845
|
-
const handleStageOnUpdate = async (ctx, next)=>{
|
|
1846
|
-
if (ctx.action !== 'update') {
|
|
1847
|
-
return next();
|
|
1848
|
-
}
|
|
1849
|
-
const { documentId } = ctx.params;
|
|
1850
|
-
const data = ctx.params.data;
|
|
1851
|
-
if (isNil(data?.[ENTITY_STAGE_ATTRIBUTE])) {
|
|
1852
|
-
delete data?.[ENTITY_STAGE_ATTRIBUTE];
|
|
1853
|
-
return next();
|
|
1854
|
-
}
|
|
1855
|
-
/**
|
|
1856
|
-
* Get last stage of the entity
|
|
1857
|
-
*/ const previousStage = await getEntityStage(ctx.contentType.uid, documentId, ctx.params);
|
|
1858
|
-
const result = await next();
|
|
1859
|
-
if (!result) {
|
|
1860
|
-
return result;
|
|
1861
|
-
}
|
|
1862
|
-
// @ts-expect-error
|
|
1863
|
-
const updatedStage = result?.[ENTITY_STAGE_ATTRIBUTE];
|
|
1864
|
-
// Stage might be null if field is not populated
|
|
1865
|
-
if (updatedStage && previousStage?.id && previousStage.id !== updatedStage.id) {
|
|
1866
|
-
const model = strapi.getModel(ctx.contentType.uid);
|
|
1867
|
-
strapi.eventHub.emit(WORKFLOW_UPDATE_STAGE, {
|
|
1868
|
-
model: model.modelName,
|
|
1869
|
-
uid: model.uid,
|
|
1870
|
-
// TODO v6: Rename to "entry", which is what is used for regular CRUD updates
|
|
1871
|
-
entity: {
|
|
1872
|
-
// @ts-expect-error
|
|
1873
|
-
id: result?.id,
|
|
1874
|
-
documentId,
|
|
1875
|
-
// @ts-expect-error
|
|
1876
|
-
locale: result?.locale,
|
|
1877
|
-
status: 'draft'
|
|
1878
|
-
},
|
|
1879
|
-
workflow: {
|
|
1880
|
-
id: previousStage.workflow.id,
|
|
1881
|
-
stages: {
|
|
1882
|
-
from: {
|
|
1883
|
-
id: previousStage.id,
|
|
1884
|
-
name: previousStage.name
|
|
1885
|
-
},
|
|
1886
|
-
to: {
|
|
1887
|
-
id: updatedStage.id,
|
|
1888
|
-
name: updatedStage.name
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
return next();
|
|
1895
|
-
};
|
|
1896
|
-
/**
|
|
1897
|
-
* Check if the entity is at the required stage before publish
|
|
1898
|
-
*/ const checkStageBeforePublish = async (ctx, next)=>{
|
|
1899
|
-
if (ctx.action !== 'publish') {
|
|
1900
|
-
return next();
|
|
1901
|
-
}
|
|
1902
|
-
const workflow = await getService('workflows').getAssignedWorkflow(ctx.contentType.uid, {
|
|
1903
|
-
populate: 'stageRequiredToPublish'
|
|
1904
|
-
});
|
|
1905
|
-
if (!workflow || !workflow.stageRequiredToPublish) {
|
|
1906
|
-
return next();
|
|
1907
|
-
}
|
|
1908
|
-
const { documentId } = ctx.params;
|
|
1909
|
-
const entryStage = await getEntityStage(ctx.contentType.uid, documentId, ctx.params);
|
|
1910
|
-
if (entryStage.id !== workflow.stageRequiredToPublish.id) {
|
|
1911
|
-
throw new errors.ValidationError('Entry is not at the required stage to publish');
|
|
1912
|
-
}
|
|
1913
|
-
return next();
|
|
1914
|
-
};
|
|
1915
|
-
var documentServiceMiddleware = (()=>({
|
|
1916
|
-
assignStageOnCreate,
|
|
1917
|
-
handleStageOnUpdate,
|
|
1918
|
-
checkStageBeforePublish
|
|
1919
|
-
}));
|
|
1920
|
-
|
|
1921
|
-
var services = {
|
|
1922
|
-
workflows: workflows$1,
|
|
1923
|
-
stages: stages$1,
|
|
1924
|
-
'stage-permissions': stagePermissions,
|
|
1925
|
-
assignees: assignees$1,
|
|
1926
|
-
validation: reviewWorkflowsValidation,
|
|
1927
|
-
'document-service-middlewares': documentServiceMiddleware,
|
|
1928
|
-
'workflow-metrics': reviewWorkflowsMetrics,
|
|
1929
|
-
'workflow-weekly-metrics': reviewWorkflowsWeeklyMetrics
|
|
1930
|
-
};
|
|
1931
|
-
|
|
1932
|
-
const stageObject = yup.object().shape({
|
|
1933
|
-
id: yup.number().integer().min(1),
|
|
1934
|
-
name: yup.string().max(255).required(),
|
|
1935
|
-
color: yup.string().matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
|
|
1936
|
-
permissions: yup.array().of(yup.object().shape({
|
|
1937
|
-
role: yup.number().integer().min(1).required(),
|
|
1938
|
-
action: yup.string().oneOf([
|
|
1939
|
-
STAGE_TRANSITION_UID
|
|
1940
|
-
]).required(),
|
|
1941
|
-
actionParameters: yup.object().shape({
|
|
1942
|
-
from: yup.number().integer().min(1).required(),
|
|
1943
|
-
to: yup.number().integer().min(1)
|
|
1944
|
-
})
|
|
1945
|
-
}))
|
|
1946
|
-
});
|
|
1947
|
-
const validateUpdateStageOnEntitySchema = yup.object().shape({
|
|
1948
|
-
id: yup.number().integer().min(1).required()
|
|
1949
|
-
}).required();
|
|
1950
|
-
const validateContentTypes = yup.array().of(yup.string().test({
|
|
1951
|
-
name: 'content-type-exists',
|
|
1952
|
-
message: (value)=>`Content type ${value.originalValue} does not exist`,
|
|
1953
|
-
test (uid) {
|
|
1954
|
-
// Warning; we use the strapi global - to avoid that, it would need to refactor how
|
|
1955
|
-
// we generate validation function by using a factory with the strapi instance as parameter.
|
|
1956
|
-
return !!strapi.getModel(uid);
|
|
1957
|
-
}
|
|
1958
|
-
}).test({
|
|
1959
|
-
name: 'content-type-review-workflow-enabled',
|
|
1960
|
-
message: (value)=>`Content type ${value.originalValue} does not have review workflow enabled`,
|
|
1961
|
-
test (uid) {
|
|
1962
|
-
const model = strapi.getModel(uid);
|
|
1963
|
-
// It's not a valid content type if it doesn't have the stage attribute
|
|
1964
|
-
return hasStageAttribute(model);
|
|
1965
|
-
}
|
|
1966
|
-
}));
|
|
1967
|
-
const validateWorkflowCreateSchema = yup.object().shape({
|
|
1968
|
-
name: yup.string().max(255).min(1, 'Workflow name can not be empty').required(),
|
|
1969
|
-
stages: yup.array().of(stageObject)// @ts-expect-error - add unique property into the yup namespace typing
|
|
1970
|
-
.uniqueProperty('name', 'Stage name must be unique').min(1, 'Can not create a workflow without stages').max(200, 'Can not have more than 200 stages').required('Can not create a workflow without stages'),
|
|
1971
|
-
contentTypes: validateContentTypes,
|
|
1972
|
-
stageRequiredToPublishName: yup.string().min(1).nullable()
|
|
1973
|
-
});
|
|
1974
|
-
const validateWorkflowUpdateSchema = yup.object().shape({
|
|
1975
|
-
name: yup.string().max(255).min(1, 'Workflow name can not be empty'),
|
|
1976
|
-
stages: yup.array().of(stageObject)// @ts-expect-error - add unique property into the yup namespace typing
|
|
1977
|
-
.uniqueProperty('name', 'Stage name must be unique').min(1, 'Can not update a workflow without stages').max(200, 'Can not have more than 200 stages'),
|
|
1978
|
-
contentTypes: validateContentTypes,
|
|
1979
|
-
stageRequiredToPublishName: yup.string().min(1).nullable()
|
|
1980
|
-
});
|
|
1981
|
-
const validateUpdateAssigneeOnEntitySchema = yup.object().shape({
|
|
1982
|
-
id: yup.number().integer().min(1).nullable()
|
|
1983
|
-
}).required();
|
|
1984
|
-
const validateLocaleSchema = yup.string().nullable();
|
|
1985
|
-
const validateWorkflowCreate = validateYupSchema(validateWorkflowCreateSchema);
|
|
1986
|
-
const validateUpdateStageOnEntity = validateYupSchema(validateUpdateStageOnEntitySchema);
|
|
1987
|
-
const validateUpdateAssigneeOnEntity = validateYupSchema(validateUpdateAssigneeOnEntitySchema);
|
|
1988
|
-
const validateWorkflowUpdate = validateYupSchema(validateWorkflowUpdateSchema);
|
|
1989
|
-
const validateLocale = validateYupSchema(validateLocaleSchema);
|
|
1990
|
-
|
|
1991
|
-
/**
|
|
1992
|
-
*
|
|
1993
|
-
* @param { Core.Strapi } strapi - Strapi instance
|
|
1994
|
-
* @param userAbility
|
|
1995
|
-
* @return { PermissionChecker }
|
|
1996
|
-
*/ function getWorkflowsPermissionChecker({ strapi: strapi1 }, userAbility) {
|
|
1997
|
-
return strapi1.plugin('content-manager').service('permission-checker').create({
|
|
1998
|
-
userAbility,
|
|
1999
|
-
model: WORKFLOW_MODEL_UID
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
/**
|
|
2003
|
-
* Transforms workflow to an admin UI format.
|
|
2004
|
-
* Some attributes (like permissions) are presented in a different format in the admin UI.
|
|
2005
|
-
* @param {Workflow} workflow
|
|
2006
|
-
*/ function formatWorkflowToAdmin(workflow) {
|
|
2007
|
-
if (!workflow) return;
|
|
2008
|
-
if (!workflow.stages) return workflow;
|
|
2009
|
-
// Transform permissions roles to be the id string instead of an object
|
|
2010
|
-
const transformPermissions = map(update('role', property('id')));
|
|
2011
|
-
const transformStages = map(update('permissions', transformPermissions));
|
|
2012
|
-
return update('stages', transformStages, workflow);
|
|
2013
|
-
}
|
|
2014
|
-
var workflows = {
|
|
2015
|
-
/**
|
|
2016
|
-
* Create a new workflow
|
|
2017
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2018
|
-
*/ async create (ctx) {
|
|
2019
|
-
const { body, query } = ctx.request;
|
|
2020
|
-
const { sanitizeCreateInput, sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker({
|
|
2021
|
-
strapi
|
|
2022
|
-
}, ctx.state.userAbility);
|
|
2023
|
-
const { populate } = await sanitizedQuery.create(query);
|
|
2024
|
-
const workflowBody = await validateWorkflowCreate(body.data);
|
|
2025
|
-
const workflowService = getService('workflows');
|
|
2026
|
-
const createdWorkflow = await workflowService.create({
|
|
2027
|
-
data: await sanitizeCreateInput(workflowBody),
|
|
2028
|
-
populate
|
|
2029
|
-
}).then(formatWorkflowToAdmin);
|
|
2030
|
-
ctx.created({
|
|
2031
|
-
data: await sanitizeOutput(createdWorkflow)
|
|
2032
|
-
});
|
|
2033
|
-
},
|
|
2034
|
-
/**
|
|
2035
|
-
* Update a workflow
|
|
2036
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2037
|
-
*/ async update (ctx) {
|
|
2038
|
-
const { id } = ctx.params;
|
|
2039
|
-
const { body, query } = ctx.request;
|
|
2040
|
-
const workflowService = getService('workflows');
|
|
2041
|
-
const { sanitizeUpdateInput, sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker({
|
|
2042
|
-
strapi
|
|
2043
|
-
}, ctx.state.userAbility);
|
|
2044
|
-
const { populate } = await sanitizedQuery.update(query);
|
|
2045
|
-
const workflowBody = await validateWorkflowUpdate(body.data);
|
|
2046
|
-
// Find if workflow exists
|
|
2047
|
-
const workflow = await workflowService.findById(id, {
|
|
2048
|
-
populate: WORKFLOW_POPULATE
|
|
2049
|
-
});
|
|
2050
|
-
if (!workflow) {
|
|
2051
|
-
return ctx.notFound();
|
|
2052
|
-
}
|
|
2053
|
-
// Sanitize input data
|
|
2054
|
-
const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
|
|
2055
|
-
const dataToUpdate = await getPermittedFieldToUpdate(workflowBody);
|
|
2056
|
-
// Update workflow
|
|
2057
|
-
const updatedWorkflow = await workflowService.update(workflow, {
|
|
2058
|
-
data: dataToUpdate,
|
|
2059
|
-
populate
|
|
2060
|
-
}).then(formatWorkflowToAdmin);
|
|
2061
|
-
// Send sanitized response
|
|
2062
|
-
ctx.body = {
|
|
2063
|
-
data: await sanitizeOutput(updatedWorkflow)
|
|
2064
|
-
};
|
|
2065
|
-
},
|
|
2066
|
-
/**
|
|
2067
|
-
* Delete a workflow
|
|
2068
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2069
|
-
*/ async delete (ctx) {
|
|
2070
|
-
const { id } = ctx.params;
|
|
2071
|
-
const { query } = ctx.request;
|
|
2072
|
-
const workflowService = getService('workflows');
|
|
2073
|
-
const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker({
|
|
2074
|
-
strapi
|
|
2075
|
-
}, ctx.state.userAbility);
|
|
2076
|
-
const { populate } = await sanitizedQuery.delete(query);
|
|
2077
|
-
const workflow = await workflowService.findById(id, {
|
|
2078
|
-
populate: WORKFLOW_POPULATE
|
|
2079
|
-
});
|
|
2080
|
-
if (!workflow) {
|
|
2081
|
-
return ctx.notFound("Workflow doesn't exist");
|
|
2082
|
-
}
|
|
2083
|
-
const deletedWorkflow = await workflowService.delete(workflow, {
|
|
2084
|
-
populate
|
|
2085
|
-
}).then(formatWorkflowToAdmin);
|
|
2086
|
-
ctx.body = {
|
|
2087
|
-
data: await sanitizeOutput(deletedWorkflow)
|
|
2088
|
-
};
|
|
2089
|
-
},
|
|
2090
|
-
/**
|
|
2091
|
-
* List all workflows
|
|
2092
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2093
|
-
*/ async find (ctx) {
|
|
2094
|
-
const { query } = ctx.request;
|
|
2095
|
-
const workflowService = getService('workflows');
|
|
2096
|
-
const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker({
|
|
2097
|
-
strapi
|
|
2098
|
-
}, ctx.state.userAbility);
|
|
2099
|
-
const { populate, filters, sort } = await sanitizedQuery.read(query);
|
|
2100
|
-
const [workflows, workflowCount] = await Promise.all([
|
|
2101
|
-
workflowService.find({
|
|
2102
|
-
populate,
|
|
2103
|
-
filters,
|
|
2104
|
-
sort
|
|
2105
|
-
}).then(map(formatWorkflowToAdmin)),
|
|
2106
|
-
workflowService.count()
|
|
2107
|
-
]);
|
|
2108
|
-
ctx.body = {
|
|
2109
|
-
data: await async.map(workflows, sanitizeOutput),
|
|
2110
|
-
meta: {
|
|
2111
|
-
workflowCount
|
|
2112
|
-
}
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
};
|
|
2116
|
-
|
|
2117
|
-
/**
|
|
2118
|
-
*
|
|
2119
|
-
* @param { Core.Strapi } strapi - Strapi instance
|
|
2120
|
-
* @param userAbility
|
|
2121
|
-
* @return { (Stage) => SanitizedStage }
|
|
2122
|
-
*/ function sanitizeStage({ strapi: strapi1 }, userAbility) {
|
|
2123
|
-
const permissionChecker = strapi1.plugin('content-manager').service('permission-checker').create({
|
|
2124
|
-
userAbility,
|
|
2125
|
-
model: STAGE_MODEL_UID
|
|
2126
|
-
});
|
|
2127
|
-
return (entity)=>permissionChecker.sanitizeOutput(entity);
|
|
2128
|
-
}
|
|
2129
|
-
var stages = {
|
|
2130
|
-
/**
|
|
2131
|
-
* List all stages
|
|
2132
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2133
|
-
*/ async find (ctx) {
|
|
2134
|
-
const { workflow_id: workflowId } = ctx.params;
|
|
2135
|
-
const { populate } = ctx.query;
|
|
2136
|
-
const stagesService = getService('stages');
|
|
2137
|
-
const sanitizer = sanitizeStage({
|
|
2138
|
-
strapi
|
|
2139
|
-
}, ctx.state.userAbility);
|
|
2140
|
-
const stages = await stagesService.find({
|
|
2141
|
-
workflowId,
|
|
2142
|
-
populate
|
|
2143
|
-
});
|
|
2144
|
-
ctx.body = {
|
|
2145
|
-
data: await async.map(stages, sanitizer)
|
|
2146
|
-
};
|
|
2147
|
-
},
|
|
2148
|
-
/**
|
|
2149
|
-
* Get one stage
|
|
2150
|
-
* @param {import('koa').BaseContext} ctx - koa context
|
|
2151
|
-
*/ async findById (ctx) {
|
|
2152
|
-
const { id, workflow_id: workflowId } = ctx.params;
|
|
2153
|
-
const { populate } = ctx.query;
|
|
2154
|
-
const stagesService = getService('stages');
|
|
2155
|
-
const sanitizer = sanitizeStage({
|
|
2156
|
-
strapi
|
|
2157
|
-
}, ctx.state.userAbility);
|
|
2158
|
-
const stage = await stagesService.findById(id, {
|
|
2159
|
-
workflowId,
|
|
2160
|
-
populate
|
|
2161
|
-
});
|
|
2162
|
-
ctx.body = {
|
|
2163
|
-
data: await sanitizer(stage)
|
|
2164
|
-
};
|
|
2165
|
-
},
|
|
2166
|
-
/**
|
|
2167
|
-
* Updates an entity's stage.
|
|
2168
|
-
* @async
|
|
2169
|
-
* @param {Object} ctx - The Koa context object.
|
|
2170
|
-
* @param {Object} ctx.params - An object containing the parameters from the request URL.
|
|
2171
|
-
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
2172
|
-
* @param {string} ctx.params.id - The ID of the entity to update.
|
|
2173
|
-
* @param {Object} ctx.request.body.data - Optional data object containing the new stage ID for the entity.
|
|
2174
|
-
* @param {string} ctx.request.body.data.id - The ID of the new stage for the entity.
|
|
2175
|
-
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
2176
|
-
* @throws {ValidationError} If the `data` object in the request body fails to pass validation.
|
|
2177
|
-
* @returns {Promise<void>} A promise that resolves when the entity's stage has been updated.
|
|
2178
|
-
*/ async updateEntity (ctx) {
|
|
2179
|
-
const stagesService = getService('stages');
|
|
2180
|
-
const stagePermissions = getService('stage-permissions');
|
|
2181
|
-
const workflowService = getService('workflows');
|
|
2182
|
-
const { model_uid: modelUID, id: documentId } = ctx.params;
|
|
2183
|
-
const { body, query = {} } = ctx.request;
|
|
2184
|
-
const { sanitizeOutput } = strapi.plugin('content-manager').service('permission-checker').create({
|
|
2185
|
-
userAbility: ctx.state.userAbility,
|
|
2186
|
-
model: modelUID
|
|
2187
|
-
});
|
|
2188
|
-
// Load entity
|
|
2189
|
-
const locale = await validateLocale(query?.locale);
|
|
2190
|
-
const entity = await strapi.documents(modelUID).findOne({
|
|
2191
|
-
documentId,
|
|
2192
|
-
// @ts-expect-error - locale should be also null in the doc service types
|
|
2193
|
-
locale,
|
|
2194
|
-
populate: [
|
|
2195
|
-
ENTITY_STAGE_ATTRIBUTE
|
|
2196
|
-
]
|
|
2197
|
-
});
|
|
2198
|
-
if (!entity) {
|
|
2199
|
-
ctx.throw(404, 'Entity not found');
|
|
2200
|
-
}
|
|
2201
|
-
// Validate if entity stage can be updated
|
|
2202
|
-
const canTransition = stagePermissions.can(STAGE_TRANSITION_UID, entity[ENTITY_STAGE_ATTRIBUTE]?.id);
|
|
2203
|
-
if (!canTransition) {
|
|
2204
|
-
ctx.throw(403, 'Forbidden stage transition');
|
|
2205
|
-
}
|
|
2206
|
-
const { id: stageId } = await validateUpdateStageOnEntity({
|
|
2207
|
-
id: Number(body?.data?.id)
|
|
2208
|
-
}, 'You should pass an id to the body of the put request.');
|
|
2209
|
-
const workflow = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
|
|
2210
|
-
workflowService.assertStageBelongsToWorkflow(stageId, workflow);
|
|
2211
|
-
const updatedEntity = await stagesService.updateEntity(entity, modelUID, stageId);
|
|
2212
|
-
ctx.body = {
|
|
2213
|
-
data: await sanitizeOutput(updatedEntity)
|
|
2214
|
-
};
|
|
2215
|
-
},
|
|
2216
|
-
/**
|
|
2217
|
-
* List all the stages that are available for a user to transition an entity to.
|
|
2218
|
-
* If the user has permission to change the current stage of the entity every other stage in the workflow is returned
|
|
2219
|
-
* @async
|
|
2220
|
-
* @param {*} ctx
|
|
2221
|
-
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
2222
|
-
* @param {string} ctx.params.id - The ID of the entity.
|
|
2223
|
-
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
2224
|
-
*/ async listAvailableStages (ctx) {
|
|
2225
|
-
const stagePermissions = getService('stage-permissions');
|
|
2226
|
-
const workflowService = getService('workflows');
|
|
2227
|
-
const { model_uid: modelUID, id: documentId } = ctx.params;
|
|
2228
|
-
const { query = {} } = ctx.request;
|
|
2229
|
-
if (strapi.plugin('content-manager').service('permission-checker').create({
|
|
2230
|
-
userAbility: ctx.state.userAbility,
|
|
2231
|
-
model: modelUID
|
|
2232
|
-
}).cannot.read()) {
|
|
2233
|
-
return ctx.forbidden();
|
|
2234
|
-
}
|
|
2235
|
-
// Load entity
|
|
2236
|
-
const locale = await validateLocale(query?.locale) ?? undefined;
|
|
2237
|
-
const entity = await strapi.documents(modelUID).findOne({
|
|
2238
|
-
documentId,
|
|
2239
|
-
locale,
|
|
2240
|
-
populate: [
|
|
2241
|
-
ENTITY_STAGE_ATTRIBUTE
|
|
2242
|
-
]
|
|
2243
|
-
});
|
|
2244
|
-
if (!entity) {
|
|
2245
|
-
ctx.throw(404, 'Entity not found');
|
|
2246
|
-
}
|
|
2247
|
-
const entityStageId = entity[ENTITY_STAGE_ATTRIBUTE]?.id;
|
|
2248
|
-
const canTransition = stagePermissions.can(STAGE_TRANSITION_UID, entityStageId);
|
|
2249
|
-
const [workflowCount, workflowResult] = await Promise.all([
|
|
2250
|
-
workflowService.count(),
|
|
2251
|
-
workflowService.getAssignedWorkflow(modelUID, {
|
|
2252
|
-
populate: 'stages'
|
|
2253
|
-
})
|
|
2254
|
-
]);
|
|
2255
|
-
const workflowStages = workflowResult ? workflowResult.stages : [];
|
|
2256
|
-
const meta = {
|
|
2257
|
-
stageCount: workflowStages.length,
|
|
2258
|
-
workflowCount
|
|
2259
|
-
};
|
|
2260
|
-
if (!canTransition) {
|
|
2261
|
-
ctx.body = {
|
|
2262
|
-
data: [],
|
|
2263
|
-
meta
|
|
2264
|
-
};
|
|
2265
|
-
return;
|
|
2266
|
-
}
|
|
2267
|
-
const data = workflowStages.filter((stage)=>stage.id !== entityStageId);
|
|
2268
|
-
ctx.body = {
|
|
2269
|
-
data,
|
|
2270
|
-
meta
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
};
|
|
2274
|
-
|
|
2275
|
-
var assignees = {
|
|
2276
|
-
/**
|
|
2277
|
-
* Updates an entity's assignee.
|
|
2278
|
-
* @async
|
|
2279
|
-
* @param {Object} ctx - The Koa context object.
|
|
2280
|
-
* @param {Object} ctx.params - An object containing the parameters from the request URL.
|
|
2281
|
-
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
2282
|
-
* @param {string} ctx.params.id - The ID of the entity to update.
|
|
2283
|
-
* @param {Object} ctx.request.body.data - Optional data object containing the new assignee ID for the entity.
|
|
2284
|
-
* @param {string} ctx.request.body.data.id - The ID of the new assignee for the entity.
|
|
2285
|
-
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
2286
|
-
* @throws {ValidationError} If the `data` object in the request body fails to pass validation.
|
|
2287
|
-
* @returns {Promise<void>} A promise that resolves when the entity's assignee has been updated.
|
|
2288
|
-
*/ async updateEntity (ctx) {
|
|
2289
|
-
const assigneeService = getService('assignees');
|
|
2290
|
-
const workflowService = getService('workflows');
|
|
2291
|
-
const stagePermissions = getService('stage-permissions');
|
|
2292
|
-
const { model_uid: model, id: documentId } = ctx.params;
|
|
2293
|
-
const locale = await validateLocale(ctx.request.query?.locale) ?? undefined;
|
|
2294
|
-
const { sanitizeOutput } = strapi.plugin('content-manager').service('permission-checker').create({
|
|
2295
|
-
userAbility: ctx.state.userAbility,
|
|
2296
|
-
model
|
|
2297
|
-
});
|
|
2298
|
-
// Retrieve the entity so we can get its current stage
|
|
2299
|
-
const entity = await strapi.documents(model).findOne({
|
|
2300
|
-
documentId,
|
|
2301
|
-
locale,
|
|
2302
|
-
populate: [
|
|
2303
|
-
ENTITY_STAGE_ATTRIBUTE
|
|
2304
|
-
]
|
|
2305
|
-
});
|
|
2306
|
-
if (!entity) {
|
|
2307
|
-
ctx.throw(404, 'Entity not found');
|
|
2308
|
-
}
|
|
2309
|
-
// Only allow users who can update the current stage to change the assignee
|
|
2310
|
-
const canTransitionStage = stagePermissions.can(STAGE_TRANSITION_UID, entity[ENTITY_STAGE_ATTRIBUTE]?.id);
|
|
2311
|
-
if (!canTransitionStage) {
|
|
2312
|
-
ctx.throw(403, 'Stage transition permission is required');
|
|
2313
|
-
}
|
|
2314
|
-
// TODO: check if user has update permission on the entity
|
|
2315
|
-
const { id: assigneeId } = await validateUpdateAssigneeOnEntity(ctx.request?.body?.data, 'You should pass a valid id to the body of the put request.');
|
|
2316
|
-
await workflowService.assertContentTypeBelongsToWorkflow(model);
|
|
2317
|
-
const updatedEntity = await assigneeService.updateEntityAssignee(entity, model, assigneeId);
|
|
2318
|
-
ctx.body = {
|
|
2319
|
-
data: await sanitizeOutput(updatedEntity)
|
|
2320
|
-
};
|
|
2321
|
-
}
|
|
2322
|
-
};
|
|
2323
|
-
|
|
2324
|
-
var controllers = {
|
|
2325
|
-
workflows,
|
|
2326
|
-
stages,
|
|
2327
|
-
assignees
|
|
2328
|
-
};
|
|
1
|
+
import register from './register.mjs';
|
|
2
|
+
import contentTypes from './content-types/index.mjs';
|
|
3
|
+
import bootstrap from './bootstrap.mjs';
|
|
4
|
+
import destroy from './destroy.mjs';
|
|
5
|
+
import routes from './routes/index.mjs';
|
|
6
|
+
import services from './services/index.mjs';
|
|
7
|
+
import controllers from './controllers/index.mjs';
|
|
2329
8
|
|
|
2330
9
|
const getPlugin = ()=>{
|
|
2331
10
|
if (strapi.ee.features.isEnabled('review-workflows')) {
|