@strapi/admin 4.11.4 → 4.12.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/admin/src/constants.js +83 -83
  2. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +8 -5
  3. package/admin/src/content-manager/components/Inputs/index.js +3 -47
  4. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +34 -37
  5. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +0 -27
  6. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +93 -14
  7. package/admin/src/content-manager/pages/ListView/index.js +65 -59
  8. package/admin/src/content-manager/pages/ListView/utils/buildValidGetParams.js +30 -0
  9. package/admin/src/content-manager/pages/ListView/utils/index.js +1 -1
  10. package/admin/src/content-manager/utils/mergeMetasWithSchema.js +5 -1
  11. package/admin/src/hooks/index.js +0 -1
  12. package/admin/src/hooks/useAdminUsers/useAdminUsers.js +3 -3
  13. package/admin/src/hooks/useEnterprise/useEnterprise.js +4 -4
  14. package/admin/src/pages/App/index.js +28 -23
  15. package/admin/src/pages/AuthPage/components/Register/index.js +5 -1
  16. package/admin/src/pages/ProfilePage/index.js +6 -1
  17. package/admin/src/pages/SettingsPage/components/Tokens/TokenBox/index.js +1 -1
  18. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +1 -1
  19. package/admin/src/translations/zh-Hans.json +1 -1
  20. package/build/0cd5f8915b265d5b1856.png +0 -0
  21. package/build/2799.cf9b491f.chunk.js +1 -0
  22. package/build/4485.d3c6dd1d.chunk.js +6 -0
  23. package/build/539.865446c0.chunk.js +1 -0
  24. package/build/{5563.86f9aa9c.chunk.js → 5563.a146acac.chunk.js} +2 -2
  25. package/build/7018.f3dad3c1.chunk.js +1 -0
  26. package/build/7259.0e25ab5d.chunk.js +1 -0
  27. package/build/9465.d8fc1377.chunk.js +112 -0
  28. package/build/9944.29289a16.chunk.js +26 -0
  29. package/build/{Admin-authenticatedApp.cb649fc1.chunk.js → Admin-authenticatedApp.9d3afb79.chunk.js} +2 -2
  30. package/build/{Admin_settingsPage.4069bb8a.chunk.js → Admin_settingsPage.074655f6.chunk.js} +13 -13
  31. package/build/admin-app.3ede71ad.chunk.js +61 -0
  32. package/build/admin-edit-users.78552758.chunk.js +10 -0
  33. package/build/admin-users.c23322fc.chunk.js +11 -0
  34. package/build/audit-logs-settings-page.37fe915c.chunk.js +1 -0
  35. package/build/content-manager.08541eeb.chunk.js +1094 -0
  36. package/build/content-type-builder-translation-en-json.38e20391.chunk.js +1 -0
  37. package/build/content-type-builder.de22f7c9.chunk.js +166 -0
  38. package/build/index.html +1 -1
  39. package/build/main.a8ede50d.js +2927 -0
  40. package/build/review-workflows-settings-create-view.56f61e18.chunk.js +1 -0
  41. package/build/review-workflows-settings-edit-view.912bc9c0.chunk.js +1 -0
  42. package/build/review-workflows-settings-list-view.cf6a08d3.chunk.js +56 -0
  43. package/build/runtime~main.5e9bf4b3.js +2 -0
  44. package/build/{users-roles-settings-page.1f505119.chunk.js → users-roles-settings-page.d286426a.chunk.js} +1 -1
  45. package/build/{zh-Hans-json.4cfef87d.chunk.js → zh-Hans-json.fada6f40.chunk.js} +1 -1
  46. package/ee/admin/constants.js +14 -14
  47. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +84 -30
  48. package/ee/admin/content-manager/{components/DynamicTable/CellContent/ReviewWorkflowsStage → pages/ListView/ReviewWorkflowsColumn}/ReviewWorkflowsStageEE.js +7 -2
  49. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +24 -0
  50. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  51. package/ee/admin/hooks/useLicenseLimitNotification/index.js +17 -6
  52. package/ee/admin/hooks/useLicenseLimits/index.js +1 -32
  53. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +44 -0
  54. package/ee/admin/pages/SettingsPage/constants.js +25 -1
  55. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +6 -4
  56. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +6 -4
  57. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +19 -4
  58. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/Layout.js +65 -0
  59. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/index.js +1 -0
  60. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js +111 -0
  61. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png +0 -0
  62. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js +3 -0
  63. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +21 -0
  64. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +1 -0
  65. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +4 -4
  66. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +110 -0
  67. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/index.js +1 -0
  68. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -1
  69. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +13 -19
  70. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +246 -0
  71. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +13 -0
  72. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +269 -0
  73. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +13 -0
  74. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +382 -0
  75. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +13 -0
  76. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +53 -23
  77. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +43 -28
  78. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +9 -2
  79. package/ee/server/config/admin-actions.js +24 -0
  80. package/ee/server/constants/default-stages.json +8 -4
  81. package/ee/server/constants/default-workflow.json +3 -1
  82. package/ee/server/constants/workflows.js +10 -1
  83. package/ee/server/content-types/workflow/index.js +10 -0
  84. package/ee/server/content-types/workflow-stage/index.js +3 -1
  85. package/ee/server/controllers/admin.js +1 -0
  86. package/ee/server/controllers/workflows/index.js +135 -8
  87. package/ee/server/controllers/workflows/stages/index.js +38 -38
  88. package/ee/server/migrations/review-workflows-content-types.js +29 -0
  89. package/ee/server/migrations/review-workflows-deleted-ct-in-workflows.js +39 -0
  90. package/ee/server/migrations/review-workflows-stage-attribute.js +49 -0
  91. package/ee/server/migrations/review-workflows-stages-color.js +2 -2
  92. package/ee/server/migrations/review-workflows-workflow-name.js +21 -0
  93. package/ee/server/register.js +12 -2
  94. package/ee/server/routes/review-workflows.js +44 -10
  95. package/ee/server/services/index.js +1 -0
  96. package/ee/server/services/review-workflows/entity-service-decorator.js +8 -13
  97. package/ee/server/services/review-workflows/review-workflows.js +45 -53
  98. package/ee/server/services/review-workflows/stages.js +84 -46
  99. package/ee/server/services/review-workflows/validation.js +60 -0
  100. package/ee/server/services/review-workflows/workflows/content-types.js +80 -0
  101. package/ee/server/services/review-workflows/workflows/index.js +207 -0
  102. package/ee/server/utils/review-workflows.js +30 -25
  103. package/ee/server/validation/review-workflows.js +49 -10
  104. package/package.json +10 -11
  105. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -2
  106. package/admin/src/content-manager/pages/ListView/utils/buildQueryString.js +0 -36
  107. package/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js +0 -4
  108. package/admin/src/hooks/useLicenseLimits/index.js +0 -3
  109. package/admin/src/pages/App/utils/index.js +0 -3
  110. package/admin/src/pages/App/utils/unique-identifier.js +0 -12
  111. package/build/1799.44d2e264.chunk.js +0 -33
  112. package/build/5932.6a23b88c.chunk.js +0 -1
  113. package/build/7018.98feed67.chunk.js +0 -1
  114. package/build/7259.fb69d4bf.chunk.js +0 -1
  115. package/build/admin-app.fea867af.chunk.js +0 -61
  116. package/build/admin-edit-users.200551e3.chunk.js +0 -10
  117. package/build/admin-users.3b12dca2.chunk.js +0 -11
  118. package/build/audit-logs-settings-page.f538490f.chunk.js +0 -1
  119. package/build/content-manager.c40f5ff9.chunk.js +0 -1088
  120. package/build/content-type-builder-translation-en-json.f592325b.chunk.js +0 -1
  121. package/build/content-type-builder.bd1bbff1.chunk.js +0 -166
  122. package/build/main.ee36abd9.js +0 -2927
  123. package/build/review-workflows-settings.93808ae0.chunk.js +0 -110
  124. package/build/runtime~main.efd966f6.js +0 -2
  125. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -58
  126. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +0 -3
  127. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ProtectedPage.js +0 -20
  128. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +0 -204
  129. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +0 -3
  130. package/ee/server/services/review-workflows/workflows.js +0 -25
