@strapi/admin 4.11.3 → 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 (176) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +2 -2
  2. package/admin/src/constants.js +83 -83
  3. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +8 -5
  4. package/admin/src/content-manager/components/Inputs/index.js +3 -47
  5. package/admin/src/content-manager/components/RelationInput/RelationInput.js +99 -178
  6. package/admin/src/content-manager/components/RelationInput/components/Option.js +17 -15
  7. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +2 -2
  8. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +34 -37
  9. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +0 -27
  10. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +93 -14
  11. package/admin/src/content-manager/pages/ListView/index.js +65 -47
  12. package/admin/src/content-manager/pages/ListView/utils/buildValidGetParams.js +30 -0
  13. package/admin/src/content-manager/pages/ListView/utils/index.js +1 -1
  14. package/admin/src/content-manager/utils/mergeMetasWithSchema.js +5 -1
  15. package/admin/src/hooks/index.js +0 -1
  16. package/admin/src/hooks/useAdminUsers/useAdminUsers.js +3 -3
  17. package/admin/src/hooks/useEnterprise/useEnterprise.js +4 -4
  18. package/admin/src/pages/App/index.js +28 -23
  19. package/admin/src/pages/AuthPage/components/Register/index.js +5 -1
  20. package/admin/src/pages/ProfilePage/index.js +6 -1
  21. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +15 -1
  22. package/admin/src/pages/SettingsPage/components/Tokens/TokenBox/index.js +1 -1
  23. package/admin/src/pages/SettingsPage/pages/Roles/ProtectedEditPage/index.js +4 -10
  24. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +2 -2
  25. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/components/WebhookForm/utils/makeWebhookValidationSchema.js +11 -5
  26. package/admin/src/translations/ca.json +1 -0
  27. package/admin/src/translations/en.json +4 -1
  28. package/admin/src/translations/es.json +5 -0
  29. package/admin/src/translations/fr.json +1 -0
  30. package/admin/src/translations/zh-Hans.json +1 -1
  31. package/build/0cd5f8915b265d5b1856.png +0 -0
  32. package/build/2799.cf9b491f.chunk.js +1 -0
  33. package/build/4485.d3c6dd1d.chunk.js +6 -0
  34. package/build/539.865446c0.chunk.js +1 -0
  35. package/build/{5542.64b623c9.chunk.js → 5542.c62d0daf.chunk.js} +1 -1
  36. package/build/{5563.86f9aa9c.chunk.js → 5563.a146acac.chunk.js} +2 -2
  37. package/build/7018.f3dad3c1.chunk.js +1 -0
  38. package/build/7259.0e25ab5d.chunk.js +1 -0
  39. package/build/9465.d8fc1377.chunk.js +112 -0
  40. package/build/{6405.27e1bee5.chunk.js → 970.89601f27.chunk.js} +2 -2
  41. package/build/9944.29289a16.chunk.js +26 -0
  42. package/build/Admin-authenticatedApp.9d3afb79.chunk.js +79 -0
  43. package/build/{Admin_settingsPage.4069bb8a.chunk.js → Admin_settingsPage.074655f6.chunk.js} +13 -13
  44. package/build/admin-app.3ede71ad.chunk.js +61 -0
  45. package/build/{admin-edit-roles-page.2040034a.chunk.js → admin-edit-roles-page.3fdd6b9d.chunk.js} +11 -11
  46. package/build/admin-edit-users.78552758.chunk.js +10 -0
  47. package/build/admin-users.c23322fc.chunk.js +11 -0
  48. package/build/api-tokens-list-page.a103f526.chunk.js +16 -0
  49. package/build/audit-logs-settings-page.37fe915c.chunk.js +1 -0
  50. package/build/ca-json.1fed5d8b.chunk.js +1 -0
  51. package/build/content-manager.08541eeb.chunk.js +1094 -0
  52. package/build/{content-type-builder-list-view.0c3ceb4e.chunk.js → content-type-builder-list-view.a200a358.chunk.js} +1 -1
  53. package/build/content-type-builder-translation-en-json.38e20391.chunk.js +1 -0
  54. package/build/content-type-builder.de22f7c9.chunk.js +166 -0
  55. package/build/{email-settings-page.6b38222d.chunk.js → email-settings-page.45695daa.chunk.js} +1 -1
  56. package/build/en-json.fb9f6ddd.chunk.js +1 -0
  57. package/build/es-json.42096084.chunk.js +1 -0
  58. package/build/fr-json.69789980.chunk.js +1 -0
  59. package/build/{i18n-settings-page.ff863f20.chunk.js → i18n-settings-page.29308d0b.chunk.js} +1 -1
  60. package/build/index.html +1 -1
  61. package/build/main.a8ede50d.js +2927 -0
  62. package/build/review-workflows-settings-create-view.56f61e18.chunk.js +1 -0
  63. package/build/review-workflows-settings-edit-view.912bc9c0.chunk.js +1 -0
  64. package/build/review-workflows-settings-list-view.cf6a08d3.chunk.js +56 -0
  65. package/build/runtime~main.5e9bf4b3.js +2 -0
  66. package/build/sso-settings-page.0cdb96a6.chunk.js +1 -0
  67. package/build/transfer-tokens-list-page.7237443d.chunk.js +16 -0
  68. package/build/{upload-settings.43cf16cd.chunk.js → upload-settings.cb6c14c3.chunk.js} +1 -1
  69. package/build/{upload.72f8f8fc.chunk.js → upload.7e629643.chunk.js} +2 -2
  70. package/build/users-advanced-settings-page.750b1f76.chunk.js +9 -0
  71. package/build/{users-email-settings-page.33359797.chunk.js → users-email-settings-page.e9bcd865.chunk.js} +1 -1
  72. package/build/{users-providers-settings-page.1e7a4a71.chunk.js → users-providers-settings-page.a94253e9.chunk.js} +1 -1
  73. package/build/{users-roles-settings-page.235378b6.chunk.js → users-roles-settings-page.d286426a.chunk.js} +5 -5
  74. package/build/webhook-edit-page.77ef4f1a.chunk.js +33 -0
  75. package/build/{zh-Hans-json.4cfef87d.chunk.js → zh-Hans-json.fada6f40.chunk.js} +1 -1
  76. package/ee/admin/constants.js +14 -14
  77. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +84 -30
  78. package/ee/admin/content-manager/{components/DynamicTable/CellContent/ReviewWorkflowsStage → pages/ListView/ReviewWorkflowsColumn}/ReviewWorkflowsStageEE.js +7 -2
  79. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +24 -0
  80. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  81. package/ee/admin/hooks/useLicenseLimitNotification/index.js +17 -6
  82. package/ee/admin/hooks/useLicenseLimits/index.js +1 -32
  83. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +44 -0
  84. package/ee/admin/pages/SettingsPage/constants.js +25 -1
  85. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +6 -4
  86. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +6 -4
  87. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js +4 -9
  88. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +19 -4
  89. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/Layout.js +65 -0
  90. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Layout/index.js +1 -0
  91. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/LimitsModal.js +111 -0
  92. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/assets/balloon.png +0 -0
  93. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal/index.js +3 -0
  94. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +21 -0
  95. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +1 -0
  96. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +4 -4
  97. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +110 -0
  98. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/index.js +1 -0
  99. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -1
  100. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +13 -19
  101. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +246 -0
  102. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +13 -0
  103. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +269 -0
  104. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +13 -0
  105. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +382 -0
  106. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +13 -0
  107. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +53 -23
  108. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +43 -28
  109. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/index.js +4 -9
  110. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +9 -2
  111. package/ee/server/config/admin-actions.js +24 -0
  112. package/ee/server/constants/default-stages.json +8 -4
  113. package/ee/server/constants/default-workflow.json +3 -1
  114. package/ee/server/constants/workflows.js +10 -1
  115. package/ee/server/content-types/workflow/index.js +10 -0
  116. package/ee/server/content-types/workflow-stage/index.js +3 -1
  117. package/ee/server/controllers/admin.js +1 -0
  118. package/ee/server/controllers/workflows/index.js +135 -8
  119. package/ee/server/controllers/workflows/stages/index.js +38 -38
  120. package/ee/server/migrations/review-workflows-content-types.js +29 -0
  121. package/ee/server/migrations/review-workflows-deleted-ct-in-workflows.js +39 -0
  122. package/ee/server/migrations/review-workflows-stage-attribute.js +49 -0
  123. package/ee/server/migrations/review-workflows-stages-color.js +2 -2
  124. package/ee/server/migrations/review-workflows-workflow-name.js +21 -0
  125. package/ee/server/register.js +12 -2
  126. package/ee/server/routes/review-workflows.js +44 -10
  127. package/ee/server/services/index.js +1 -0
  128. package/ee/server/services/review-workflows/entity-service-decorator.js +28 -24
  129. package/ee/server/services/review-workflows/review-workflows.js +45 -53
  130. package/ee/server/services/review-workflows/stages.js +84 -46
  131. package/ee/server/services/review-workflows/validation.js +60 -0
  132. package/ee/server/services/review-workflows/workflows/content-types.js +80 -0
  133. package/ee/server/services/review-workflows/workflows/index.js +207 -0
  134. package/ee/server/utils/review-workflows.js +30 -25
  135. package/ee/server/validation/review-workflows.js +49 -10
  136. package/package.json +13 -14
  137. package/server/content-types/User.js +10 -0
  138. package/server/strategies/api-token.js +9 -5
  139. package/server/strategies/data-transfer.js +9 -5
  140. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -2
  141. package/admin/src/content-manager/components/RelationInput/components/Relation.js +0 -53
  142. package/admin/src/content-manager/pages/ListView/utils/buildQueryString.js +0 -36
  143. package/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js +0 -4
  144. package/admin/src/hooks/useLicenseLimits/index.js +0 -3
  145. package/admin/src/pages/App/utils/index.js +0 -3
  146. package/admin/src/pages/App/utils/unique-identifier.js +0 -12
  147. package/build/1799.84268ad3.chunk.js +0 -33
  148. package/build/5932.6a23b88c.chunk.js +0 -1
  149. package/build/7018.98feed67.chunk.js +0 -1
  150. package/build/7259.fb69d4bf.chunk.js +0 -1
  151. package/build/Admin-authenticatedApp.69855f1b.chunk.js +0 -79
  152. package/build/admin-app.fea867af.chunk.js +0 -61
  153. package/build/admin-edit-users.53e4290a.chunk.js +0 -10
  154. package/build/admin-users.3b12dca2.chunk.js +0 -11
  155. package/build/api-tokens-list-page.201fb67a.chunk.js +0 -16
  156. package/build/audit-logs-settings-page.b07ad202.chunk.js +0 -1
  157. package/build/ca-json.43e14418.chunk.js +0 -1
  158. package/build/content-manager.66cec770.chunk.js +0 -1094
  159. package/build/content-type-builder-translation-en-json.f592325b.chunk.js +0 -1
  160. package/build/content-type-builder.e1b6d13b.chunk.js +0 -166
  161. package/build/en-json.f5fa476a.chunk.js +0 -1
  162. package/build/es-json.715b6fd8.chunk.js +0 -1
  163. package/build/fr-json.73494bf5.chunk.js +0 -1
  164. package/build/main.83edb3fc.js +0 -2926
  165. package/build/review-workflows-settings.93808ae0.chunk.js +0 -110
  166. package/build/runtime~main.20c3cac6.js +0 -2
  167. package/build/sso-settings-page.35b67909.chunk.js +0 -1
  168. package/build/transfer-tokens-list-page.217573c3.chunk.js +0 -16
  169. package/build/users-advanced-settings-page.1911adf5.chunk.js +0 -9
  170. package/build/webhook-edit-page.1ee02c4b.chunk.js +0 -33
  171. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +0 -58
  172. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +0 -3
  173. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ProtectedPage.js +0 -20
  174. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +0 -204
  175. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +0 -3
  176. package/ee/server/services/review-workflows/workflows.js +0 -25
