@strapi/admin 4.10.0-beta.0 → 4.10.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 (229) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.js +14 -0
  3. package/admin/src/components/LanguageProvider/index.js +1 -1
  4. package/admin/src/components/LocalesProvider/__mocks__/useLocalesProvider.js +7 -0
  5. package/admin/src/components/LocalesProvider/index.js +2 -3
  6. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +3 -6
  7. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +2 -0
  8. package/admin/src/content-manager/components/DynamicTable/index.js +11 -29
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +12 -6
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/findAllAndReplace.js +10 -3
  11. package/admin/src/content-manager/components/InputUID/endActionStyle.js +4 -13
  12. package/admin/src/content-manager/components/InputUID/index.js +95 -72
  13. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +8 -2
  14. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +4 -8
  15. package/admin/src/content-manager/pages/App/LeftMenu/index.js +56 -35
  16. package/admin/src/content-manager/pages/App/actions.js +19 -7
  17. package/admin/src/content-manager/pages/App/constants.js +3 -3
  18. package/admin/src/content-manager/pages/App/index.js +5 -4
  19. package/admin/src/content-manager/pages/App/reducer.js +7 -6
  20. package/admin/src/content-manager/pages/App/selectors.js +3 -0
  21. package/admin/src/content-manager/pages/App/{useModels.js → useContentManagerInitData.js} +29 -28
  22. package/admin/src/content-manager/pages/App/utils/generateModelsLinks.js +2 -2
  23. package/admin/src/content-manager/pages/App/utils/getContentTypeLinks.js +17 -15
  24. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +6 -5
  25. package/admin/src/content-manager/pages/EditSettingsView/index.js +4 -0
  26. package/admin/src/content-manager/pages/EditSettingsView/reducer.js +12 -7
  27. package/admin/src/content-manager/pages/EditSettingsView/utils/layout.js +1 -30
  28. package/admin/src/content-manager/pages/EditView/DeleteLink/index.js +5 -11
  29. package/admin/src/content-manager/pages/EditView/index.js +2 -7
  30. package/admin/src/content-manager/pages/EditViewLayoutManager/reducer.js +4 -4
  31. package/admin/src/content-manager/pages/ListSettingsView/index.js +1 -0
  32. package/admin/src/hooks/useConfigurations/__mocks__/index.js +7 -0
  33. package/admin/src/hooks/useFetchMarketplacePlugins/utils/api.js +7 -8
  34. package/admin/src/hooks/useFetchMarketplaceProviders/utils/api.js +5 -0
  35. package/admin/src/hooks/useFetchPermissionsLayout/index.js +24 -15
  36. package/admin/src/hooks/useFetchRole/index.js +37 -30
  37. package/admin/src/hooks/useModels/index.js +22 -12
  38. package/admin/src/hooks/useRegenerate/index.js +12 -7
  39. package/admin/src/hooks/useSettingsForm/index.js +14 -6
  40. package/admin/src/pages/App/index.js +19 -24
  41. package/admin/src/pages/AuthPage/components/Register/index.js +46 -38
  42. package/admin/src/pages/HomePage/SocialLinks.js +1 -1
  43. package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +3 -3
  44. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +42 -9
  45. package/admin/src/pages/MarketplacePage/components/NpmPackagesPagination/index.js +26 -0
  46. package/admin/src/pages/MarketplacePage/components/OfflineLayout/index.js +45 -0
  47. package/admin/src/pages/MarketplacePage/index.js +80 -175
  48. package/admin/src/pages/MarketplacePage/utils/useMarketplaceData.js +85 -0
  49. package/admin/src/pages/SettingsPage/components/Tokens/FormHead/index.js +4 -0
  50. package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +5 -3
  51. package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +7 -5
  52. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js +1 -3
  53. package/admin/src/pages/SettingsPage/pages/Roles/CreatePage/index.js +6 -12
  54. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js +6 -10
  55. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/components/RoleRow/index.js +12 -4
  56. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +21 -2
  57. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +41 -0
  58. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +53 -9
  59. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +1 -0
  60. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +27 -5
  61. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js +18 -33
  62. package/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js +41 -41
  63. package/admin/src/translations/en.json +52 -50
  64. package/admin/src/translations/fr.json +89 -1
  65. package/admin/src/translations/ru.json +51 -19
  66. package/build/1387.84b454d3.chunk.js +1 -0
  67. package/build/1657.45231968.chunk.js +168 -0
  68. package/build/3081.bcf9a12f.chunk.js +108 -0
  69. package/build/462.8fff7f3b.chunk.js +71 -0
  70. package/build/4628.20631dd1.chunk.js +1 -0
  71. package/build/5542.b8240e3f.chunk.js +70 -0
  72. package/build/5563.905daa13.chunk.js +79 -0
  73. package/build/6404.68405699.chunk.js +100 -0
  74. package/build/7259.b7d00cea.chunk.js +1 -0
  75. package/build/8694.6522968d.chunk.js +247 -0
  76. package/build/Admin-authenticatedApp.b036fe95.chunk.js +79 -0
  77. package/build/Admin_InternalErrorPage.15c6bf07.chunk.js +1 -0
  78. package/build/Admin_homePage.f9309c6d.chunk.js +73 -0
  79. package/build/Admin_marketplace.56bc1008.chunk.js +31 -0
  80. package/build/Admin_pluginsPage.f6b52ee9.chunk.js +6 -0
  81. package/build/Admin_profilePage.9112cffc.chunk.js +15 -0
  82. package/build/Admin_settingsPage.4604a16c.chunk.js +79 -0
  83. package/build/Upload_ConfigureTheView.eaaec495.chunk.js +1 -0
  84. package/build/admin-app.014adc27.chunk.js +110 -0
  85. package/build/admin-edit-roles-page.8a4063f7.chunk.js +280 -0
  86. package/build/admin-edit-users.7e14d85f.chunk.js +10 -0
  87. package/build/admin-roles-list.329c1f63.chunk.js +31 -0
  88. package/build/admin-users.d02de059.chunk.js +34 -0
  89. package/build/api-tokens-create-page.97595e12.chunk.js +1 -0
  90. package/build/api-tokens-edit-page.cd36e30e.chunk.js +1 -0
  91. package/build/api-tokens-list-page.6757c7b9.chunk.js +16 -0
  92. package/build/audit-logs-settings-page.19d90bda.chunk.js +76 -0
  93. package/build/content-manager.84f81966.chunk.js +1130 -0
  94. package/build/content-type-builder-list-view.9c2c020c.chunk.js +214 -0
  95. package/build/{content-type-builder-translation-de-json.0979cccb.chunk.js → content-type-builder-translation-de-json.393a76c0.chunk.js} +1 -1
  96. package/build/{content-type-builder-translation-dk-json.e05583e9.chunk.js → content-type-builder-translation-dk-json.fbd39bb7.chunk.js} +1 -1
  97. package/build/content-type-builder-translation-en-json.446b611d.chunk.js +1 -0
  98. package/build/{content-type-builder-translation-es-json.fe4daad8.chunk.js → content-type-builder-translation-es-json.9288474b.chunk.js} +1 -1
  99. package/build/{content-type-builder-translation-fr-json.b1eb52f6.chunk.js → content-type-builder-translation-fr-json.d35e269c.chunk.js} +1 -1
  100. package/build/{content-type-builder-translation-id-json.ee3b36bb.chunk.js → content-type-builder-translation-id-json.f0513929.chunk.js} +1 -1
  101. package/build/{content-type-builder-translation-it-json.13b3c26a.chunk.js → content-type-builder-translation-it-json.aaf16753.chunk.js} +1 -1
  102. package/build/{content-type-builder-translation-ko-json.8a274be5.chunk.js → content-type-builder-translation-ko-json.8fe21a7f.chunk.js} +1 -1
  103. package/build/{content-type-builder-translation-ms-json.2d29c1e0.chunk.js → content-type-builder-translation-ms-json.3b5d2d3e.chunk.js} +1 -1
  104. package/build/{content-type-builder-translation-nl-json.40bbc562.chunk.js → content-type-builder-translation-nl-json.225ef5d3.chunk.js} +1 -1
  105. package/build/{content-type-builder-translation-pl-json.24a34349.chunk.js → content-type-builder-translation-pl-json.92f36be2.chunk.js} +1 -1
  106. package/build/{content-type-builder-translation-pt-BR-json.97f71a9d.chunk.js → content-type-builder-translation-pt-BR-json.3bd10f89.chunk.js} +1 -1
  107. package/build/{content-type-builder-translation-ru-json.54d11230.chunk.js → content-type-builder-translation-ru-json.9bfe47ce.chunk.js} +1 -1
  108. package/build/{content-type-builder-translation-sk-json.626c9493.chunk.js → content-type-builder-translation-sk-json.d03cc18a.chunk.js} +1 -1
  109. package/build/{content-type-builder-translation-sv-json.59f5e1e5.chunk.js → content-type-builder-translation-sv-json.d23dcd32.chunk.js} +1 -1
  110. package/build/{content-type-builder-translation-th-json.6fe3ed55.chunk.js → content-type-builder-translation-th-json.7ad256e2.chunk.js} +1 -1
  111. package/build/{content-type-builder-translation-tr-json.cea4d226.chunk.js → content-type-builder-translation-tr-json.926f6191.chunk.js} +1 -1
  112. package/build/{content-type-builder-translation-uk-json.c4524247.chunk.js → content-type-builder-translation-uk-json.7bf19546.chunk.js} +1 -1
  113. package/build/{content-type-builder-translation-zh-json.faedd610.chunk.js → content-type-builder-translation-zh-json.ad24dbeb.chunk.js} +1 -1
  114. package/build/content-type-builder.68af11d2.chunk.js +126 -0
  115. package/build/email-settings-page.1095e1ab.chunk.js +10 -0
  116. package/build/en-json.c7fc79af.chunk.js +1 -0
  117. package/build/fr-json.5947cf63.chunk.js +1 -0
  118. package/build/{highlight.js.26ef649f.chunk.js → highlight.js.28a1547e.chunk.js} +2 -2
  119. package/build/i18n-settings-page.d95b32df.chunk.js +60 -0
  120. package/build/index.html +1 -1
  121. package/build/main.841e0dcb.js +2280 -0
  122. package/build/review-workflows-settings.f7890c40.chunk.js +106 -0
  123. package/build/ru-json.e0662702.chunk.js +1 -0
  124. package/build/{runtime~main.5a95bee6.js → runtime~main.965f8af8.js} +1 -1
  125. package/build/sso-settings-page.1dd4886e.chunk.js +1 -0
  126. package/build/transfer-tokens-create-page.ec2ca215.chunk.js +1 -0
  127. package/build/transfer-tokens-edit-page.22bf28e5.chunk.js +1 -0
  128. package/build/transfer-tokens-list-page.cf8c77f2.chunk.js +16 -0
  129. package/build/upload-settings.945fdcfa.chunk.js +13 -0
  130. package/build/{upload-translation-th-json.3847dae0.chunk.js → upload-translation-th-json.98d35574.chunk.js} +1 -1
  131. package/build/upload.a86b1054.chunk.js +33 -0
  132. package/build/users-advanced-settings-page.5b5a9baa.chunk.js +8 -0
  133. package/build/users-email-settings-page.e5506eb4.chunk.js +23 -0
  134. package/build/users-providers-settings-page.e32089c2.chunk.js +28 -0
  135. package/build/users-roles-settings-page.20656f92.chunk.js +30 -0
  136. package/build/webhook-edit-page.a3b62049.chunk.js +75 -0
  137. package/build/webhook-list-page.ca38eeef.chunk.js +71 -0
  138. package/{admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStage.js → ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js} +2 -2
  139. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +47 -0
  140. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +3 -0
  141. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +64 -18
  142. package/ee/admin/hooks/useAuthProviders/index.js +28 -29
  143. package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
  144. package/ee/admin/pages/AuthResponse/index.js +7 -3
  145. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +6 -3
  146. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js +15 -5
  147. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +52 -45
  148. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +9 -5
  149. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +2 -2
  150. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +2 -2
  151. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +2 -1
  152. package/ee/server/content-types/workflow/index.js +0 -3
  153. package/ee/server/content-types/workflow-stage/index.js +0 -5
  154. package/ee/server/controllers/authentication/middlewares.js +2 -1
  155. package/ee/server/controllers/workflows/stages/index.js +8 -1
  156. package/ee/server/register.js +3 -1
  157. package/ee/server/services/audit-logs.js +75 -16
  158. package/ee/server/services/index.js +1 -0
  159. package/ee/server/services/review-workflows/entity-service-decorator.js +17 -5
  160. package/ee/server/services/review-workflows/metrics.js +24 -0
  161. package/ee/server/services/review-workflows/review-workflows.js +44 -94
  162. package/ee/server/services/review-workflows/stages.js +136 -17
  163. package/ee/server/utils/persisted-tables.js +116 -22
  164. package/ee/server/utils/review-workflows.js +9 -0
  165. package/jest.config.front.js +1 -6
  166. package/package.json +25 -23
  167. package/server/controllers/transfer/runner.js +4 -2
  168. package/server/middlewares/data-transfer.js +4 -1
  169. package/server/routes/transfer.js +13 -4
  170. package/server/services/constants.js +4 -0
  171. package/server/services/transfer/permission.js +1 -1
  172. package/server/services/transfer/token.js +33 -31
  173. package/server/strategies/api-token.js +8 -1
  174. package/server/validation/transfer/token.js +10 -2
  175. package/webpack.config.js +6 -2
  176. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +0 -1
  177. package/admin/src/content-manager/pages/App/LeftMenu/utils/index.js +0 -1
  178. package/admin/src/content-manager/pages/App/LeftMenu/utils/matchByTitle.js +0 -24
  179. package/build/2263.4c5916f9.chunk.js +0 -98
  180. package/build/4049.64715f20.chunk.js +0 -1
  181. package/build/4649.213b8a3b.chunk.js +0 -30
  182. package/build/6985.66cca29c.chunk.js +0 -1
  183. package/build/7259.aefb51e8.chunk.js +0 -1
  184. package/build/8469.853c822b.chunk.js +0 -1
  185. package/build/9505.dbe702ab.chunk.js +0 -14
  186. package/build/9816.01ee964f.chunk.js +0 -2
  187. package/build/Admin-authenticatedApp.f50ad423.chunk.js +0 -79
  188. package/build/Admin_InternalErrorPage.4ad8b0df.chunk.js +0 -1
  189. package/build/Admin_homePage.1411fb7c.chunk.js +0 -68
  190. package/build/Admin_marketplace.02608d56.chunk.js +0 -22
  191. package/build/Admin_pluginsPage.15e3b0fd.chunk.js +0 -1
  192. package/build/Admin_profilePage.76afeca0.chunk.js +0 -15
  193. package/build/Admin_settingsPage.147755cd.chunk.js +0 -9
  194. package/build/Upload_ConfigureTheView.34dde278.chunk.js +0 -1
  195. package/build/admin-app.55dd7921.chunk.js +0 -112
  196. package/build/admin-edit-roles-page.cf543488.chunk.js +0 -216
  197. package/build/admin-edit-users.31c20712.chunk.js +0 -10
  198. package/build/admin-roles-list.489c501f.chunk.js +0 -2
  199. package/build/admin-users.3e111a7d.chunk.js +0 -11
  200. package/build/api-tokens-create-page.4328b852.chunk.js +0 -1
  201. package/build/api-tokens-edit-page.bce5050f.chunk.js +0 -1
  202. package/build/api-tokens-list-page.93f24348.chunk.js +0 -16
  203. package/build/audit-logs-settings-page.7be97e82.chunk.js +0 -1
  204. package/build/content-manager.4480ae88.chunk.js +0 -1137
  205. package/build/content-type-builder-list-view.cf38fe2f.chunk.js +0 -191
  206. package/build/content-type-builder-translation-en-json.7961593e.chunk.js +0 -1
  207. package/build/content-type-builder.af9abf1e.chunk.js +0 -126
  208. package/build/email-settings-page.4bdbef9a.chunk.js +0 -3
  209. package/build/en-json.697b4bcf.chunk.js +0 -1
  210. package/build/fr-json.f66c3211.chunk.js +0 -1
  211. package/build/i18n-settings-page.2bb5be96.chunk.js +0 -1
  212. package/build/main.af8c0f31.js +0 -3790
  213. package/build/review-workflows-settings.7a7dc773.chunk.js +0 -57
  214. package/build/ru-json.6a01cea6.chunk.js +0 -1
  215. package/build/sso-settings-page.272b87c8.chunk.js +0 -1
  216. package/build/transfer-tokens-create-page.a1f14bb1.chunk.js +0 -1
  217. package/build/transfer-tokens-edit-page.00ee1c74.chunk.js +0 -1
  218. package/build/transfer-tokens-list-page.ce37354b.chunk.js +0 -16
  219. package/build/upload-settings.0875e973.chunk.js +0 -1
  220. package/build/upload.c7da1611.chunk.js +0 -13
  221. package/build/users-advanced-settings-page.1d3c14c7.chunk.js +0 -1
  222. package/build/users-email-settings-page.e8db68c4.chunk.js +0 -1
  223. package/build/users-providers-settings-page.14cac425.chunk.js +0 -1
  224. package/build/users-roles-settings-page.2ea4de84.chunk.js +0 -30
  225. package/build/webhook-edit-page.329141a5.chunk.js +0 -23
  226. package/build/webhook-list-page.029957a4.chunk.js +0 -1
  227. package/ee/server/migrations/review-workflows.js +0 -39
  228. package/ee/server/utils/test.js +0 -11
  229. /package/admin/src/{content-manager/components/InputUID/useDebounce.js → hooks/useDebounce/index.js} +0 -0