@@ -4,20 +4,35 @@ import isEqual from 'lodash/isEqual';
4
4
  import {
5
5
  ACTION_ADD_STAGE,
6
6
  ACTION_DELETE_STAGE,
7
- ACTION_SET_WORKFLOWS,
7
+ ACTION_RESET_WORKFLOW,
8
+ ACTION_SET_WORKFLOW,
8
9
  ACTION_UPDATE_STAGE,
9
10
  ACTION_UPDATE_STAGE_POSITION,
11
+ ACTION_UPDATE_WORKFLOW,
10
12
  STAGE_COLOR_DEFAULT,
11
13
  } from '../constants';
12
14
 
13
15
  export const initialState = {
14
16
  status: 'loading',
15
17
  serverState: {
16
- currentWorkflow: null,
17
- workflows: [],
18
+ workflow: null,
18
19
  },
19
20
  clientState: {
20
- currentWorkflow: { data: null, isDirty: false, hasDeletedServerStages: false },
21
+ currentWorkflow: {
22
+ data: {
23
+ name: '',
24
+ contentTypes: [],
25
+ stages: [
26
+ {
27
+ color: STAGE_COLOR_DEFAULT,
28
+ name: '',
29
+ __temp_key__: 1,
30
+ },
31
+ ],
32
+ },
33
+ isDirty: false,
34
+ hasDeletedServerStages: false,
35
+ },
21
36
  },
22
37
  };
