@strapi/admin 4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8 → 4.10.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 (167) hide show
  1. package/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/PublicationState.js +26 -0
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/index.js +1 -0
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStage.js +15 -0
  4. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +1 -0
  5. package/admin/src/content-manager/components/DynamicTable/index.js +43 -49
  6. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +2 -0
  7. package/admin/src/content-manager/components/DynamicZone/utils/select.js +1 -1
  8. package/admin/src/content-manager/components/FieldComponent/utils/select.js +2 -1
  9. package/admin/src/content-manager/components/Inputs/utils/getInputType.js +1 -1
  10. package/admin/src/content-manager/components/Inputs/utils/select.js +1 -1
  11. package/admin/src/content-manager/hooks/useContentTypeLayout/index.js +1 -2
  12. package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +4 -1
  13. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/index.js +1 -2
  14. package/admin/src/content-manager/pages/EditSettingsView/init.js +3 -1
  15. package/admin/src/content-manager/pages/EditSettingsView/utils/createPossibleMainFieldsForModelsAndComponents.js +2 -4
  16. package/admin/src/content-manager/pages/EditView/DeleteLink/utils/select.js +1 -1
  17. package/admin/src/content-manager/pages/EditView/Information/index.js +77 -53
  18. package/admin/src/content-manager/pages/EditView/InformationBox/InformationBoxCE.js +13 -0
  19. package/admin/src/content-manager/pages/EditView/InformationBox/index.js +3 -0
  20. package/admin/src/content-manager/pages/EditView/index.js +3 -4
  21. package/admin/src/content-manager/pages/EditView/utils/createAttributesLayout.js +2 -1
  22. package/admin/src/content-manager/pages/EditView/utils/getFieldsActionMatchingPermissions.js +2 -1
  23. package/admin/src/content-manager/pages/ListView/index.js +6 -9
  24. package/admin/src/content-manager/utils/checkIfAttributeIsDisplayable.js +1 -1
  25. package/admin/src/content-manager/utils/createDefaultForm.js +2 -2
  26. package/admin/src/content-manager/utils/formatLayoutToApi.js +2 -1
  27. package/admin/src/content-manager/utils/getFieldName.js +1 -1
  28. package/admin/src/content-manager/utils/mergeMetasWithSchema.js +1 -1
  29. package/admin/src/content-manager/utils/paths.js +1 -1
  30. package/admin/src/content-manager/utils/removePasswordFieldsFromData.js +1 -1
  31. package/admin/src/hooks/useRegenerate/index.js +1 -1
  32. package/admin/src/hooks/useSettingsForm/index.js +3 -3
  33. package/admin/src/hooks/useSettingsForm/reducer.js +3 -1
  34. package/admin/src/hooks/useSettingsMenu/reducer.js +1 -1
  35. package/admin/src/index.js +1 -0
  36. package/admin/src/pages/AuthPage/reducer.js +1 -1
  37. package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +5 -7
  38. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +1 -1
  39. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +1 -1
  40. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +6 -8
  41. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ConditionsModal/utils/createDefaultConditionsForm.js +1 -1
  42. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/Collapse/utils/generateCheckboxesActions.js +2 -1
  43. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/CollapsePropertyMatrix/ActionRow/utils/getRowLabelCheckboxeState.js +2 -1
  44. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/GlobalActions/utils/getRowLabelCheckboxesState.js +1 -1
  45. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/reducer.js +6 -1
  46. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/createDefaultCTFormFromLayout.js +5 -1
  47. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/createDefaultPluginsFormFromLayout.js +1 -2
  48. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/formatContentTypesPermissionToAPI.js +2 -2
  49. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/updateConditionsToFalse.js +4 -1
  50. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/Permissions/utils/updateValues.js +1 -1
  51. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/PluginsAndSettings/SubCategory/utils/formatActions.js +1 -1
  52. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/utils/createArrayOfValues.js +2 -1
  53. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
  54. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +0 -41
  55. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +4 -6
  56. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +0 -1
  57. package/admin/src/pages/SettingsPage/pages/Users/ProtectedEditPage/index.js +2 -2
  58. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/utils/formatData.js +1 -7
  59. package/admin/src/permissions/index.js +1 -1
  60. package/admin/src/translations/en.json +6 -0
  61. package/admin/src/utils/getAttributesToDisplay.js +2 -4
  62. package/admin/src/utils/getExistingActions.js +1 -3
  63. package/admin/src/utils/sortLinks.js +1 -1
  64. package/build/{8580.b0dcf37c.chunk.js → 2263.4c5916f9.chunk.js} +61 -61
  65. package/build/4649.213b8a3b.chunk.js +30 -0
  66. package/build/6985.66cca29c.chunk.js +1 -0
  67. package/build/7259.aefb51e8.chunk.js +1 -0
  68. package/build/{7112.2bf13da3.chunk.js → 9505.dbe702ab.chunk.js} +6 -6
  69. package/build/{Admin-authenticatedApp.5aa08bf5.chunk.js → Admin-authenticatedApp.f50ad423.chunk.js} +3 -3
  70. package/build/{Admin_marketplace.0f6c8ee2.chunk.js → Admin_marketplace.02608d56.chunk.js} +1 -1
  71. package/build/{Admin_profilePage.d2a8f9ab.chunk.js → Admin_profilePage.76afeca0.chunk.js} +1 -1
  72. package/build/Admin_settingsPage.147755cd.chunk.js +9 -0
  73. package/build/admin-app.55dd7921.chunk.js +112 -0
  74. package/build/admin-edit-roles-page.cf543488.chunk.js +216 -0
  75. package/build/admin-edit-users.31c20712.chunk.js +10 -0
  76. package/build/admin-roles-list.489c501f.chunk.js +2 -0
  77. package/build/{admin-users.af8c3123.chunk.js → admin-users.3e111a7d.chunk.js} +1 -1
  78. package/build/{api-tokens-create-page.2a6e22bd.chunk.js → api-tokens-create-page.4328b852.chunk.js} +1 -1
  79. package/build/{api-tokens-edit-page.fa38cd63.chunk.js → api-tokens-edit-page.bce5050f.chunk.js} +1 -1
  80. package/build/content-manager.4480ae88.chunk.js +1137 -0
  81. package/build/content-type-builder-translation-en-json.7961593e.chunk.js +1 -0
  82. package/build/content-type-builder.af9abf1e.chunk.js +126 -0
  83. package/build/en-json.697b4bcf.chunk.js +1 -0
  84. package/build/index.html +1 -1
  85. package/build/main.af8c0f31.js +3790 -0
  86. package/build/review-workflows-settings.7a7dc773.chunk.js +57 -0
  87. package/build/runtime~main.5a95bee6.js +2 -0
  88. package/build/sso-settings-page.272b87c8.chunk.js +1 -0
  89. package/build/{upload-settings.0200561d.chunk.js → upload-settings.0875e973.chunk.js} +1 -1
  90. package/build/upload-translation-fr-json.baab9911.chunk.js +1 -0
  91. package/build/users-advanced-settings-page.1d3c14c7.chunk.js +1 -0
  92. package/build/{users-email-settings-page.a3c80419.chunk.js → users-email-settings-page.e8db68c4.chunk.js} +1 -1
  93. package/build/users-providers-settings-page.14cac425.chunk.js +1 -0
  94. package/build/users-roles-settings-page.2ea4de84.chunk.js +30 -0
  95. package/build/{webhook-edit-page.a2a2b7bb.chunk.js → webhook-edit-page.329141a5.chunk.js} +3 -3
  96. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +92 -0
  97. package/ee/admin/content-manager/pages/EditView/InformationBox/index.js +3 -0
  98. package/ee/admin/hooks/useSettingsMenu/utils/customAdminLinks.js +12 -12
  99. package/ee/admin/hooks/useSettingsMenu/utils/customGlobalLinks.js +21 -13
  100. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +195 -0
  101. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +42 -0
  102. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/AddStage.js +87 -0
  103. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/index.js +1 -0
  104. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +90 -0
  105. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/index.js +1 -0
  106. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +92 -0
  107. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/index.js +1 -0
  108. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
  109. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +35 -0
  110. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +3 -0
  111. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +121 -0
  112. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +25 -0
  113. package/ee/admin/pages/SettingsPage/utils/customRoutes.js +16 -2
  114. package/ee/admin/permissions/customPermissions.js +3 -0
  115. package/ee/server/bootstrap.js +16 -0
  116. package/ee/server/config/admin-actions.js +10 -0
  117. package/ee/server/constants/default-stages.json +14 -0
  118. package/ee/server/constants/default-workflow.json +1 -0
  119. package/ee/server/constants/workflows.js +8 -0
  120. package/ee/server/content-types/index.js +9 -0
  121. package/ee/server/content-types/workflow/index.js +34 -0
  122. package/ee/server/content-types/workflow-stage/index.js +41 -0
  123. package/ee/server/controllers/index.js +2 -0
  124. package/ee/server/controllers/workflows/index.js +36 -0
  125. package/ee/server/controllers/workflows/stages/index.js +95 -0
  126. package/ee/server/index.js +1 -0
  127. package/ee/server/middlewares/review-workflows.js +40 -0
  128. package/ee/server/migrations/review-workflows.js +39 -0
  129. package/ee/server/register.js +7 -0
  130. package/ee/server/routes/index.js +104 -0
  131. package/ee/server/services/index.js +4 -0
  132. package/ee/server/services/review-workflows/entity-service-decorator.js +42 -0
  133. package/ee/server/services/review-workflows/review-workflows.js +175 -0
  134. package/ee/server/services/review-workflows/stages.js +148 -0
  135. package/ee/server/services/review-workflows/workflows.js +25 -0
  136. package/ee/server/utils/index.js +8 -0
  137. package/ee/server/utils/persisted-tables.js +49 -0
  138. package/ee/server/utils/review-workflows.js +25 -0
  139. package/ee/server/utils/test.js +11 -0
  140. package/ee/server/validation/review-workflows.js +24 -0
  141. package/package.json +13 -13
  142. package/server/controllers/transfer/runner.js +2 -4
  143. package/server/routes/transfer.js +4 -13
  144. package/server/services/constants.js +0 -4
  145. package/server/services/permission/permissions-manager/sanitize.js +2 -0
  146. package/server/services/transfer/permission.js +1 -1
  147. package/server/services/transfer/token.js +31 -33
  148. package/server/validation/transfer/token.js +2 -10
  149. package/build/2637.679b590b.chunk.js +0 -1
  150. package/build/5563.451e91ee.chunk.js +0 -30
  151. package/build/7259.7744297b.chunk.js +0 -1
  152. package/build/Admin_settingsPage.489ec4eb.chunk.js +0 -9
  153. package/build/admin-app.4b313104.chunk.js +0 -112
  154. package/build/admin-edit-roles-page.3b196317.chunk.js +0 -216
  155. package/build/admin-edit-users.af3b0f15.chunk.js +0 -10
  156. package/build/admin-roles-list.0ad504a7.chunk.js +0 -2
  157. package/build/content-manager.f530e141.chunk.js +0 -1139
  158. package/build/content-type-builder-translation-en-json.e577d595.chunk.js +0 -1
  159. package/build/content-type-builder.6ecd201d.chunk.js +0 -126
  160. package/build/en-json.01a88a30.chunk.js +0 -1
  161. package/build/main.43b93ff3.js +0 -3843
  162. package/build/runtime~main.a40b1b57.js +0 -2
  163. package/build/sso-settings-page.5a8588ef.chunk.js +0 -1
  164. package/build/upload-translation-fr-json.84429734.chunk.js +0 -1
  165. package/build/users-advanced-settings-page.c0cae03a.chunk.js +0 -1
  166. package/build/users-providers-settings-page.5f86e45c.chunk.js +0 -1
  167. package/build/users-roles-settings-page.b02986df.chunk.js +0 -30