@@ -12,27 +12,7 @@ const customOperators = [
12
12
  },
13
13
  ];
14
14
 
15
- const getDisplayedFilters = ({ formatMessage, users }) => {
16
- const getDisplaynameFromUser = (user) => {
17
- if (user.username) {
18
- return user.username;
19
- }
20
- if (user.firstname && user.lastname) {
21
- return formatMessage(
22
- {
23
- id: 'Settings.permissions.auditLogs.user.fullname',
24
- defaultMessage: '{firstname} {lastname}',
25
- },
26
- {
27
- firstname: user.firstname,
28
- lastname: user.lastname,
29
- }
30
- );
31
- }
32
-
33
- return user.email;
34
- };
35
-
15
+ const getDisplayedFilters = ({ formatMessage, users, canReadUsers }) => {
36
16
  const actionOptions = Object.keys(actionTypes).map((action) => {
37
17
  return {
38
18
  label: formatMessage(
@@ -46,17 +26,7 @@ const getDisplayedFilters = ({ formatMessage, users }) => {
46
26
  };
47
27
  });
48
28
 
49
- const userOptions =
50
- users &&
51
- users.results.map((user) => {
52
- return {
53
- label: getDisplaynameFromUser(user),
54
- // Combobox expects a string value
55
- customValue: user.id.toString(),
56
- };
57
- });
58
-
59
- return [
29
+ const filters = [
60
30
  {
61
31
  name: 'action',
62
32
  metadatas: {
@@ -80,20 +50,57 @@ const getDisplayedFilters = ({ formatMessage, users }) => {
80
50
  },
81
51
  fieldSchema: { type: 'datetime' },
82
52
  },
83
- {
84
- name: 'user',
85
- metadatas: {
86
- customOperators,
87
- label: formatMessage({
88
- id: 'Settings.permissions.auditLogs.user',
89
- defaultMessage: 'User',
90
- }),
91
- options: userOptions,
92
- customInput: ComboboxFilter,
93
- },
94
- fieldSchema: { type: 'relation', mainField: { name: 'id' } },
95
- },
96
53
  ];
54
+
55
+ if (canReadUsers && users) {
56
+ const getDisplayNameFromUser = (user) => {
57
+ if (user.username) {
58
+ return user.username;
59
+ }
60
+
61
+ if (user.firstname && user.lastname) {
62
+ return formatMessage(
63
+ {
64
+ id: 'Settings.permissions.auditLogs.user.fullname',
65
+ defaultMessage: '{firstname} {lastname}',
66
+ },
67
+ {
68
+ firstname: user.firstname,
69
+ lastname: user.lastname,
70
+ }
71
+ );
72
+ }
73
+
74
+ return user.email;
75
+ };
76
+
77
+ const userOptions = users.results.map((user) => {
78
+ return {
79
+ label: getDisplayNameFromUser(user),
80
+ // Combobox expects a string value
81
+ customValue: user.id.toString(),
82
+ };
83
+ });
84
+
85
+ return [
86
+ ...filters,
87
+ {
88
+ name: 'user',
89
+ metadatas: {
90
+ customOperators,
91
+ label: formatMessage({
92
+ id: 'Settings.permissions.auditLogs.user',
93
+ defaultMessage: 'User',
94
+ }),
95
+ options: userOptions,
96
+ customInput: ComboboxFilter,
97
+ },
98
+ fieldSchema: { type: 'relation', mainField: { name: 'id' } },
99
+ },
100
+ ];
101
+ }
102
+
103
+ return filters;
97
104
  };
98
105
 
99
106
  export default getDisplayedFilters;
@@ -86,10 +86,10 @@ export function ReviewWorkflowsPage() {
86
86
  };
87
87
 
88
88
  const submitForm = async () => {
89
- setIsConfirmDeleteDialogOpen(false);
90
-
91
89
  await updateWorkflowStages(currentWorkflow.id, currentWorkflow.stages);
92
- refetchWorkflow();
90
+ await refetchWorkflow();
91
+
92
+ setIsConfirmDeleteDialogOpen(false);
93
93
  };
94
94
 
95
95
  const handleConfirmDeleteDialog = async () => {
@@ -122,7 +122,8 @@ export function ReviewWorkflowsPage() {
122
122
 
123
123
  useEffect(() => {
124
124
  trackUsage('didViewWorkflow');
125
- }, [trackUsage]);
125
+ // eslint-disable-next-line react-hooks/exhaustive-deps
126
+ }, []);
126
127
 
127
128
  return (
128
129
  <CheckPagePermissions permissions={adminPermissions.settings['review-workflows'].main}>
@@ -143,6 +144,9 @@ export function ReviewWorkflowsPage() {
143
144
  type="submit"
144
145
  size="M"
145
146
  disabled={!currentWorkflowIsDirty}
147
+ // if the confirm dialog is open the loading state is on
148
+ // the confirm button already
149
+ loading={!isConfirmDeleteDialogOpen && isLoading}
146
150
  >
147
151
  {formatMessage({
148
152
  id: 'global.save',
@@ -181,7 +185,7 @@ export function ReviewWorkflowsPage() {
181
185
  bodyText={{
182
186
  id: 'Settings.review-workflows.page.delete.confirm.body',
183
187
  defaultMessage:
184
- 'All entries assigned to deleted stages will be moved to the first stage. Are you sure you want to save this?',
188
+ 'All entries assigned to deleted stages will be moved to the previous stage. Are you sure you want to save?',
185
189
  }}
186
190
  isConfirmButtonLoading={isLoading}
187
191
  isOpen={isConfirmDeleteDialogOpen}
@@ -45,7 +45,7 @@ function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
45
45
  action={
46
46
  canDelete ? (
47
47
  <IconButton
48
- backgroundColor="transparent"
48
+ background="transparent"
49
49
  noBorder
50
50
  onClick={() => dispatch(deleteStage(id))}
51
51
  label={formatMessage({
@@ -57,7 +57,7 @@ function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
57
57
  ) : null
58
58
  }
59
59
  />
60
- <AccordionContent padding={6} background="neutral0">
60
+ <AccordionContent padding={6} background="neutral0" hasRadius>
61
61
  <Grid gap={4}>
62
62
  <GridItem col={6}>
63
63
  <TextInput
@@ -22,8 +22,8 @@ export function useReviewWorkflows(workflowId) {
22
22
  }
23
23
  }
24
24
 
25
- function refetchWorkflow() {
26
- client.refetchQueries(workflowQueryKey);
25
+ async function refetchWorkflow() {
26
+ await client.refetchQueries(workflowQueryKey);
27
27
  }
28
28
 
29
29
  const workflows = useQuery(workflowQueryKey, fetchWorkflows);
@@ -35,6 +35,7 @@ export function reducer(state = initialState, action) {
35
35
  draft.serverState.workflows = workflows;
36
36
  draft.serverState.currentWorkflow = defaultWorkflow;
37
37
  draft.clientState.currentWorkflow.data = defaultWorkflow;
38
+ draft.clientState.currentWorkflow.hasDeletedServerStages = false;
38
39
  }
39
40
  break;
40
41
  }
@@ -97,7 +98,7 @@ export function reducer(state = initialState, action) {
97
98
  if (state.clientState.currentWorkflow.data) {
98
99
  draft.clientState.currentWorkflow.isDirty = !isEqual(
99
100
  current(draft.clientState.currentWorkflow).data,
100
- state.serverState.currentWorkflow
101
+ draft.serverState.currentWorkflow
101
102
  );
102
103
  }
103
104
  });
@@ -20,9 +20,6 @@ module.exports = {
20
20
  },
21
21
  },
22
22
  attributes: {
23
- uid: {
24
- type: 'string',
25
- },
26
23
  stages: {
27
24
  type: 'relation',
28
25
  target: 'admin::workflow-stage',
@@ -31,11 +31,6 @@ module.exports = {
31
31
  inversedBy: 'stages',
32
32
  configurable: false,
33
33
  },
34
- related: {
35
- type: 'relation',
36
- relation: 'morphToMany',
37
- configurable: false,
38
- },
39
34
  },
40
35
  },
41
36
  };
@@ -95,13 +95,14 @@ const redirectWithAuth = (ctx) => {
95
95
  params: { provider },
96
96
  } = ctx;
97
97
  const redirectUrls = utils.getPrefixedRedirectUrls();
98
+ const domain = strapi.config.get('server.admin.auth.domain');
98
99
  const { user } = ctx.state;
99
100
 
100
101
  const jwt = getService('token').createJwtToken(user);
101
102
 
102
103
  const isProduction = strapi.config.get('environment') === 'production';
103
104
 
104
- const cookiesOptions = { httpOnly: false, secure: isProduction, overwrite: true };
105
+ const cookiesOptions = { httpOnly: false, secure: isProduction, overwrite: true, domain };
105
106
 
106
107
  const sanitizedUser = getService('user').sanitizeUser(user);
107
108
  strapi.eventHub.emit('admin.auth.success', { user: sanitizedUser, provider });
@@ -46,6 +46,11 @@ module.exports = {
46
46
  };
47
47
  },
48
48
 
49
+ /**
50
+ * Replace all stages in a workflow
51
+ * @param {import('koa').BaseContext} ctx - koa context
52
+ *
53
+ */
49
54
  async replace(ctx) {
50
55
  const { workflow_id: workflowId } = ctx.params;
51
56
  const stagesService = getService('stages');
@@ -90,6 +95,8 @@ module.exports = {
90
95
  // TODO When multiple workflows are possible, check if the stage is part of the right one
91
96
  // Didn't need this today as their can only be one workflow
92
97
 
93
- ctx.body = await stagesService.updateEntity({ id: entityId, modelUID }, stageId);
98
+ const data = await stagesService.updateEntity({ id: entityId, modelUID }, stageId);
99
+
100
+ ctx.body = { data };
94
101
  },
95
102
  };
@@ -8,7 +8,9 @@ const reviewWorkflowsMiddlewares = require('./middlewares/review-workflows');
8
8
  const { getService } = require('./utils');
9
9
 
10
10
  module.exports = async ({ strapi }) => {
11
- if (features.isEnabled('audit-logs')) {
11
+ const auditLogsIsEnabled = strapi.config.get('admin.auditLogs.enabled', true);
12
+
13
+ if (auditLogsIsEnabled) {
12
14
  strapi.hook('strapi::content-types.beforeSync').register(migrateAuditLogsTable);
13
15
  const auditLogsService = createAuditLogsService(strapi);
14
16
  strapi.container.register('audit-logs', auditLogsService);
@@ -63,16 +63,37 @@ const getEventMap = (defaultEvents) => {
63
63
  }, {});
64
64
  };
65
65
 
66
+ const getRetentionDays = (strapi) => {
67
+ const licenseRetentionDays = features.get('audit-logs')?.options.retentionDays;
68
+ const userRetentionDays = strapi.config.get('admin.auditLogs.retentionDays');
69
+
70
+ // For enterprise plans, use 90 days by default, but allow users to override it
71
+ if (licenseRetentionDays == null) {
72
+ return userRetentionDays ?? DEFAULT_RETENTION_DAYS;
73
+ }
74
+
75
+ // Allow users to override the license retention days, but not to increase it
76
+ if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
77
+ return userRetentionDays;
78
+ }
79
+
80
+ // User didn't provide a retention days value, use the license one
81
+ return licenseRetentionDays;
82
+ };
83
+
66
84
  const createAuditLogsService = (strapi) => {
85
+ // Manage internal service state privately
86
+ const state = {};
87
+
67
88
  // NOTE: providers should be able to replace getEventMap to add or remove events
68
89
  const eventMap = getEventMap(defaultEvents);
69
90
 
70
91
  const processEvent = (name, ...args) => {
71
- const state = strapi.requestContext.get()?.state;
92
+ const requestState = strapi.requestContext.get()?.state;
72
93
 
73
94
  // Ignore events with auth strategies different from admin
74
- const isUsingAdminAuth = state?.auth?.strategy.name === 'admin';
75
- const user = state?.user;
95
+ const isUsingAdminAuth = requestState?.auth?.strategy.name === 'admin';
96
+ const user = requestState?.user;
76
97
 
77
98
  if (!isUsingAdminAuth || !user) {
78
99
  return null;
@@ -103,26 +124,60 @@ const createAuditLogsService = (strapi) => {
103
124
  const processedEvent = processEvent(name, ...args);
104
125
 
105
126
  if (processedEvent) {
106
- await this._provider.saveEvent(processedEvent);
127
+ await state.provider.saveEvent(processedEvent);
107
128
  }
108
129
  }
109
130
 
110
131
  return {
111
132
  async register() {
112
- const retentionDays =
113
- features.get('audit-logs')?.options.retentionDays ?? DEFAULT_RETENTION_DAYS;
114
- this._provider = await localProvider.register({ strapi });
115
- this._eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
116
- this._deleteExpiredJob = scheduleJob('0 0 * * *', () => {
133
+ // Handle license being enabled
134
+ if (!state.eeEnableUnsubscribe) {
135
+ state.eeEnableUnsubscribe = strapi.eventHub.on('ee.enable', () => {
136
+ // Recreate the service to use the new license info
137
+ this.destroy();
138
+ this.register();
139
+ });
140
+ }
141
+
142
+ // Handle license being updated
143
+ if (!state.eeUpdateUnsubscribe) {
144
+ state.eeUpdateUnsubscribe = strapi.eventHub.on('ee.update', () => {
145
+ // Recreate the service to use the new license info
146
+ this.destroy();
147
+ this.register();
148
+ });
149
+ }
150
+
151
+ // Handle license being disabled
152
+ state.eeDisableUnsubscribe = strapi.eventHub.on('ee.disable', () => {
153
+ // Turn off service when the license gets disabled
154
+ // Only ee.enable and ee.update listeners remain active to recreate the service
155
+ this.destroy();
156
+ });
157
+
158
+ // Register the provider now because collections can't be added later at runtime
159
+ state.provider = await localProvider.register({ strapi });
160
+
161
+ // Check current state of license
162
+ if (!features.isEnabled('audit-logs')) {
163
+ return this;
164
+ }
165
+
166
+ // Start saving events
167
+ state.eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
168
+
169
+ // Manage audit logs auto deletion
170
+ const retentionDays = getRetentionDays(strapi);
171
+ state.deleteExpiredJob = scheduleJob('0 0 * * *', () => {
117
172
  const expirationDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
118
- this._provider.deleteExpiredEvents(expirationDate);
173
+ state.provider.deleteExpiredEvents(expirationDate);
119
174
  });
120
175
 
121
176
  return this;
122
177
  },
123
178
 
124
179
  async findMany(query) {
125
- const { results, pagination } = await this._provider.findMany(query);
180
+ const { results, pagination } = await state.provider.findMany(query);
126
181
 
127
182
  const sanitizedResults = results.map((result) => {
128
183
  const { user, ...rest } = result;
@@ -139,7 +194,7 @@ const createAuditLogsService = (strapi) => {
139
194
  },
140
195
 
141
196
  async findOne(id) {
142
- const result = await this._provider.findOne(id);
197
+ const result = await state.provider.findOne(id);
143
198
 
144
199
  if (!result) {
145
200
  return null;
@@ -153,12 +208,16 @@ const createAuditLogsService = (strapi) => {
153
208
  },
154
209
 
155
210
  unsubscribe() {
156
- if (this._eventHubUnsubscribe) {
157
- this._eventHubUnsubscribe();
211
+ if (state.eeDisableUnsubscribe) {
212
+ state.eeDisableUnsubscribe();
213
+ }
214
+
215
+ if (state.eventHubUnsubscribe) {
216
+ state.eventHubUnsubscribe();
158
217
  }
159
218
 
160
- if (this._deleteExpiredJob) {
161
- this._deleteExpiredJob.cancel();
219
+ if (state.deleteExpiredJob) {
220
+ state.deleteExpiredJob.cancel();
162
221
  }
163
222
 
164
223
  return this;
@@ -9,4 +9,5 @@ module.exports = {
9
9
  stages: require('./review-workflows/stages'),
10
10
  'review-workflows': require('./review-workflows/review-workflows'),
11
11
  'review-workflows-decorator': require('./review-workflows/entity-service-decorator'),
12
+ 'review-workflows-metrics': require('./review-workflows/metrics'),
12
13
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { isNil } = require('lodash/fp');
3
+ const { isNil, isNull } = require('lodash/fp');
4
4
  const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
5
5
  const { hasReviewWorkflow, getDefaultWorkflow } = require('../../utils/review-workflows');
6
6
 
@@ -30,10 +30,22 @@ const decorator = (service) => ({
30
30
  }
31
31
 
32
32
  const data = await getDataWithStage(opts.data);
33
- return service.create.call(this, uid, {
34
- ...opts,
35
- data,
36
- });
33
+ return service.create.call(this, uid, { ...opts, data });
34
+ },
35
+ async update(uid, entityId, opts = {}) {
36
+ const hasRW = hasReviewWorkflow({ strapi }, uid);
37
+
38
+ if (!hasRW) {
39
+ return service.update.call(this, uid, entityId, opts);
40
+ }
41
+
42
+ // Prevents the stage from being set to null
43
+ const data = { ...opts.data };
44
+ if (isNull(data[ENTITY_STAGE_ATTRIBUTE])) {
45
+ delete data[ENTITY_STAGE_ATTRIBUTE];
46
+ }
47
+
48
+ return service.update.call(this, uid, entityId, { ...opts, data });
37
49
  },
38
50
  });
39
51
 
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const sendDidCreateStage = async () => {
4
+ strapi.telemetry.send('didCreateStage', {});
5
+ };
6
+
7
+ const sendDidEditStage = async () => {
8
+ strapi.telemetry.send('didEditStage', {});
9
+ };
10
+
11
+ const sendDidDeleteStage = async () => {
12
+ strapi.telemetry.send('didDeleteStage', {});
13
+ };
14
+
15
+ const sendDidChangeEntryStage = async () => {
16
+ strapi.telemetry.send('didChangeEntryStage', {});
17
+ };
18
+
19
+ module.exports = {
20
+ sendDidCreateStage,
21
+ sendDidEditStage,
22
+ sendDidDeleteStage,
23
+ sendDidChangeEntryStage,
24
+ };
@@ -1,46 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const { set, get, forEach, keys, pickBy, pipe } = require('lodash/fp');
3
+ const { set, forEach, pipe, map } = require('lodash/fp');
4
4
  const { mapAsync } = require('@strapi/utils');
5
5
  const { getService } = require('../../utils');
6
+ const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
6
7
 
7
8
  const defaultStages = require('../../constants/default-stages.json');
8
9
  const defaultWorkflow = require('../../constants/default-workflow.json');
9
10
  const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
10
11
 
11
- const {
12
- disableOnContentTypes: disableReviewWorkflows,
13
- } = require('../../migrations/review-workflows');
14
12
  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
- }
13
+ const { persistTables, removePersistedTablesWithSuffix } = require('../../utils/persisted-tables');
44
14
 
45
15
  async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
46
16
  const wfCount = await workflowsService.count();
@@ -53,7 +23,7 @@ async function initDefaultWorkflow({ workflowsService, stagesService, strapi })
53
23
  const workflow = {
54
24
  ...defaultWorkflow,
55
25
  stages: {
56
- connect: buildStagesConnectArray(stages),
26
+ connect: stages.map((stage) => stage.id),
57
27
  },
58
28
  };
59
29
 
@@ -63,19 +33,18 @@ async function initDefaultWorkflow({ workflowsService, stagesService, strapi })
63
33
  }
64
34
  }
65
35
 
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
36
  function extendReviewWorkflowContentTypes({ strapi }) {
78
37
  const extendContentType = (contentTypeUID) => {
38
+ const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
39
+ writable: true,
40
+ private: false,
41
+ configurable: false,
42
+ visible: false,
43
+ useJoinTable: true, // We want a join table to persist data when downgrading to CE
44
+ type: 'relation',
45
+ relation: 'oneToOne',
46
+ target: 'admin::workflow-stage',
47
+ });
79
48
  strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
80
49
  };
81
50
  pipe([
@@ -101,63 +70,44 @@ function enableReviewWorkflow({ strapi }) {
101
70
  return;
102
71
  }
103
72
  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
- );
73
+ const stagesService = getService('stages', { strapi });
74
+
75
+ const updateEntitiesStage = async (contentTypeUID) => {
76
+ // Update CT entities stage
77
+ return stagesService.updateEntitiesStage(contentTypeUID, {
78
+ fromStageId: null,
79
+ toStageId: firstStage.id,
80
+ });
151
81
  };
152
82
 
153
83
  return pipe([
154
84
  getContentTypeUIDsWithActivatedReviewWorkflows,
155
85
  // Iterate over UIDs to extend the content-type
156
- (contentTypesUIDs) => mapAsync(contentTypesUIDs, up),
86
+ (contentTypesUIDs) => mapAsync(contentTypesUIDs, updateEntitiesStage),
157
87
  ])(contentTypes);
158
88
  };
159
89
  }
160
90
 
91
+ function persistStagesJoinTables({ strapi }) {
92
+ return async ({ contentTypes }) => {
93
+ const getStageTableToPersist = (contentTypeUID) => {
94
+ // Persist the stage join table
95
+ const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
96
+ const joinTableName = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable.name;
97
+ return { name: joinTableName, dependsOn: { name: tableName } };
98
+ };
99
+
100
+ const joinTablesToPersist = pipe([
101
+ getContentTypeUIDsWithActivatedReviewWorkflows,
102
+ map(getStageTableToPersist),
103
+ ])(contentTypes);
104
+
105
+ // TODO: Instead of removing all the tables, we should only remove the ones that are not in the joinTablesToPersist
106
+ await removePersistedTablesWithSuffix('_strapi_review_workflows_stage_links');
107
+ await persistTables(joinTablesToPersist);
108
+ };
109
+ }
110
+
161
111
  module.exports = ({ strapi }) => {
162
112
  const workflowsService = getService('workflows', { strapi });
163
113
  const stagesService = getService('stages', { strapi });
@@ -169,7 +119,7 @@ module.exports = ({ strapi }) => {
169
119
  async register() {
170
120
  extendReviewWorkflowContentTypes({ strapi });
171
121
  strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
172
- strapi.hook('strapi::content-types.afterSync').register(disableReviewWorkflows);
122
+ strapi.hook('strapi::content-types.afterSync').register(persistStagesJoinTables({ strapi }));
173
123
  },
174
124
  };
175
125
  };