23
38
 
@@ -26,29 +41,31 @@ export function reducer(state = initialState, action) {
26
41
  const { payload } = action;
27
42
 
28
43
  switch (action.type) {
29
- case ACTION_SET_WORKFLOWS: {
30
- const { status, workflows } = payload;
44
+ case ACTION_SET_WORKFLOW: {
45
+ const { status, workflow } = payload;
31
46
 
32
47
  draft.status = status;
33
48
 
34
- if (workflows?.length > 0) {
35
- let defaultWorkflow = workflows[0];
36
-
37
- // A safety net in case a stage does not have a color assigned;
38
- // this normallly should not happen
39
- defaultWorkflow = {
40
- ...defaultWorkflow,
41
- stages: defaultWorkflow.stages.map((stage) => ({
49
+ if (workflow) {
50
+ draft.serverState.workflow = workflow;
51
+ draft.clientState.currentWorkflow.data = {
52
+ ...workflow,
53
+ stages: workflow.stages.map((stage) => ({
42
54
  ...stage,
55
+ // A safety net in case a stage does not have a color assigned;
56
+ // this normallly should not happen
43
57
  color: stage?.color ?? STAGE_COLOR_DEFAULT,
44
58
  })),
45
59
  };
46
-
47
- draft.serverState.workflows = workflows;
48
- draft.serverState.currentWorkflow = defaultWorkflow;
49
- draft.clientState.currentWorkflow.data = defaultWorkflow;
50
- draft.clientState.currentWorkflow.hasDeletedServerStages = false;
51
60
  }
61
+
62
+ draft.clientState.currentWorkflow.hasDeletedServerStages = false;
63
+ break;
64
+ }
65
+
66
+ case ACTION_RESET_WORKFLOW: {
67
+ draft.clientState.currentWorkflow.data = initialState.clientState.currentWorkflow.data;
68
+ draft.serverState = initialState.serverState;
52
69
  break;
53
70
  }
54
71
 
@@ -61,8 +78,9 @@ export function reducer(state = initialState, action) {
61
78
  );
62
79
 
63
80
  if (!currentWorkflow.hasDeletedServerStages) {
64
- draft.clientState.currentWorkflow.hasDeletedServerStages =
65
- !!state.serverState.currentWorkflow.stages.find((stage) => stage.id === stageId);
81
+ draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
82
+ state.serverState.currentWorkflow?.stages ?? []
83
+ ).find((stage) => stage.id === stageId);
66
84
  }
67
85
 
68
86
  break;
@@ -125,15 +143,27 @@ export function reducer(state = initialState, action) {
125
143
  break;
126
144
  }
127
145
 
146
+ case ACTION_UPDATE_WORKFLOW: {
147
+ draft.clientState.currentWorkflow.data = {
148
+ ...draft.clientState.currentWorkflow.data,
149
+ ...payload,
150
+ };
151
+
152
+ break;
153
+ }
154
+
128
155
  default:
129
156
  break;
130
157
  }
131
158
 
132
- if (state.clientState.currentWorkflow.data) {
159
+ if (state.clientState.currentWorkflow.data && draft.serverState.workflow) {
133
160
  draft.clientState.currentWorkflow.isDirty = !isEqual(
134
161
  current(draft.clientState.currentWorkflow).data,
135
- draft.serverState.currentWorkflow
162
+ draft.serverState.workflow
136
163
  );
164
+ } else {
165
+ // if there is no workflow on the server, the workflow is awalys considered dirty
166
+ draft.clientState.currentWorkflow.isDirty = true;
137
167
  }
138
168
  });
139
169
  }
@@ -2,33 +2,48 @@ import * as yup from 'yup';
2
2
 
3
3
  export function getWorkflowValidationSchema({ formatMessage }) {
4
4
  return yup.object({
5
- stages: yup.array().of(
6
- yup.object().shape({
7
- name: yup
8
- .string()
9
- .required(
10
- formatMessage({
11
- id: 'Settings.review-workflows.validation.stage.name',
12
- defaultMessage: 'Name is required',
13
- })
14
- )
15
- .max(
16
- 255,
17
- formatMessage({
18
- id: 'Settings.review-workflows.validation.stage.max-length',
19
- defaultMessage: 'Name can not be longer than 255 characters',
20
- })
21
- ),
22
- color: yup
23
- .string()
24
- .required(
25
- formatMessage({
26
- id: 'Settings.review-workflows.validation.stage.color',
27
- defaultMessage: 'Color is required',
28
- })
29
- )
30
- .matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
31
- })
32
- ),
5
+ contentTypes: yup.array().of(yup.string()),
6
+ name: yup
7
+ .string()
8
+ .max(
9
+ 255,
10
+ formatMessage({
11
+ id: 'Settings.review-workflows.validation.name.max-length',
12
+ defaultMessage: 'Name can not be longer than 255 characters',
13
+ })
14
+ )
15
+ .required(),
16
+
17
+ stages: yup
18
+ .array()
19
+ .of(
20
+ yup.object().shape({
21
+ name: yup
22
+ .string()
23
+ .required(
24
+ formatMessage({
25
+ id: 'Settings.review-workflows.validation.stage.name',
26
+ defaultMessage: 'Name is required',
27
+ })
28
+ )
29
+ .max(
30
+ 255,
31
+ formatMessage({
32
+ id: 'Settings.review-workflows.validation.stage.max-length',
33
+ defaultMessage: 'Name can not be longer than 255 characters',
34
+ })
35
+ ),
36
+ color: yup
37
+ .string()
38
+ .required(
39
+ formatMessage({
40
+ id: 'Settings.review-workflows.validation.stage.color',
41
+ defaultMessage: 'Color is required',
42
+ })
43
+ )
44
+ .matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
45
+ })
46
+ )
47
+ .min(1),
33
48
  });
34
49
  }
@@ -10,8 +10,15 @@ import { useLicenseLimits } from '../../../../../../hooks';
10
10
 
11
11
  const CreateAction = ({ onClick }) => {
12
12
  const { formatMessage } = useIntl();
13
- const { license } = useLicenseLimits();
14
- const { permittedSeats, shouldStopCreate } = license?.data ?? {};
13
+ const {
14
+ license: { permittedSeats, shouldStopCreate },
15
+ isError,
16
+ isLoading,
17
+ } = useLicenseLimits();
18
+
19
+ if (isError || isLoading) {
20
+ return null;
21
+ }
15
22
 
16
23
  return (
17
24
  <Flex gap={2}>
@@ -30,6 +30,30 @@ module.exports = {
30
30
  },
31
31
  ],
32
32
  reviewWorkflows: [
33
+ {
34
+ uid: 'review-workflows.create',
35
+ displayName: 'Create',
36
+ pluginName: 'admin',
37
+ section: 'settings',
38
+ category: 'review workflows',
39
+ subCategory: 'options',
40
+ },
41
+ {
42
+ uid: 'review-workflows.update',
43
+ displayName: 'Update',
44
+ pluginName: 'admin',
45
+ section: 'settings',
46
+ category: 'review workflows',
47
+ subCategory: 'options',
48
+ },
49
+ {
50
+ uid: 'review-workflows.delete',
51
+ displayName: 'Delete',
52
+ pluginName: 'admin',
53
+ section: 'settings',
54
+ category: 'review workflows',
55
+ subCategory: 'options',
56
+ },
33
57
  {
34
58
  uid: 'review-workflows.read',
35
59
  displayName: 'Read',
@@ -1,14 +1,18 @@
1
1
  [
2
2
  {
3
- "name": "To do"
3
+ "name": "To do",
4
+ "color": "#4945FF"
4
5
  },
5
6
  {
6
- "name": "Ready to review"
7
+ "name": "Ready to review",
8
+ "color": "#9736E8"
7
9
  },
8
10
  {
9
- "name": "In progress"
11
+ "name": "In progress",
12
+ "color": "#FF4945"
10
13
  },
11
14
  {
12
- "name": "Reviewed"
15
+ "name": "Reviewed",
16
+ "color": "#328048"
13
17
  }
14
18
  ]
@@ -1 +1,3 @@
1
- {}
1
+ {
2
+ "name": "Default"
3
+ }
@@ -5,5 +5,14 @@ module.exports = {
5
5
  WORKFLOW_MODEL_UID: 'admin::workflow',
6
6
  STAGE_MODEL_UID: 'admin::workflow-stage',
7
7
  STAGE_DEFAULT_COLOR: '#4945FF',
8
- ENTITY_STAGE_ATTRIBUTE: 'strapi_reviewWorkflows_stage',
8
+ ENTITY_STAGE_ATTRIBUTE: 'strapi_stage',
9
+ MAX_WORKFLOWS: 200,
10
+ MAX_STAGES_PER_WORKFLOW: 200,
11
+ ERRORS: {
12
+ WORKFLOW_WITHOUT_STAGES: 'A workflow must have at least one stage.',
13
+ WORKFLOWS_LIMIT:
14
+ 'You’ve reached the limit of workflows in your plan. Delete a workflow or contact Sales to enable more workflows.',
15
+ STAGES_LIMIT:
16
+ 'You’ve reached the limit of stages for this workflow in your plan. Try deleting some stages or contact Sales to enable more stages.',
17
+ },
9
18
  };
@@ -20,12 +20,22 @@ module.exports = {
20
20
  },
21
21
  },
22
22
  attributes: {
23
+ name: {
24
+ type: 'string',
25
+ required: true,
26
+ unique: true,
27
+ },
23
28
  stages: {
24
29
  type: 'relation',
25
30
  target: 'admin::workflow-stage',
26
31
  relation: 'oneToMany',
27
32
  mappedBy: 'workflow',
28
33
  },
34
+ contentTypes: {
35
+ type: 'json',
36
+ required: true,
37
+ default: [],
38
+ },
29
39
  },
30
40
  },
31
41
  };
@@ -12,7 +12,9 @@ module.exports = {
12
12
  pluralName: 'workflow-stages',
13
13
  displayName: 'Stages',
14
14
  },
15
- options: {},
15
+ options: {
16
+ version: '1.1.0',
17
+ },
16
18
  pluginOptions: {
17
19
  'content-manager': {
18
20
  visible: false,
@@ -42,6 +42,7 @@ module.exports = {
42
42
  shouldStopCreate: isNil(permittedSeats) ? false : currentActiveUserCount >= permittedSeats,
43
43
  licenseLimitStatus,
44
44
  isHostedOnStrapiCloud: env('STRAPI_HOSTING', null) === 'strapi.cloud',
45
+ features: ee.features.list() ?? [],
45
46
  };
46
47
 
47
48
  return { data };
@@ -1,36 +1,163 @@
1
1
  'use strict';
2
2
 
3
+ const { mapAsync } = require('@strapi/utils');
3
4
  const { getService } = require('../../utils');
4
5
 
6
+ const {
7
+ validateWorkflowCreate,
8
+ validateWorkflowUpdate,
9
+ } = require('../../validation/review-workflows');
10
+ const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
11
+
12
+ /**
13
+ *
14
+ * @param { Strapi } strapi - Strapi instance
15
+ * @param userAbility
16
+ * @return { PermissionChecker }
17
+ */
18
+ function getWorkflowsPermissionChecker({ strapi }, userAbility) {
19
+ return strapi
20
+ .plugin('content-manager')
21
+ .service('permission-checker')
22
+ .create({ userAbility, model: WORKFLOW_MODEL_UID });
23
+ }
24
+
5
25
  module.exports = {
6
26
  /**
7
- * List all workflows
27
+ * Create a new workflow
8
28
  * @param {import('koa').BaseContext} ctx - koa context
9
29
  */
10
- async find(ctx) {
11
- const { populate } = ctx.query;
30
+ async create(ctx) {
31
+ const { body, query } = ctx.request;
32
+ const { sanitizeCreateInput, sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
33
+ { strapi },
34
+ ctx.state.userAbility
35
+ );
36
+ const { populate } = await sanitizedQuery.create(query);
37
+
38
+ const workflowBody = await validateWorkflowCreate(body.data);
39
+
40
+ const workflowService = getService('workflows');
41
+ const createdWorkflow = await workflowService.create({
42
+ data: await sanitizeCreateInput(workflowBody),
43
+ populate,
44
+ });
45
+
46
+ ctx.body = {
47
+ data: await sanitizeOutput(createdWorkflow),
48
+ };
49
+ },
50
+
51
+ /**
52
+ * Update a workflow
53
+ * @param {import('koa').BaseContext} ctx - koa context
54
+ */
55
+ async update(ctx) {
56
+ const { id } = ctx.params;
57
+ const { body, query } = ctx.request;
12
58
  const workflowService = getService('workflows');
13
- const data = await workflowService.find({
59
+ const { sanitizeUpdateInput, sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
60
+ { strapi },
61
+ ctx.state.userAbility
62
+ );
63
+ const { populate } = await sanitizedQuery.update(query);
64
+
65
+ const workflowBody = await validateWorkflowUpdate(body.data);
66
+
67
+ const workflow = await workflowService.findById(id, { populate: ['stages'] });
68
+ if (!workflow) {
69
+ return ctx.notFound();
70
+ }
71
+ const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
72
+
73
+ const dataToUpdate = await getPermittedFieldToUpdate(workflowBody);
74
+
75
+ const updatedWorkflow = await workflowService.update(workflow, {
76
+ data: dataToUpdate,
14
77
  populate,
15
78
  });
16
79
 
17
80
  ctx.body = {
18
- data,
81
+ data: await sanitizeOutput(updatedWorkflow),
82
+ };
83
+ },
84
+
85
+ /**
86
+ * Delete a workflow
87
+ * @param {import('koa').BaseContext} ctx - koa context
88
+ */
89
+ async delete(ctx) {
90
+ const { id } = ctx.params;
91
+ const { query } = ctx.request;
92
+ const workflowService = getService('workflows');
93
+ const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
94
+ { strapi },
95
+ ctx.state.userAbility
96
+ );
97
+ const { populate } = await sanitizedQuery.delete(query);
98
+
99
+ const workflow = await workflowService.findById(id, { populate: ['stages'] });
100
+ if (!workflow) {
101
+ return ctx.notFound("Workflow doesn't exist");
102
+ }
103
+
104
+ const deletedWorkflow = await workflowService.delete(workflow, { populate });
105
+
106
+ ctx.body = {
107
+ data: await sanitizeOutput(deletedWorkflow),
108
+ };
109
+ },
110
+
111
+ /**
112
+ * List all workflows
113
+ * @param {import('koa').BaseContext} ctx - koa context
114
+ */
115
+ async find(ctx) {
116
+ const { query } = ctx.request;
117
+ const workflowService = getService('workflows');
118
+ const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
119
+ { strapi },
120
+ ctx.state.userAbility
121
+ );
122
+ const { populate, filters, sort } = await sanitizedQuery.read(query);
123
+
124
+ const [workflows, workflowCount] = await Promise.all([
125
+ workflowService.find({ populate, filters, sort }),
126
+ workflowService.count(),
127
+ ]);
128
+
129
+ ctx.body = {
130
+ data: await mapAsync(workflows, sanitizeOutput),
131
+ meta: {
132
+ workflowCount,
133
+ },
19
134
  };
20
135
  },
21
136
  /**
22
137
  * Get one workflow based on its id contained in request parameters
138
+ * Returns count of workflows in meta, used to prevent workflow edition when
139
+ * max workflow count is reached for the current plan
23
140
  * @param {import('koa').BaseContext} ctx - koa context
24
141
  */
25
142
  async findById(ctx) {
26
143
  const { id } = ctx.params;
27
- const { populate } = ctx.query;
144
+ const { query } = ctx.request;
145
+ const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
146
+ { strapi },
147
+ ctx.state.userAbility
148
+ );
149
+ const { populate } = await sanitizedQuery.read(query);
28
150
 
29
151
  const workflowService = getService('workflows');
30
- const data = await workflowService.findById(id, { populate });
152
+
153
+ const [workflow, workflowCount] = await Promise.all([
154
+ workflowService.findById(id, { populate }),
155
+ workflowService.count(),
156
+ ]);
31
157
 
32
158
  ctx.body = {
33
- data,
159
+ data: await sanitizeOutput(workflow),
160
+ meta: { workflowCount },
34
161
  };
35
162
  },
36
163
  };
@@ -1,12 +1,24 @@
1
1
  'use strict';
2
2
 
3
- const { ApplicationError } = require('@strapi/utils').errors;
3
+ const { mapAsync } = require('@strapi/utils');
4
4
  const { getService } = require('../../../utils');
5
- const { hasReviewWorkflow } = require('../../../utils/review-workflows');
6
- const {
7
- validateUpdateStages,
8
- validateUpdateStageOnEntity,
9
- } = require('../../../validation/review-workflows');
5
+ const { validateUpdateStageOnEntity } = require('../../../validation/review-workflows');
6
+ const { STAGE_MODEL_UID } = require('../../../constants/workflows');
7
+
8
+ /**
9
+ *
10
+ * @param { Strapi } strapi - Strapi instance
11
+ * @param userAbility
12
+ * @return { (Stage) => SanitizedStage }
13
+ */
14
+ function sanitizeStage({ strapi }, userAbility) {
15
+ const permissionChecker = strapi
16
+ .plugin('content-manager')
17
+ .service('permission-checker')
18
+ .create({ userAbility, model: STAGE_MODEL_UID });
19
+
20
+ return (entity) => permissionChecker.sanitizeOutput(entity);
21
+ }
10
22
 
11
23
  module.exports = {
12
24
  /**
@@ -17,14 +29,15 @@ module.exports = {
17
29
  const { workflow_id: workflowId } = ctx.params;
18
30
  const { populate } = ctx.query;
19
31
  const stagesService = getService('stages');
32
+ const sanitizer = sanitizeStage({ strapi }, ctx.state.userAbility);
20
33
 
21
- const data = await stagesService.find({
34
+ const stages = await stagesService.find({
22
35
  workflowId,
23
36
  populate,
24
37
  });
25
38
 
26
39
  ctx.body = {
27
- data,
40
+ data: await mapAsync(stages, sanitizer),
28
41
  };
29
42
  },
30
43
  /**
@@ -35,36 +48,18 @@ module.exports = {
35
48
  const { id, workflow_id: workflowId } = ctx.params;
36
49
  const { populate } = ctx.query;
37
50
  const stagesService = getService('stages');
51
+ const sanitizer = sanitizeStage({ strapi }, ctx.state.userAbility);
38
52
 
39
- const data = await stagesService.findById(id, {
53
+ const stage = await stagesService.findById(id, {
40
54
  workflowId,
41
55
  populate,
42
56
  });
43
57
 
44
58
  ctx.body = {
45
- data,
59
+ data: await sanitizer(stage),
46
60
  };
47
61
  },
48
62
 
49
- /**
50
- * Replace all stages in a workflow
51
- * @param {import('koa').BaseContext} ctx - koa context
52
- *
53
- */
54
- async replace(ctx) {
55
- const { workflow_id: workflowId } = ctx.params;
56
- const stagesService = getService('stages');
57
- const {
58
- body: { data: stages },
59
- } = ctx.request;
60
-
61
- const stagesValidated = await validateUpdateStages(stages);
62
-
63
- const data = await stagesService.replaceWorkflowStages(workflowId, stagesValidated);
64
-
65
- ctx.body = { data };
66
- },
67
-
68
63
  /**
69
64
  * Updates an entity's stage.
70
65
  * @async
@@ -80,23 +75,28 @@ module.exports = {
80
75
  */
81
76
  async updateEntity(ctx) {
82
77
  const stagesService = getService('stages');
78
+ const workflowService = getService('workflows');
79
+
83
80
  const { model_uid: modelUID, id: entityIdString } = ctx.params;
81
+ const { body } = ctx.request;
82
+
84
83
  const entityId = Number(entityIdString);
85
84
 
85
+ const { sanitizeOutput } = strapi
86
+ .plugin('content-manager')
87
+ .service('permission-checker')
88
+ .create({ userAbility: ctx.state.userAbility, model: modelUID });
89
+
86
90
  const { id: stageId } = await validateUpdateStageOnEntity(
87
- ctx.request?.body?.data,
91
+ { id: Number(body?.data?.id) },
88
92
  'You should pass an id to the body of the put request.'
89
93
  );
90
94
 
91
- if (!hasReviewWorkflow({ strapi }, modelUID)) {
92
- throw new ApplicationError(`Review workflows is not activated on ${modelUID}.`);
93
- }
94
-
95
- // TODO When multiple workflows are possible, check if the stage is part of the right one
96
- // Didn't need this today as their can only be one workflow
95
+ const workflow = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
96
+ workflowService.assertStageBelongsToWorkflow(stageId, workflow);
97
97
 
98
- const data = await stagesService.updateEntity({ id: entityId, modelUID }, stageId);
98
+ const entity = await stagesService.updateEntity({ id: entityId, modelUID }, stageId);
99
99
 
100
- ctx.body = { data };
100
+ ctx.body = { data: await sanitizeOutput(entity) };
101
101
  },
102
102
  };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const { get, keys, pickBy, pipe } = require('lodash/fp');
4
+ const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
5
+
6
+ async function migrateWorkflowsContentTypes({ oldContentTypes, contentTypes }) {
7
+ // Look for RW contentTypes attribute
8
+ const hadContentTypes = !!oldContentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.contentTypes;
9
+ const hasContentTypes = !!contentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.contentTypes;
10
+
11
+ if (!hadContentTypes && hasContentTypes) {
12
+ // Initialize contentTypes with an empty array and assign only to one
13
+ // workflow the Content Types which were using Review Workflow before.
14
+ await strapi.query(WORKFLOW_MODEL_UID).updateMany({ data: { contentTypes: [] } });
15
+
16
+ // Find Content Types which were using Review Workflow before
17
+ const contentTypes = pipe([pickBy(get('options.reviewWorkflows')), keys])(oldContentTypes);
18
+
19
+ if (contentTypes.length) {
20
+ // Update only one workflow with the contentTypes
21
+ // Before this release there was only one workflow, so this operation is safe.
22
+ await strapi
23
+ .query(WORKFLOW_MODEL_UID)
24
+ .update({ where: { id: { $notNull: true } }, data: { contentTypes } });
25
+ }
26
+ }
27
+ }
28
+
29
+ module.exports = migrateWorkflowsContentTypes;