@@ -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;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const { difference, keys } = require('lodash/fp');
4
+ const { mapAsync } = require('@strapi/utils');
5
+ const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
6
+ const { getWorkflowContentTypeFilter } = require('../utils/review-workflows');
7
+
8
+ /**
9
+ * @param {Object} oldContentTypes
10
+ * @param {Object} contentTypes
11
+ * @return {Promise<void>}
12
+ */
13
+ async function migrateDeletedCTInWorkflows({ oldContentTypes, contentTypes }) {
14
+ const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes)) ?? [];
15
+
16
+ if (deletedContentTypes.length) {
17
+ await mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
18
+ const workflow = await strapi.query(WORKFLOW_MODEL_UID).findOne({
19
+ select: ['id', 'contentTypes'],
20
+ where: {
21
+ contentTypes: getWorkflowContentTypeFilter({ strapi }, deletedContentTypeUID),
22
+ },
23
+ });
24
+
25
+ if (workflow) {
26
+ await strapi.query(WORKFLOW_MODEL_UID).update({
27
+ where: { id: workflow.id },
28
+ data: {
29
+ contentTypes: workflow.contentTypes.filter(
30
+ (contentTypeUID) => contentTypeUID !== deletedContentTypeUID
31
+ ),
32
+ },
33
+ });
34
+ }
35
+ });
36
+ }
37
+ }
38
+
39
+ module.exports = migrateDeletedCTInWorkflows;
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const semver = require('semver');
4
+ const { getOr } = require('lodash/fp');
5
+ const { mapAsync } = require('@strapi/utils');
6
+ const { STAGE_MODEL_UID } = require('../constants/workflows');
7
+ const { findTables } = require('../utils/persisted-tables');
8
+
9
+ function checkVersionThreshold(startVersion, currentVersion, thresholdVersion) {
10
+ return semver.gte(currentVersion, thresholdVersion) && semver.lt(startVersion, thresholdVersion);
11
+ }
12
+
13
+ async function migrateStageAttribute({ oldContentTypes, contentTypes }) {
14
+ const getRWVersion = getOr('0.0.0', `${STAGE_MODEL_UID}.options.version`);
15
+ const oldRWVersion = getRWVersion(oldContentTypes);
16
+ const currentRWVersion = getRWVersion(contentTypes);
17
+
18
+ const migrationNeeded = checkVersionThreshold(oldRWVersion, currentRWVersion, '1.1.0');
19
+
20
+ if (migrationNeeded) {
21
+ const oldAttributeTableName = 'strapi_review_workflows_stage';
22
+ const newAttributeTableName = 'strapi_stage';
23
+ const tables = await findTables({ strapi }, new RegExp(oldAttributeTableName));
24
+
25
+ await mapAsync(tables, async (tableName) => {
26
+ const newTableName = tableName.replace(oldAttributeTableName, newAttributeTableName);
27
+ const alreadyHasNextTable = await strapi.db.connection.schema.hasTable(newTableName);
28
+
29
+ // The table can be already created but empty. In order to rename the old one, we need to drop the previously created empty one.
30
+ if (alreadyHasNextTable) {
31
+ const dataInTable = await strapi.db.connection(newTableName).select().limit(1);
32
+ if (!dataInTable.length) {
33
+ await strapi.db.connection.schema.dropTable(newTableName);
34
+ }
35
+ }
36
+
37
+ try {
38
+ await strapi.db.connection.schema.renameTable(tableName, newTableName);
39
+ } catch (e) {
40
+ strapi.log.warn(
41
+ `An error occurred during the migration of ${tableName} table to ${newTableName}.\nIf ${newTableName} already exists, migration can't be done automatically.`
42
+ );
43
+ strapi.log.warn(e.message);
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ module.exports = migrateStageAttribute;
@@ -5,10 +5,10 @@ const { STAGE_DEFAULT_COLOR } = require('../constants/workflows');
5
5
  async function migrateReviewWorkflowStagesColor({ oldContentTypes, contentTypes }) {
6
6
  // Look for CT's color attribute
7
7
  const hadColor = !!oldContentTypes?.['admin::workflow-stage']?.attributes?.color;
8
- const hasColor = !!contentTypes['admin::workflow-stage']?.attributes?.color;
8
+ const hasColor = !!contentTypes?.['admin::workflow-stage']?.attributes?.color;
9
9
 
10
10
  // Add the default stage color if color attribute was added
11
- if (!hadColor || hasColor) {
11
+ if (!hadColor && hasColor) {
12
12
  await strapi.query('admin::workflow-stage').updateMany({
13
13
  data: {
14
14
  color: STAGE_DEFAULT_COLOR,
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
4
+ const defaultWorkflow = require('../constants/default-workflow.json');
5
+
6
+ async function migrateReviewWorkflowName({ oldContentTypes, contentTypes }) {
7
+ // Look for RW name attribute
8
+ const hadName = !!oldContentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.name;
9
+ const hasName = !!contentTypes?.[WORKFLOW_MODEL_UID]?.attributes?.name;
10
+
11
+ // Add the default workflow name if name attribute was added
12
+ if (!hadName && hasName) {
13
+ await strapi.query(WORKFLOW_MODEL_UID).updateMany({
14
+ data: {
15
+ name: defaultWorkflow.name,
16
+ },
17
+ });
18
+ }
19
+ }
20
+
21
+ module.exports = migrateReviewWorkflowName;
@@ -4,6 +4,10 @@ const { features } = require('@strapi/strapi/lib/utils/ee');
4
4
  const executeCERegister = require('../../server/register');
5
5
  const migrateAuditLogsTable = require('./migrations/audit-logs-table');
6
6
  const migrateReviewWorkflowStagesColor = require('./migrations/review-workflows-stages-color');
7
+ const migrateReviewWorkflowName = require('./migrations/review-workflows-workflow-name');
8
+ const migrateWorkflowsContentTypes = require('./migrations/review-workflows-content-types');
9
+ const migrateStageAttribute = require('./migrations/review-workflows-stage-attribute');
10
+ const migrateDeletedCTInWorkflows = require('./migrations/review-workflows-deleted-ct-in-workflows');
7
11
  const createAuditLogsService = require('./services/audit-logs');
8
12
  const reviewWorkflowsMiddlewares = require('./middlewares/review-workflows');
9
13
  const { getService } = require('./utils');
@@ -18,11 +22,17 @@ module.exports = async ({ strapi }) => {
18
22
  await auditLogsService.register();
19
23
  }
20
24
  if (features.isEnabled('review-workflows')) {
21
- strapi.hook('strapi::content-types.afterSync').register(migrateReviewWorkflowStagesColor);
25
+ strapi.hook('strapi::content-types.beforeSync').register(migrateStageAttribute);
26
+ strapi
27
+ .hook('strapi::content-types.afterSync')
28
+ .register(migrateReviewWorkflowStagesColor)
29
+ .register(migrateReviewWorkflowName)
30
+ .register(migrateWorkflowsContentTypes)
31
+ .register(migrateDeletedCTInWorkflows);
22
32
  const reviewWorkflowService = getService('review-workflows');
23
33
 
24
34
  reviewWorkflowsMiddlewares.contentTypeMiddleware(strapi);
25
- await reviewWorkflowService.register();
35
+ await reviewWorkflowService.register(features.get('review-workflows'));
26
36
  }
27
37
  await executeCERegister({ strapi });
28
38
  };
@@ -7,9 +7,9 @@ module.exports = {
7
7
  routes: [
8
8
  // Review workflow
9
9
  {
10
- method: 'GET',
10
+ method: 'POST',
11
11
  path: '/review-workflows/workflows',
12
- handler: 'workflows.find',
12
+ handler: 'workflows.create',
13
13
  config: {
14
14
  middlewares: [enableFeatureMiddleware('review-workflows')],
15
15
  policies: [
@@ -17,16 +17,50 @@ module.exports = {
17
17
  {
18
18
  name: 'admin::hasPermissions',
19
19
  config: {
20
- actions: ['admin::review-workflows.read'],
20
+ actions: ['admin::review-workflows.create'],
21
21
  },
22
22
  },
23
23
  ],
24
24
  },
25
25
  },
26
26
  {
27
- method: 'GET',
27
+ method: 'PUT',
28
28
  path: '/review-workflows/workflows/:id',
29
- handler: 'workflows.findById',
29
+ handler: 'workflows.update',
30
+ config: {
31
+ middlewares: [enableFeatureMiddleware('review-workflows')],
32
+ policies: [
33
+ 'admin::isAuthenticatedAdmin',
34
+ {
35
+ name: 'admin::hasPermissions',
36
+ config: {
37
+ actions: ['admin::review-workflows.update'],
38
+ },
39
+ },
40
+ ],
41
+ },
42
+ },
43
+ {
44
+ method: 'DELETE',
45
+ path: '/review-workflows/workflows/:id',
46
+ handler: 'workflows.delete',
47
+ config: {
48
+ middlewares: [enableFeatureMiddleware('review-workflows')],
49
+ policies: [
50
+ 'admin::isAuthenticatedAdmin',
51
+ {
52
+ name: 'admin::hasPermissions',
53
+ config: {
54
+ actions: ['admin::review-workflows.delete'],
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ },
60
+ {
61
+ method: 'GET',
62
+ path: '/review-workflows/workflows',
63
+ handler: 'workflows.find',
30
64
  config: {
31
65
  middlewares: [enableFeatureMiddleware('review-workflows')],
32
66
  policies: [
@@ -42,8 +76,8 @@ module.exports = {
42
76
  },
43
77
  {
44
78
  method: 'GET',
45
- path: '/review-workflows/workflows/:workflow_id/stages',
46
- handler: 'stages.find',
79
+ path: '/review-workflows/workflows/:id',
80
+ handler: 'workflows.findById',
47
81
  config: {
48
82
  middlewares: [enableFeatureMiddleware('review-workflows')],
49
83
  policies: [
@@ -58,9 +92,9 @@ module.exports = {
58
92
  },
59
93
  },
60
94
  {
61
- method: 'PUT',
95
+ method: 'GET',
62
96
  path: '/review-workflows/workflows/:workflow_id/stages',
63
- handler: 'stages.replace',
97
+ handler: 'stages.find',
64
98
  config: {
65
99
  middlewares: [enableFeatureMiddleware('review-workflows')],
66
100
  policies: [
@@ -102,7 +136,7 @@ module.exports = {
102
136
  {
103
137
  name: 'admin::hasPermissions',
104
138
  config: {
105
- actions: ['admin::review-workflows.read'],
139
+ actions: ['admin::review-workflows.update'],
106
140
  },
107
141
  },
108
142
  ],
@@ -9,6 +9,7 @@ module.exports = {
9
9
  workflows: require('./review-workflows/workflows'),
10
10
  stages: require('./review-workflows/stages'),
11
11
  'review-workflows': require('./review-workflows/review-workflows'),
12
+ 'review-workflows-validation': require('./review-workflows/validation'),
12
13
  'review-workflows-decorator': require('./review-workflows/entity-service-decorator'),
13
14
  'review-workflows-metrics': require('./review-workflows/metrics'),
14
15
  };
@@ -3,17 +3,16 @@
3
3
  const { isNil, isNull } = require('lodash/fp');
4
4
  const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
5
5
  const { WORKFLOW_UPDATE_STAGE } = require('../../constants/webhookEvents');
6
- const { hasReviewWorkflow, getDefaultWorkflow } = require('../../utils/review-workflows');
6
+ const { getService } = require('../../utils');
7
7
 
8
8
  /**
9
9
  * Assigns the entity data to the default workflow stage if no stage is present in the data
10
10
  * @param {Object} data
11
11
  * @returns
12
12
  */
13
- const getDataWithStage = async (data) => {
13
+ const getDataWithStage = async (workflow, data) => {
14
14
  if (!isNil(ENTITY_STAGE_ATTRIBUTE, data)) {
15
- const defaultWorkflow = await getDefaultWorkflow({ strapi });
16
- return { ...data, [ENTITY_STAGE_ATTRIBUTE]: defaultWorkflow.stages[0].id };
15
+ return { ...data, [ENTITY_STAGE_ATTRIBUTE]: workflow.stages[0].id };
17
16
  }
18
17
  return data;
19
18
  };
@@ -43,22 +42,18 @@ const getEntityStage = async (uid, id) => {
43
42
  */
44
43
  const decorator = (service) => ({
45
44
  async create(uid, opts = {}) {
46
- const hasRW = hasReviewWorkflow({ strapi }, uid);
45
+ const workflow = await getService('workflows').getAssignedWorkflow(uid, {
46
+ populate: 'stages',
47
+ });
47
48
 
48
- if (!hasRW) {
49
+ if (!workflow) {
49
50
  return service.create.call(this, uid, opts);
50
51
  }
51
52
 
52
- const data = await getDataWithStage(opts.data);
53
+ const data = await getDataWithStage(workflow, opts.data);
53
54
  return service.create.call(this, uid, { ...opts, data });
54
55
  },
55
56
  async update(uid, entityId, opts = {}) {
56
- const hasRW = hasReviewWorkflow({ strapi }, uid);
57
-
58
- if (!hasRW) {
59
- return service.update.call(this, uid, entityId, opts);
60
- }
61
-
62
57
  // Prevents the stage from being set to null
63
58
  const data = { ...opts.data };
64
59
  if (isNull(data[ENTITY_STAGE_ATTRIBUTE])) {
@@ -69,22 +64,31 @@ const decorator = (service) => ({
69
64
  const previousStage = await getEntityStage(uid, entityId);
70
65
 
71
66
  const updatedEntity = await service.update.call(this, uid, entityId, { ...opts, data });
72
- if (
73
- previousStage?.workflow?.id &&
74
- previousStage?.id &&
75
- previousStage.id !== data[ENTITY_STAGE_ATTRIBUTE]
76
- ) {
77
- const webhookPayload = {
78
- entityId,
67
+ const updatedStage = updatedEntity[ENTITY_STAGE_ATTRIBUTE];
68
+
69
+ if (previousStage?.id && previousStage.id !== updatedStage.id) {
70
+ const model = strapi.getModel(uid);
71
+
72
+ strapi.eventHub.emit(WORKFLOW_UPDATE_STAGE, {
73
+ model: model.modelName,
74
+ uid: model.uid,
75
+ entity: {
76
+ id: entityId,
77
+ },
79
78
  workflow: {
80
79
  id: previousStage.workflow.id,
81
80
  stages: {
82
- from: previousStage.id,
83
- to: data[ENTITY_STAGE_ATTRIBUTE],
81
+ from: {
82
+ id: previousStage.id,
83
+ name: previousStage.name,
84
+ },
85
+ to: {
86
+ id: updatedStage.id,
87
+ name: updatedStage.name,
88
+ },
84
89
  },
85
90
  },
86
- };
87
- await strapi.entityService.emitEvent(uid, WORKFLOW_UPDATE_STAGE, webhookPayload);
91
+ });
88
92
  }
89
93
 
90
94
  return updatedEntity;
@@ -1,41 +1,57 @@
1
1
  'use strict';
2
2
 
3
- const { set, forEach, pipe, map } = require('lodash/fp');
4
- const { mapAsync } = require('@strapi/utils');
3
+ const { filter, set, forEach, pipe, map, stubTrue, cond, defaultsDeep } = require('lodash/fp');
5
4
  const { getService } = require('../../utils');
6
- const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
5
+ const { getVisibleContentTypesUID, hasStageAttribute } = require('../../utils/review-workflows');
7
6
 
8
7
  const defaultStages = require('../../constants/default-stages.json');
9
8
  const defaultWorkflow = require('../../constants/default-workflow.json');
10
- const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
9
+ const {
10
+ ENTITY_STAGE_ATTRIBUTE,
11
+ MAX_WORKFLOWS,
12
+ MAX_STAGES_PER_WORKFLOW,
13
+ } = require('../../constants/workflows');
11
14
 
12
- const { getDefaultWorkflow } = require('../../utils/review-workflows');
13
15
  const { persistTables, removePersistedTablesWithSuffix } = require('../../utils/persisted-tables');
14
16
  const webhookEvents = require('../../constants/webhookEvents');
15
17
 
16
- async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
18
+ const MAX_DB_TABLE_NAME_LEN = 63; // Postgres limit
19
+ // The longest index name that Strapi can create is prefixed with '_strapi_stage_links_inv_fk', so the content type name should be no longer than this.
20
+ const MAX_JOIN_TABLE_NAME_SUFFIX =
21
+ 1 /* _ */ + ENTITY_STAGE_ATTRIBUTE.length + '_links_inv_fk'.length;
22
+ const MAX_CONTENT_TYPE_NAME_LEN = MAX_DB_TABLE_NAME_LEN - MAX_JOIN_TABLE_NAME_SUFFIX;
23
+
24
+ const DEFAULT_OPTIONS = {
25
+ workflows: MAX_WORKFLOWS,
26
+ stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW,
27
+ };
28
+
29
+ async function initDefaultWorkflow({ workflowsService, stagesService }) {
17
30
  const wfCount = await workflowsService.count();
18
31
  const stagesCount = await stagesService.count();
19
32
 
20
33
  // Check if there is nothing about review-workflow in DB
21
34
  // If any, the feature has already been initialized with a workflow and stages
22
35
  if (wfCount === 0 && stagesCount === 0) {
23
- const stages = await stagesService.createMany(defaultStages, { fields: ['id'] });
24
36
  const workflow = {
25
37
  ...defaultWorkflow,
26
- stages: {
27
- connect: stages.map((stage) => stage.id),
28
- },
38
+ stages: defaultStages,
29
39
  };
30
40
 
31
- await workflowsService.create(workflow);
32
- // If there is any manually activated RW on content-types, we want to migrate the related entities
33
- await enableReviewWorkflow({ strapi })({ contentTypes: strapi.contentTypes });
41
+ await workflowsService.create({ data: workflow });
34
42
  }
35
43
  }
36
44
 
37
45
  function extendReviewWorkflowContentTypes({ strapi }) {
38
46
  const extendContentType = (contentTypeUID) => {
47
+ const assertContentTypeCompatibility = (contentType) =>
48
+ contentType.collectionName.length <= MAX_CONTENT_TYPE_NAME_LEN;
49
+ const incompatibleContentTypeAlert = (contentType) => {
50
+ strapi.log.warn(
51
+ `Review Workflow cannot be activated for the content type with the name '${contentType.info.displayName}' because the name exceeds the maximum length of ${MAX_CONTENT_TYPE_NAME_LEN} characters.`
52
+ );
53
+ return contentType;
54
+ };
39
55
  const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
40
56
  writable: true,
41
57
  private: false,
@@ -46,49 +62,21 @@ function extendReviewWorkflowContentTypes({ strapi }) {
46
62
  relation: 'oneToOne',
47
63
  target: 'admin::workflow-stage',
48
64
  });
49
- strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
65
+
66
+ const extendContentTypeIfCompatible = cond([
67
+ [assertContentTypeCompatibility, setStageAttribute],
68
+ [stubTrue, incompatibleContentTypeAlert],
69
+ ]);
70
+ strapi.container.get('content-types').extend(contentTypeUID, extendContentTypeIfCompatible);
50
71
  };
72
+
51
73
  pipe([
52
- getContentTypeUIDsWithActivatedReviewWorkflows,
74
+ getVisibleContentTypesUID,
53
75
  // Iterate over UIDs to extend the content-type
54
76
  forEach(extendContentType),
55
77
  ])(strapi.contentTypes);
56
78
  }
57
79
 
58
- /**
59
- * Enables the review workflow for the given content types.
60
- * @param {Object} strapi - Strapi instance
61
- */
62
- function enableReviewWorkflow({ strapi }) {
63
- /**
64
- * @param {Array<string>} contentTypes - Content type UIDs to enable the review workflow for.
65
- * @returns {Promise<void>} - Promise that resolves when the review workflow is enabled.
66
- */
67
- return async ({ contentTypes }) => {
68
- const defaultWorkflow = await getDefaultWorkflow({ strapi });
69
- // This is possible if this is the first start of EE, there won't be any workflow in DB before bootstrap
70
- if (!defaultWorkflow) {
71
- return;
72
- }
73
- const firstStage = defaultWorkflow.stages[0];
74
- const stagesService = getService('stages', { strapi });
75
-
76
- const updateEntitiesStage = async (contentTypeUID) => {
77
- // Update CT entities stage
78
- return stagesService.updateEntitiesStage(contentTypeUID, {
79
- fromStageId: null,
80
- toStageId: firstStage.id,
81
- });
82
- };
83
-
84
- return pipe([
85
- getContentTypeUIDsWithActivatedReviewWorkflows,
86
- // Iterate over UIDs to extend the content-type
87
- (contentTypesUIDs) => mapAsync(contentTypesUIDs, updateEntitiesStage),
88
- ])(contentTypes);
89
- };
90
- }
91
-
92
80
  function persistStagesJoinTables({ strapi }) {
93
81
  return async ({ contentTypes }) => {
94
82
  const getStageTableToPersist = (contentTypeUID) => {
@@ -99,12 +87,13 @@ function persistStagesJoinTables({ strapi }) {
99
87
  };
100
88
 
101
89
  const joinTablesToPersist = pipe([
102
- getContentTypeUIDsWithActivatedReviewWorkflows,
90
+ getVisibleContentTypesUID,
91
+ filter((uid) => hasStageAttribute(contentTypes[uid])),
103
92
  map(getStageTableToPersist),
104
93
  ])(contentTypes);
105
94
 
106
95
  // TODO: Instead of removing all the tables, we should only remove the ones that are not in the joinTablesToPersist
107
- await removePersistedTablesWithSuffix('_strapi_review_workflows_stage_links');
96
+ await removePersistedTablesWithSuffix('_strapi_stage_links');
108
97
  await persistTables(joinTablesToPersist);
109
98
  };
110
99
  }
@@ -117,16 +106,19 @@ const registerWebhookEvents = async ({ strapi }) =>
117
106
  module.exports = ({ strapi }) => {
118
107
  const workflowsService = getService('workflows', { strapi });
119
108
  const stagesService = getService('stages', { strapi });
109
+ const workflowsValidationService = getService('review-workflows-validation', { strapi });
120
110
 
121
111
  return {
122
112
  async bootstrap() {
123
113
  await registerWebhookEvents({ strapi });
124
114
  await initDefaultWorkflow({ workflowsService, stagesService, strapi });
125
115
  },
126
- async register() {
116
+ async register({ options } = { options: {} }) {
127
117
  extendReviewWorkflowContentTypes({ strapi });
128
- strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
129
118
  strapi.hook('strapi::content-types.afterSync').register(persistStagesJoinTables({ strapi }));
119
+
120
+ const reviewWorkflowsOptions = defaultsDeep(DEFAULT_OPTIONS, options);
121
+ workflowsValidationService.register(reviewWorkflowsOptions);
130
122
  },
131
123
  };
132
124
  };