@@ -122,4 +122,108 @@ module.exports = [
122
122
  ],
123
123
  },
124
124
  },
125
+
126
+ // Review workflow
127
+ {
128
+ method: 'GET',
129
+ path: '/review-workflows/workflows',
130
+ handler: 'workflows.find',
131
+ config: {
132
+ middlewares: [enableFeatureMiddleware('review-workflows')],
133
+ policies: [
134
+ 'admin::isAuthenticatedAdmin',
135
+ {
136
+ name: 'admin::hasPermissions',
137
+ config: {
138
+ actions: ['admin::review-workflows.read'],
139
+ },
140
+ },
141
+ ],
142
+ },
143
+ },
144
+ {
145
+ method: 'GET',
146
+ path: '/review-workflows/workflows/:id',
147
+ handler: 'workflows.findById',
148
+ config: {
149
+ middlewares: [enableFeatureMiddleware('review-workflows')],
150
+ policies: [
151
+ 'admin::isAuthenticatedAdmin',
152
+ {
153
+ name: 'admin::hasPermissions',
154
+ config: {
155
+ actions: ['admin::review-workflows.read'],
156
+ },
157
+ },
158
+ ],
159
+ },
160
+ },
161
+ {
162
+ method: 'GET',
163
+ path: '/review-workflows/workflows/:workflow_id/stages',
164
+ handler: 'stages.find',
165
+ config: {
166
+ middlewares: [enableFeatureMiddleware('review-workflows')],
167
+ policies: [
168
+ 'admin::isAuthenticatedAdmin',
169
+ {
170
+ name: 'admin::hasPermissions',
171
+ config: {
172
+ actions: ['admin::review-workflows.read'],
173
+ },
174
+ },
175
+ ],
176
+ },
177
+ },
178
+ {
179
+ method: 'PUT',
180
+ path: '/review-workflows/workflows/:workflow_id/stages',
181
+ handler: 'stages.replace',
182
+ config: {
183
+ middlewares: [enableFeatureMiddleware('review-workflows')],
184
+ policies: [
185
+ 'admin::isAuthenticatedAdmin',
186
+ {
187
+ name: 'admin::hasPermissions',
188
+ config: {
189
+ actions: ['admin::review-workflows.read'],
190
+ },
191
+ },
192
+ ],
193
+ },
194
+ },
195
+ {
196
+ method: 'GET',
197
+ path: '/review-workflows/workflows/:workflow_id/stages/:id',
198
+ handler: 'stages.findById',
199
+ config: {
200
+ middlewares: [enableFeatureMiddleware('review-workflows')],
201
+ policies: [
202
+ 'admin::isAuthenticatedAdmin',
203
+ {
204
+ name: 'admin::hasPermissions',
205
+ config: {
206
+ actions: ['admin::review-workflows.read'],
207
+ },
208
+ },
209
+ ],
210
+ },
211
+ },
212
+ {
213
+ method: 'PUT',
214
+ path: '/content-manager/(collection|single)-types/:model_uid/:id/stage',
215
+ handler: 'stages.updateEntity',
216
+ config: {
217
+ middlewares: [enableFeatureMiddleware('review-workflows')],
218
+ policies: [
219
+ 'admin::isAuthenticatedAdmin',
220
+ {
221
+ name: 'admin::hasPermissions',
222
+ config: {
223
+ actions: ['admin::review-workflows.read'],
224
+ },
225
+ },
226
+ ],
227
+ },
228
+ },
125
229
  ];
@@ -5,4 +5,8 @@ module.exports = {
5
5
  role: require('./role'),
6
6
  user: require('./user'),
7
7
  'seat-enforcement': require('./seat-enforcement'),
8
+ workflows: require('./review-workflows/workflows'),
9
+ stages: require('./review-workflows/stages'),
10
+ 'review-workflows': require('./review-workflows/review-workflows'),
11
+ 'review-workflows-decorator': require('./review-workflows/entity-service-decorator'),
8
12
  };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const { isNil } = require('lodash/fp');
4
+ const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
5
+ const { hasReviewWorkflow, getDefaultWorkflow } = require('../../utils/review-workflows');
6
+
7
+ /**
8
+ * Assigns the entity data to the default workflow stage if no stage is present in the data
9
+ * @param {Object} data
10
+ * @returns
11
+ */
12
+ const getDataWithStage = async (data) => {
13
+ if (!isNil(ENTITY_STAGE_ATTRIBUTE, data)) {
14
+ const defaultWorkflow = await getDefaultWorkflow({ strapi });
15
+ return { ...data, [ENTITY_STAGE_ATTRIBUTE]: defaultWorkflow.stages[0].id };
16
+ }
17
+ return data;
18
+ };
19
+
20
+ /**
21
+ * Decorates the entity service with RW business logic
22
+ * @param {object} service - entity service
23
+ */
24
+ const decorator = (service) => ({
25
+ async create(uid, opts = {}) {
26
+ const hasRW = hasReviewWorkflow({ strapi }, uid);
27
+
28
+ if (!hasRW) {
29
+ return service.create.call(this, uid, opts);
30
+ }
31
+
32
+ const data = await getDataWithStage(opts.data);
33
+ return service.create.call(this, uid, {
34
+ ...opts,
35
+ data,
36
+ });
37
+ },
38
+ });
39
+
40
+ module.exports = () => ({
41
+ decorator,
42
+ });
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ const { set, get, forEach, keys, pickBy, pipe } = require('lodash/fp');
4
+ const { mapAsync } = require('@strapi/utils');
5
+ const { getService } = require('../../utils');
6
+
7
+ const defaultStages = require('../../constants/default-stages.json');
8
+ const defaultWorkflow = require('../../constants/default-workflow.json');
9
+ const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
10
+
11
+ const {
12
+ disableOnContentTypes: disableReviewWorkflows,
13
+ } = require('../../migrations/review-workflows');
14
+ const { getDefaultWorkflow } = require('../../utils/review-workflows');
15
+
16
+ const getContentTypeUIDsWithActivatedReviewWorkflows = pipe([
17
+ // Pick only content-types with reviewWorkflows options set to true
18
+ pickBy(get('options.reviewWorkflows')),
19
+ // Get UIDs
20
+ keys,
21
+ ]);
22
+
23
+ /**
24
+ * Map every stage in the array to be ordered in the relation
25
+ * @param {Object[]} stages
26
+ * @param {number} stages.id
27
+ * @return {Object[]}
28
+ */
29
+ function buildStagesConnectArray(stages) {
30
+ return stages.map((stage, index) => {
31
+ const connect = {
32
+ id: stage.id,
33
+ position: {},
34
+ };
35
+
36
+ if (index === 0) {
37
+ connect.position.start = true;
38
+ } else {
39
+ connect.position.after = stages[index - 1].id;
40
+ }
41
+ return connect;
42
+ });
43
+ }
44
+
45
+ async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
46
+ const wfCount = await workflowsService.count();
47
+ const stagesCount = await stagesService.count();
48
+
49
+ // Check if there is nothing about review-workflow in DB
50
+ // If any, the feature has already been initialized with a workflow and stages
51
+ if (wfCount === 0 && stagesCount === 0) {
52
+ const stages = await stagesService.createMany(defaultStages, { fields: ['id'] });
53
+ const workflow = {
54
+ ...defaultWorkflow,
55
+ stages: {
56
+ connect: buildStagesConnectArray(stages),
57
+ },
58
+ };
59
+
60
+ await workflowsService.create(workflow);
61
+ // If there is any manually activated RW on content-types, we want to migrate the related entities
62
+ await enableReviewWorkflow({ strapi })({ contentTypes: strapi.contentTypes });
63
+ }
64
+ }
65
+
66
+ const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
67
+ writable: true,
68
+ private: false,
69
+ configurable: false,
70
+ visible: false,
71
+ type: 'relation',
72
+ relation: 'morphOne',
73
+ target: 'admin::workflow-stage',
74
+ morphBy: 'related',
75
+ });
76
+
77
+ function extendReviewWorkflowContentTypes({ strapi }) {
78
+ const extendContentType = (contentTypeUID) => {
79
+ strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
80
+ };
81
+ pipe([
82
+ getContentTypeUIDsWithActivatedReviewWorkflows,
83
+ // Iterate over UIDs to extend the content-type
84
+ forEach(extendContentType),
85
+ ])(strapi.contentTypes);
86
+ }
87
+
88
+ /**
89
+ * Enables the review workflow for the given content types.
90
+ * @param {Object} strapi - Strapi instance
91
+ */
92
+ function enableReviewWorkflow({ strapi }) {
93
+ /**
94
+ * @param {Array<string>} contentTypes - Content type UIDs to enable the review workflow for.
95
+ * @returns {Promise<void>} - Promise that resolves when the review workflow is enabled.
96
+ */
97
+ return async ({ contentTypes }) => {
98
+ const defaultWorkflow = await getDefaultWorkflow({ strapi });
99
+ // This is possible if this is the first start of EE, there won't be any workflow in DB before bootstrap
100
+ if (!defaultWorkflow) {
101
+ return;
102
+ }
103
+ const firstStage = defaultWorkflow.stages[0];
104
+
105
+ const up = async (contentTypeUID) => {
106
+ const contentTypeMetadata = strapi.db.metadata.get(contentTypeUID);
107
+ const { target, morphBy } = contentTypeMetadata.attributes[ENTITY_STAGE_ATTRIBUTE];
108
+ const { joinTable } = strapi.db.metadata.get(target).attributes[morphBy];
109
+ const { idColumn, typeColumn } = joinTable.morphColumn;
110
+
111
+ // Execute an SQL query to insert records into the join table mapping the specified content type with the first stage of the default workflow.
112
+ // Only entities that do not have a record in the join table yet are selected.
113
+ const selectStatement = strapi.db
114
+ .getConnection()
115
+ .select({
116
+ [idColumn.name]: 'entity.id',
117
+ field: strapi.db.connection.raw('?', [ENTITY_STAGE_ATTRIBUTE]),
118
+ order: 1,
119
+ [joinTable.joinColumn.name]: firstStage.id,
120
+ [typeColumn.name]: strapi.db.connection.raw('?', [contentTypeUID]),
121
+ })
122
+ .leftJoin(`${joinTable.name} AS jointable`, function joinFunc() {
123
+ this.on('entity.id', '=', `jointable.${idColumn.name}`).andOn(
124
+ `jointable.${typeColumn.name}`,
125
+ '=',
126
+ strapi.db.connection.raw('?', [contentTypeUID])
127
+ );
128
+ })
129
+ .where(`jointable.${idColumn.name}`, null)
130
+ .from(`${contentTypeMetadata.tableName} AS entity`)
131
+ .toSQL();
132
+
133
+ const columnsToInsert = [
134
+ idColumn.name,
135
+ 'field',
136
+ strapi.db.connection.raw('??', ['order']),
137
+ joinTable.joinColumn.name,
138
+ typeColumn.name,
139
+ ];
140
+
141
+ // Insert rows for all entries of the content type that do not have a
142
+ // default stage
143
+ await strapi.db
144
+ .getConnection(joinTable.name)
145
+ .insert(
146
+ strapi.db.connection.raw(
147
+ `(${columnsToInsert.join(',')}) ${selectStatement.sql}`,
148
+ selectStatement.bindings
149
+ )
150
+ );
151
+ };
152
+
153
+ return pipe([
154
+ getContentTypeUIDsWithActivatedReviewWorkflows,
155
+ // Iterate over UIDs to extend the content-type
156
+ (contentTypesUIDs) => mapAsync(contentTypesUIDs, up),
157
+ ])(contentTypes);
158
+ };
159
+ }
160
+
161
+ module.exports = ({ strapi }) => {
162
+ const workflowsService = getService('workflows', { strapi });
163
+ const stagesService = getService('stages', { strapi });
164
+
165
+ return {
166
+ async bootstrap() {
167
+ await initDefaultWorkflow({ workflowsService, stagesService, strapi });
168
+ },
169
+ async register() {
170
+ extendReviewWorkflowContentTypes({ strapi });
171
+ strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
172
+ strapi.hook('strapi::content-types.afterSync').register(disableReviewWorkflows);
173
+ },
174
+ };
175
+ };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ mapAsync,
5
+ errors: { ApplicationError },
6
+ } = require('@strapi/utils');
7
+
8
+ const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
9
+ const { getService } = require('../../utils');
10
+
11
+ module.exports = ({ strapi }) => {
12
+ const workflowsService = getService('workflows', { strapi });
13
+
14
+ return {
15
+ find({ workflowId, populate }) {
16
+ const params = {
17
+ filters: { workflow: workflowId },
18
+ populate,
19
+ };
20
+ return strapi.entityService.findMany(STAGE_MODEL_UID, params);
21
+ },
22
+
23
+ findById(id, { workflowId, populate }) {
24
+ const params = {
25
+ filters: { workflow: workflowId },
26
+ populate,
27
+ };
28
+ return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
29
+ },
30
+
31
+ createMany(stagesList, { fields }) {
32
+ const params = {
33
+ select: fields,
34
+ };
35
+ return Promise.all(
36
+ stagesList.map((stage) =>
37
+ strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
38
+ )
39
+ );
40
+ },
41
+
42
+ update(stageId, stageData) {
43
+ return strapi.entityService.update(STAGE_MODEL_UID, stageId, { data: stageData });
44
+ },
45
+
46
+ delete(stageId) {
47
+ return strapi.entityService.delete(STAGE_MODEL_UID, stageId);
48
+ },
49
+
50
+ count() {
51
+ return strapi.entityService.count(STAGE_MODEL_UID);
52
+ },
53
+
54
+ async replaceWorkflowStages(workflowId, stages) {
55
+ const workflow = await workflowsService.findById(workflowId, { populate: ['stages'] });
56
+
57
+ const { created, updated, deleted } = getDiffBetweenStages(workflow.stages, stages);
58
+
59
+ assertAtLeastOneStageRemain(workflow.stages, { created, deleted });
60
+
61
+ return strapi.db.transaction(async () => {
62
+ const newStages = await this.createMany(created, { fields: ['id'] });
63
+ const stagesIds = stages.map((stage) => stage.id ?? newStages.shift().id);
64
+
65
+ await mapAsync(updated, (stage) => this.update(stage.id, stage));
66
+ await mapAsync(deleted, (stage) => this.delete(stage.id));
67
+ return workflowsService.update(workflowId, {
68
+ stages: stagesIds,
69
+ });
70
+ });
71
+ },
72
+
73
+ /**
74
+ * Update the stage of an entity
75
+ *
76
+ * @param {object} entityInfo
77
+ * @param {number} entityInfo.id - Entity id
78
+ * @param {string} entityInfo.modelUID - the content-type of the entity
79
+ * @param {number} stageId - The id of the stage to assign to the entity
80
+ */
81
+ updateEntity(entityInfo, stageId) {
82
+ return strapi.entityService.update(entityInfo.modelUID, entityInfo.id, {
83
+ data: { [ENTITY_STAGE_ATTRIBUTE]: stageId },
84
+ populate: [ENTITY_STAGE_ATTRIBUTE],
85
+ });
86
+ },
87
+ };
88
+ };
89
+
90
+ /**
91
+ * Compares two arrays of stages and returns an object indicating the differences.
92
+ *
93
+ * The function compares the `id` properties of each stage in `sourceStages` and `comparisonStages` to determine if the stage is present in both arrays.
94
+ * If a stage with the same `id` is found in both arrays but the `name` property is different, the stage is considered updated.
95
+ * If a stage with a particular `id` is only found in `comparisonStages`, it is considered created.
96
+ * If a stage with a particular `id` is only found in `sourceStages`, it is considered deleted.
97
+ *
98
+ * @typedef {{id: Number, name: String, workflow: Number}} Stage
99
+ * @typedef {{created: Stage[], updated: Stage[], deleted: Stage[]}} DiffStages
100
+ *
101
+ * The DiffStages object has three properties: `created`, `updated`, and `deleted`.
102
+ * `created` is an array of stages that are in `comparisonStages` but not in `sourceStages`.
103
+ * `updated` is an array of stages that have different names in `comparisonStages` and `sourceStages`.
104
+ * `deleted` is an array of stages that are in `sourceStages` but not in `comparisonStages`.
105
+ *
106
+ * @param {Stage[]} sourceStages
107
+ * @param {Stage[]} comparisonStages
108
+ * @returns { DiffStages }
109
+ */
110
+ function getDiffBetweenStages(sourceStages, comparisonStages) {
111
+ const result = comparisonStages.reduce(
112
+ (acc, stageToCompare) => {
113
+ const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
114
+
115
+ if (!srcStage) {
116
+ acc.created.push(stageToCompare);
117
+ } else if (srcStage.name !== stageToCompare.name) {
118
+ acc.updated.push(stageToCompare);
119
+ }
120
+ return acc;
121
+ },
122
+ { created: [], updated: [] }
123
+ );
124
+
125
+ result.deleted = sourceStages.filter(
126
+ (srcStage) => !comparisonStages.some((cmpStage) => cmpStage.id === srcStage.id)
127
+ );
128
+
129
+ return result;
130
+ }
131
+
132
+ /**
133
+ * Asserts that at least one stage remains in the workflow after applying deletions and additions.
134
+ *
135
+ * @param {Array} workflowStages - An array of stages in the current workflow.
136
+ * @param {Object} diffStages - An object containing the stages to be deleted and created.
137
+ * @param {Array} diffStages.deleted - An array of stages that are planned to be deleted from the workflow.
138
+ * @param {Array} diffStages.created - An array of stages that are planned to be created in the workflow.
139
+ *
140
+ * @throws {ApplicationError} If the number of remaining stages in the workflow after applying deletions and additions is less than 1.
141
+ */
142
+ function assertAtLeastOneStageRemain(workflowStages, diffStages) {
143
+ const remainingStagesCount =
144
+ workflowStages.length - diffStages.deleted.length + diffStages.created.length;
145
+ if (remainingStagesCount < 1) {
146
+ throw new ApplicationError('At least one stage must remain in the workflow.');
147
+ }
148
+ }
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
4
+
5
+ module.exports = ({ strapi }) => ({
6
+ find(opts) {
7
+ return strapi.entityService.findMany(WORKFLOW_MODEL_UID, opts);
8
+ },
9
+
10
+ findById(id, opts) {
11
+ return strapi.entityService.findOne(WORKFLOW_MODEL_UID, id, opts);
12
+ },
13
+
14
+ create(workflowData) {
15
+ return strapi.entityService.create(WORKFLOW_MODEL_UID, { data: workflowData });
16
+ },
17
+
18
+ count() {
19
+ return strapi.entityService.count(WORKFLOW_MODEL_UID);
20
+ },
21
+
22
+ update(id, workflowData) {
23
+ return strapi.entityService.update(WORKFLOW_MODEL_UID, id, { data: workflowData });
24
+ },
25
+ });
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const getService = (name, { strapi } = { strapi: global.strapi }) => {
4
+ return strapi.service(`admin::${name}`);
5
+ };
6
+ module.exports = {
7
+ getService,
8
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Finds all tables in the database that start with a prefix
5
+ * @param {string} prefix
6
+ * @returns {Array}
7
+ */
8
+ const findTablesThatStartWithPrefix = async (prefix) => {
9
+ const tables = await strapi.db.dialect.schemaInspector.getTables();
10
+ return tables.filter((tableName) => tableName.startsWith(prefix));
11
+ };
12
+
13
+ /**
14
+ * Get all reserved table names from the core store
15
+ * @returns {Array}
16
+ */
17
+ const getPersistedTables = async () =>
18
+ (await strapi.store.get({
19
+ type: 'core',
20
+ key: 'persisted_tables',
21
+ })) ?? [];
22
+
23
+ /**
24
+ * Add all table names that start with a prefix to the reserved tables in
25
+ * core store
26
+ * @param {string} tableNamePrefix
27
+ */
28
+
29
+ const persistTablesWithPrefix = async (tableNamePrefix) => {
30
+ const persistedTables = await getPersistedTables();
31
+ const tableNames = await findTablesThatStartWithPrefix(tableNamePrefix);
32
+ const notReservedTableNames = tableNames.filter((name) => !persistedTables.includes(name));
33
+
34
+ if (!notReservedTableNames.length) {
35
+ return;
36
+ }
37
+
38
+ persistedTables.push(...notReservedTableNames);
39
+ await strapi.store.set({
40
+ type: 'core',
41
+ key: 'persisted_tables',
42
+ value: persistedTables,
43
+ });
44
+ };
45
+
46
+ module.exports = {
47
+ persistTablesWithPrefix,
48
+ findTablesThatStartWithPrefix,
49
+ };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
4
+
5
+ /**
6
+ * Checks if a content type has review workflows enabled.
7
+ * @param {string|Object} contentType - Either the modelUID of the content type, or the content type object.
8
+ * @returns {boolean} Whether review workflows are enabled for the specified content type.
9
+ */
10
+ function hasReviewWorkflow({ strapi }, contentType) {
11
+ if (typeof contentType === 'string') {
12
+ // If the input is a string, assume it's the modelUID of the content type and retrieve the corresponding object.
13
+ return hasReviewWorkflow({ strapi }, strapi.getModel(contentType));
14
+ }
15
+ // Otherwise, assume it's the content type object itself and return its `reviewWorkflows` option if it exists.
16
+ return contentType?.options?.reviewWorkflows || false;
17
+ }
18
+ // TODO To be refactored when multiple workflows are added
19
+ const getDefaultWorkflow = async ({ strapi }) =>
20
+ strapi.query(WORKFLOW_MODEL_UID).findOne({ populate: ['stages'] });
21
+
22
+ module.exports = {
23
+ hasReviewWorkflow,
24
+ getDefaultWorkflow,
25
+ };
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Execute a test suite only if the condition is true
5
+ * @return Jest.Describe
6
+ */
7
+ const describeOnCondition = (bool) => (bool ? describe : describe.skip);
8
+
9
+ module.exports = {
10
+ describeOnCondition,
11
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { yup, validateYupSchema } = require('@strapi/utils');
4
+
5
+ const stageObject = yup.object().shape({
6
+ id: yup.number().integer().min(1),
7
+ name: yup.string().max(255).required(),
8
+ });
9
+
10
+ const validateUpdateStagesSchema = yup.array().of(stageObject).required();
11
+ const validateUpdateStageOnEntity = yup
12
+ .object()
13
+ .shape({
14
+ id: yup.number().integer().min(1).required(),
15
+ })
16
+ .required();
17
+
18
+ module.exports = {
19
+ validateUpdateStages: validateYupSchema(validateUpdateStagesSchema, {
20
+ strict: false,
21
+ stripUnknown: true,
22
+ }),
23
+ validateUpdateStageOnEntity: validateYupSchema(validateUpdateStageOnEntity),
24
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/admin",
3
- "version": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
3
+ "version": "4.10.0-beta.0",
4
4
  "description": "Strapi Admin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,15 +46,15 @@
46
46
  "@casl/ability": "^5.4.3",
47
47
  "@fingerprintjs/fingerprintjs": "3.3.6",
48
48
  "@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
49
- "@strapi/babel-plugin-switch-ee-ce": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
50
- "@strapi/data-transfer": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
51
- "@strapi/design-system": "1.6.5",
52
- "@strapi/helper-plugin": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
53
- "@strapi/icons": "1.6.5",
54
- "@strapi/permissions": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
55
- "@strapi/provider-audit-logs-local": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
56
- "@strapi/typescript-utils": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
57
- "@strapi/utils": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
49
+ "@strapi/babel-plugin-switch-ee-ce": "4.10.0-beta.0",
50
+ "@strapi/data-transfer": "4.10.0-beta.0",
51
+ "@strapi/design-system": "1.6.6",
52
+ "@strapi/helper-plugin": "4.10.0-beta.0",
53
+ "@strapi/icons": "1.6.6",
54
+ "@strapi/permissions": "4.10.0-beta.0",
55
+ "@strapi/provider-audit-logs-local": "4.10.0-beta.0",
56
+ "@strapi/typescript-utils": "4.10.0-beta.0",
57
+ "@strapi/utils": "4.10.0-beta.0",
58
58
  "axios": "1.3.4",
59
59
  "babel-loader": "^9.1.2",
60
60
  "babel-plugin-styled-components": "2.0.2",
@@ -115,7 +115,7 @@
115
115
  "react-error-boundary": "3.1.4",
116
116
  "react-fast-compare": "^3.2.0",
117
117
  "react-helmet": "^6.1.0",
118
- "react-intl": "6.2.8",
118
+ "react-intl": "6.3.2",
119
119
  "react-is": "^17.0.2",
120
120
  "react-query": "3.24.3",
121
121
  "react-redux": "8.0.5",
@@ -134,7 +134,7 @@
134
134
  "typescript": "4.6.2",
135
135
  "webpack": "^5.76.0",
136
136
  "webpack-cli": "^5.0.1",
137
- "webpack-dev-server": "^4.11.1",
137
+ "webpack-dev-server": "^4.13.1",
138
138
  "webpackbar": "^5.0.2",
139
139
  "yup": "^0.32.9"
140
140
  },
@@ -166,5 +166,5 @@
166
166
  }
167
167
  }
168
168
  },
169
- "gitHead": "366eb8a0d0f06935914854c6d9c4b3fe859468e0"
169
+ "gitHead": "1519ef0e56d27b738f24fc88223797651ad47aaf"
170
170
  }