@strapi/admin 4.12.0-beta.1 → 4.12.0-beta.4

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 (166) hide show
  1. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +14 -38
  2. package/admin/src/content-manager/components/RelationInput/components/Option.js +6 -5
  3. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +7 -7
  4. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +23 -21
  5. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +21 -23
  6. package/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js +24 -22
  7. package/admin/src/content-manager/pages/ListView/components/Body/index.js +191 -0
  8. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/ConfirmBulkActionDialog/index.js +164 -0
  9. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/index.js +468 -0
  10. package/admin/src/content-manager/{components/DynamicTable/BulkActionsBar → pages/ListView/components/BulkActionButtons}/index.js +56 -132
  11. package/admin/src/content-manager/pages/ListView/components/CellContent/RelationMultiple/index.js +63 -69
  12. package/admin/src/content-manager/pages/ListView/components/CellContent/RepeatableComponent/index.js +28 -21
  13. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +7 -3
  14. package/admin/src/content-manager/pages/ListView/index.js +191 -132
  15. package/admin/src/layouts/UnauthenticatedLayout/LocaleToggle/index.js +7 -7
  16. package/admin/src/pages/AuthPage/components/Register/index.js +4 -0
  17. package/admin/src/translations/ca.json +4 -4
  18. package/admin/src/translations/de.json +4 -4
  19. package/admin/src/translations/dk.json +2 -2
  20. package/admin/src/translations/en.json +23 -4
  21. package/admin/src/translations/es.json +4 -4
  22. package/admin/src/translations/eu.json +4 -4
  23. package/admin/src/translations/fr.json +2 -2
  24. package/admin/src/translations/gu.json +4 -4
  25. package/admin/src/translations/hi.json +4 -4
  26. package/admin/src/translations/hu.json +4 -4
  27. package/admin/src/translations/ja.json +2 -2
  28. package/admin/src/translations/ko.json +2 -2
  29. package/admin/src/translations/ml.json +4 -4
  30. package/admin/src/translations/nl.json +4 -4
  31. package/admin/src/translations/pl.json +4 -4
  32. package/admin/src/translations/pt-BR.json +4 -4
  33. package/admin/src/translations/ru.json +4 -4
  34. package/admin/src/translations/sa.json +4 -4
  35. package/admin/src/translations/sk.json +4 -4
  36. package/admin/src/translations/sv.json +4 -4
  37. package/admin/src/translations/tr.json +4 -4
  38. package/admin/src/translations/zh-Hans.json +4 -4
  39. package/admin/src/translations/zh.json +4 -4
  40. package/build/2379.0ca87a89.chunk.js +1 -0
  41. package/build/2395.df7a044a.chunk.js +26 -0
  42. package/build/2801.b1140c9b.chunk.js +1 -0
  43. package/build/{3100.21c343fa.chunk.js → 3100.2ba4df95.chunk.js} +1 -1
  44. package/build/{3483.e182b190.chunk.js → 3483.e2ee2547.chunk.js} +1 -1
  45. package/build/{970.89601f27.chunk.js → 3739.63e352f1.chunk.js} +52 -20
  46. package/build/3984.dda474f7.chunk.js +1 -0
  47. package/build/502.8ae8ef60.chunk.js +1 -0
  48. package/build/5483.6dd2e776.chunk.js +6 -0
  49. package/build/6158.c974fd83.chunk.js +1 -0
  50. package/build/6691.f880a0b6.chunk.js +105 -0
  51. package/build/7065.99ca8ab1.chunk.js +112 -0
  52. package/build/7464.8a6c1e6c.chunk.js +1 -0
  53. package/build/8276.6c7b8e6e.chunk.js +26 -0
  54. package/build/{9932.b5a3bb3a.chunk.js → 9806.91360bb6.chunk.js} +47 -47
  55. package/build/{Admin-authenticatedApp.2ffa318a.chunk.js → Admin-authenticatedApp.24998de8.chunk.js} +2 -2
  56. package/build/Admin_settingsPage.8c600d1a.chunk.js +111 -0
  57. package/build/{admin-app.088bcd33.chunk.js → admin-app.c2e4e128.chunk.js} +9 -9
  58. package/build/{ca-json.1fed5d8b.chunk.js → ca-json.a53c10b6.chunk.js} +1 -1
  59. package/build/content-manager.8772445b.chunk.js +1103 -0
  60. package/build/{content-type-builder-translation-ar-json.56d8fcf4.chunk.js → content-type-builder-translation-ar-json.3e808e2f.chunk.js} +1 -1
  61. package/build/{content-type-builder-translation-cs-json.a5b299ca.chunk.js → content-type-builder-translation-cs-json.1ef9e106.chunk.js} +1 -1
  62. package/build/{content-type-builder-translation-de-json.393a76c0.chunk.js → content-type-builder-translation-de-json.63fcff7b.chunk.js} +1 -1
  63. package/build/{content-type-builder-translation-dk-json.fbd39bb7.chunk.js → content-type-builder-translation-dk-json.fd626b67.chunk.js} +1 -1
  64. package/build/{content-type-builder-translation-en-json.38e20391.chunk.js → content-type-builder-translation-en-json.ed29ff4d.chunk.js} +1 -1
  65. package/build/{content-type-builder-translation-es-json.9288474b.chunk.js → content-type-builder-translation-es-json.a4a361a9.chunk.js} +1 -1
  66. package/build/{content-type-builder-translation-fr-json.d35e269c.chunk.js → content-type-builder-translation-fr-json.499c3a46.chunk.js} +1 -1
  67. package/build/{content-type-builder-translation-id-json.f0513929.chunk.js → content-type-builder-translation-id-json.65255f93.chunk.js} +1 -1
  68. package/build/{content-type-builder-translation-it-json.aaf16753.chunk.js → content-type-builder-translation-it-json.e268ab74.chunk.js} +1 -1
  69. package/build/{content-type-builder-translation-ko-json.8fe21a7f.chunk.js → content-type-builder-translation-ko-json.04cb309d.chunk.js} +1 -1
  70. package/build/{content-type-builder-translation-ms-json.3b5d2d3e.chunk.js → content-type-builder-translation-ms-json.f6b743b9.chunk.js} +1 -1
  71. package/build/{content-type-builder-translation-nl-json.225ef5d3.chunk.js → content-type-builder-translation-nl-json.997fe8cc.chunk.js} +1 -1
  72. package/build/{content-type-builder-translation-pl-json.92f36be2.chunk.js → content-type-builder-translation-pl-json.634f638b.chunk.js} +1 -1
  73. package/build/{content-type-builder-translation-pt-BR-json.3bd10f89.chunk.js → content-type-builder-translation-pt-BR-json.6a95dc71.chunk.js} +1 -1
  74. package/build/{content-type-builder-translation-ru-json.9bfe47ce.chunk.js → content-type-builder-translation-ru-json.3af65503.chunk.js} +1 -1
  75. package/build/{content-type-builder-translation-sk-json.d03cc18a.chunk.js → content-type-builder-translation-sk-json.c6078082.chunk.js} +1 -1
  76. package/build/{content-type-builder-translation-sv-json.d23dcd32.chunk.js → content-type-builder-translation-sv-json.a6df2462.chunk.js} +1 -1
  77. package/build/{content-type-builder-translation-th-json.7ad256e2.chunk.js → content-type-builder-translation-th-json.122277cc.chunk.js} +1 -1
  78. package/build/{content-type-builder-translation-tr-json.926f6191.chunk.js → content-type-builder-translation-tr-json.41f44f77.chunk.js} +1 -1
  79. package/build/{content-type-builder-translation-uk-json.7bf19546.chunk.js → content-type-builder-translation-uk-json.e1315acd.chunk.js} +1 -1
  80. package/build/{content-type-builder-translation-zh-Hans-json.415577fb.chunk.js → content-type-builder-translation-zh-Hans-json.6ff57db6.chunk.js} +1 -1
  81. package/build/{content-type-builder-translation-zh-json.958d90e1.chunk.js → content-type-builder-translation-zh-json.3532b962.chunk.js} +1 -1
  82. package/build/{content-type-builder.3963fb2d.chunk.js → content-type-builder.40534de5.chunk.js} +21 -17
  83. package/build/{de-json.fcac7381.chunk.js → de-json.b3be02c7.chunk.js} +1 -1
  84. package/build/{dk-json.e34cad0d.chunk.js → dk-json.842aa391.chunk.js} +1 -1
  85. package/build/{en-json.fb9f6ddd.chunk.js → en-json.4c733bd1.chunk.js} +1 -1
  86. package/build/{es-json.42096084.chunk.js → es-json.f57b5335.chunk.js} +1 -1
  87. package/build/{eu-json.fb17c8f9.chunk.js → eu-json.633025f0.chunk.js} +1 -1
  88. package/build/{fr-json.69789980.chunk.js → fr-json.aa8839d2.chunk.js} +1 -1
  89. package/build/{gu-json.4d667d0c.chunk.js → gu-json.5bd62812.chunk.js} +1 -1
  90. package/build/{hi-json.323be97d.chunk.js → hi-json.9104eb78.chunk.js} +1 -1
  91. package/build/{hu-json.fe71e6c8.chunk.js → hu-json.9f4aae42.chunk.js} +1 -1
  92. package/build/index.html +1 -1
  93. package/build/{ja-json.81b6d1e3.chunk.js → ja-json.91286391.chunk.js} +1 -1
  94. package/build/{ko-json.4539f4ba.chunk.js → ko-json.fcf3ec4b.chunk.js} +1 -1
  95. package/build/main.ef5fb1a8.js +2856 -0
  96. package/build/{ml-json.8988e374.chunk.js → ml-json.557aa14c.chunk.js} +1 -1
  97. package/build/{nl-json.98345913.chunk.js → nl-json.b2b16eea.chunk.js} +1 -1
  98. package/build/{pl-json.59a5dab3.chunk.js → pl-json.f094a417.chunk.js} +1 -1
  99. package/build/{pt-BR-json.9410688b.chunk.js → pt-BR-json.dec7fb01.chunk.js} +1 -1
  100. package/build/review-workflows-settings-create-view.d4b5dbb8.chunk.js +1 -0
  101. package/build/review-workflows-settings-edit-view.77299c63.chunk.js +1 -0
  102. package/build/review-workflows-settings-list-view.3ee9190d.chunk.js +56 -0
  103. package/build/{ru-json.678cd48b.chunk.js → ru-json.8193d8c4.chunk.js} +1 -1
  104. package/build/{runtime~main.44bf2a37.js → runtime~main.c99f4c36.js} +2 -2
  105. package/build/{sa-json.6359a11c.chunk.js → sa-json.a56836f1.chunk.js} +1 -1
  106. package/build/{sk-json.2374f129.chunk.js → sk-json.bf2f057a.chunk.js} +1 -1
  107. package/build/sso-settings-page.3a1ed8c9.chunk.js +1 -0
  108. package/build/{sv-json.ae6e71ea.chunk.js → sv-json.fd0e86c6.chunk.js} +1 -1
  109. package/build/{tr-json.bac5dbd3.chunk.js → tr-json.56c32cf6.chunk.js} +1 -1
  110. package/build/upload.cbfeefa5.chunk.js +58 -0
  111. package/build/{zh-Hans-json.fada6f40.chunk.js → zh-Hans-json.36d81cdc.chunk.js} +1 -1
  112. package/build/{zh-json.3529f1e5.chunk.js → zh-json.1cc86ff0.chunk.js} +1 -1
  113. package/ee/admin/constants.js +3 -0
  114. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +12 -4
  115. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +2 -2
  116. package/ee/admin/hooks/index.js +1 -1
  117. package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
  118. package/ee/admin/hooks/useLicenseLimits/__mocks__/index.js +8 -0
  119. package/ee/admin/hooks/useLicenseLimits/index.js +1 -3
  120. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +3 -13
  121. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +19 -4
  122. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +7 -0
  123. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +20 -16
  124. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +38 -23
  125. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -0
  126. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +62 -14
  127. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +8 -3
  128. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +91 -33
  129. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +8 -3
  130. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +72 -55
  131. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +8 -3
  132. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +1 -7
  133. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/{getWorkflowValidationSchema.js → validateWorkflow.js} +33 -2
  134. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/utils/schema.js +8 -5
  135. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
  136. package/ee/server/constants/workflows.js +1 -0
  137. package/ee/server/services/review-workflows/review-workflows.js +1 -1
  138. package/ee/server/services/review-workflows/validation.js +12 -4
  139. package/ee/server/services/review-workflows/workflows/content-types.js +28 -19
  140. package/ee/server/services/review-workflows/workflows/index.js +14 -2
  141. package/ee/server/validation/authentication.js +14 -8
  142. package/ee/server/validation/review-workflows.js +6 -2
  143. package/package.json +9 -9
  144. package/build/190.66d89241.chunk.js +0 -117
  145. package/build/2379.d33a2e16.chunk.js +0 -1
  146. package/build/2395.b0419a54.chunk.js +0 -26
  147. package/build/2801.18ac397d.chunk.js +0 -1
  148. package/build/3984.ea7b8036.chunk.js +0 -1
  149. package/build/502.ccb38223.chunk.js +0 -1
  150. package/build/5483.ed2c7efa.chunk.js +0 -6
  151. package/build/6158.f9d82db9.chunk.js +0 -1
  152. package/build/6691.e6d5ac38.chunk.js +0 -105
  153. package/build/7464.c6d0565c.chunk.js +0 -1
  154. package/build/8276.23e0763b.chunk.js +0 -26
  155. package/build/Admin_settingsPage.3ad19487.chunk.js +0 -79
  156. package/build/content-manager.9b569036.chunk.js +0 -1094
  157. package/build/main.98c989b0.js +0 -2908
  158. package/build/review-workflows-settings-create-view.60bc516c.chunk.js +0 -1
  159. package/build/review-workflows-settings-edit-view.898ea409.chunk.js +0 -1
  160. package/build/review-workflows-settings-list-view.240cacdf.chunk.js +0 -56
  161. package/build/sso-settings-page.ed6f3f15.chunk.js +0 -1
  162. package/build/upload.8d01c525.chunk.js +0 -26
  163. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +0 -21
  164. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +0 -1
  165. /package/admin/src/content-manager/components/{DynamicTable → ListViewTable}/CellContent/PublicationState/PublicationState.js +0 -0
  166. /package/admin/src/content-manager/components/{DynamicTable → ListViewTable}/CellContent/PublicationState/index.js +0 -0
@@ -23,18 +23,22 @@ import {
23
23
  useAPIErrorHandler,
24
24
  useFetchClient,
25
25
  useNotification,
26
+ useRBAC,
26
27
  useTracking,
27
28
  } from '@strapi/helper-plugin';
28
29
  import { Pencil, Plus, Trash } from '@strapi/icons';
29
30
  import { useIntl } from 'react-intl';
30
31
  import { useMutation } from 'react-query';
32
+ import { useSelector } from 'react-redux';
31
33
  import { useHistory } from 'react-router-dom';
32
34
  import styled from 'styled-components';
33
35
 
34
36
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
37
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
35
38
  import { useLicenseLimits } from '../../../../../../hooks';
36
39
  import * as Layout from '../../components/Layout';
37
40
  import * as LimitsModal from '../../components/LimitsModal';
41
+ import { CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME } from '../../constants';
38
42
  import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
39
43
 
40
44
  const ActionLink = styled(Link)`
@@ -76,6 +80,10 @@ export function ReviewWorkflowsListView() {
76
80
  const toggleNotification = useNotification();
77
81
  const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
78
82
  const { trackUsage } = useTracking();
83
+ const permissions = useSelector(selectAdminPermissions);
84
+ const {
85
+ allowedActions: { canCreate, canDelete },
86
+ } = useRBAC(permissions.settings['review-workflows']);
79
87
 
80
88
  const limits = getFeature('review-workflows');
81
89
 
@@ -148,24 +156,21 @@ export function ReviewWorkflowsListView() {
148
156
 
149
157
  React.useEffect(() => {
150
158
  if (!isLoading && !isLicenseLoading) {
151
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
159
+ if (
160
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
161
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
162
+ ) {
152
163
  setShowLimitModal(true);
153
164
  }
154
165
  }
155
- }, [
156
- isLicenseLoading,
157
- isLoading,
158
- limits.stagesPerWorkflow,
159
- limits.workflows,
160
- meta?.workflowCount,
161
- meta.workflowsTotal,
162
- ]);
166
+ }, [isLicenseLoading, isLoading, limits, meta?.workflowCount, meta.workflowsTotal]);
163
167
 
164
168
  return (
165
169
  <>
166
170
  <Layout.Header
167
171
  primaryAction={
168
172
  <LinkButton
173
+ disabled={!canCreate}
169
174
  startIcon={<Plus />}
170
175
  size="S"
171
176
  to="/settings/review-workflows/create"
@@ -180,7 +185,10 @@ export function ReviewWorkflowsListView() {
180
185
  * current hard-limit of 200 they will see an error thrown by the API.
181
186
  */
182
187
 
183
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
188
+ if (
189
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
190
+ meta?.workflowCount >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
191
+ ) {
184
192
  event.preventDefault();
185
193
  setShowLimitModal(true);
186
194
  } else {
@@ -207,42 +215,50 @@ export function ReviewWorkflowsListView() {
207
215
 
208
216
  <Layout.Root>
209
217
  {isLoading || isLoadingModels ? (
210
- <Loader>
211
- {formatMessage({
212
- id: 'Settings.review-workflows.page.list.isLoading',
213
- defaultMessage: 'Workflows are loading',
214
- })}
215
- </Loader>
218
+ <Flex justifyContent="center">
219
+ <Loader>
220
+ {formatMessage({
221
+ id: 'Settings.review-workflows.page.list.isLoading',
222
+ defaultMessage: 'Workflows are loading',
223
+ })}
224
+ </Loader>
225
+ </Flex>
216
226
  ) : (
217
227
  <Table
218
228
  colCount={3}
219
229
  footer={
220
230
  // TODO: we should be able to use a link here instead of an (inaccessible onClick) handler
221
- <TFooter
222
- icon={<Plus />}
223
- onClick={() => {
224
- /**
225
- * If the current license has a workflow limit:
226
- * check if the total count of workflows exceeds that limit
227
- *
228
- * If the current license does not have a limit (e.g. offline license):
229
- * allow the user to navigate to the create-view. In case they exceed the
230
- * current hard-limit of 200 they will see an error thrown by the API.
231
- */
231
+ canCreate && (
232
+ <TFooter
233
+ icon={<Plus />}
234
+ onClick={() => {
235
+ /**
236
+ * If the current license has a workflow limit:
237
+ * check if the total count of workflows exceeds that limit
238
+ *
239
+ * If the current license does not have a limit (e.g. offline license):
240
+ * allow the user to navigate to the create-view. In case they exceed the
241
+ * current hard-limit of 200 they will see an error thrown by the API.
242
+ */
232
243
 
233
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
234
- setShowLimitModal(true);
235
- } else {
236
- push('/settings/review-workflows/create');
237
- trackUsage('willCreateWorkflow');
238
- }
239
- }}
240
- >
241
- {formatMessage({
242
- id: 'Settings.review-workflows.list.page.create',
243
- defaultMessage: 'Create new workflow',
244
- })}
245
- </TFooter>
244
+ if (
245
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
246
+ meta?.workflowCount >=
247
+ parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
248
+ ) {
249
+ setShowLimitModal(true);
250
+ } else {
251
+ push('/settings/review-workflows/create');
252
+ trackUsage('willCreateWorkflow');
253
+ }
254
+ }}
255
+ >
256
+ {formatMessage({
257
+ id: 'Settings.review-workflows.list.page.create',
258
+ defaultMessage: 'Create new workflow',
259
+ })}
260
+ </TFooter>
261
+ )
246
262
  }
247
263
  rowCount={1}
248
264
  >
@@ -326,21 +342,22 @@ export function ReviewWorkflowsListView() {
326
342
  <Pencil />
327
343
  </ActionLink>
328
344
 
329
- <IconButton
330
- aria-label={formatMessage(
331
- {
332
- id: 'Settings.review-workflows.list.page.list.column.actions.delete.label',
333
- defaultMessage: 'Delete {name}',
334
- },
335
- { name: 'Default workflow' }
336
- )}
337
- disabled={workflows.length === 1}
338
- icon={<Trash />}
339
- noBorder
340
- onClick={() => {
341
- handleDeleteWorkflow(workflow.id);
342
- }}
343
- />
345
+ {workflows.length > 1 && canDelete && (
346
+ <IconButton
347
+ aria-label={formatMessage(
348
+ {
349
+ id: 'Settings.review-workflows.list.page.list.column.actions.delete.label',
350
+ defaultMessage: 'Delete {name}',
351
+ },
352
+ { name: 'Default workflow' }
353
+ )}
354
+ icon={<Trash />}
355
+ noBorder
356
+ onClick={() => {
357
+ handleDeleteWorkflow(workflow.id);
358
+ }}
359
+ />
360
+ )}
344
361
  </Flex>
345
362
  </Td>
346
363
  </Tr>
@@ -1,13 +1,18 @@
1
1
  import React from 'react';
2
2
 
3
- import { ProtectedPage } from '../../components/ProtectedPage';
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+
6
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
4
7
 
5
8
  import { ReviewWorkflowsListView } from './ListView';
6
9
 
7
10
  export default function () {
11
+ const permissions = useSelector(selectAdminPermissions);
12
+
8
13
  return (
9
- <ProtectedPage>
14
+ <CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
10
15
  <ReviewWorkflowsListView />
11
- </ProtectedPage>
16
+ </CheckPagePermissions>
12
17
  );
13
18
  }
@@ -22,13 +22,7 @@ export const initialState = {
22
22
  data: {
23
23
  name: '',
24
24
  contentTypes: [],
25
- stages: [
26
- {
27
- color: STAGE_COLOR_DEFAULT,
28
- name: '',
29
- __temp_key__: 1,
30
- },
31
- ],
25
+ stages: [],
32
26
  },
33
27
  isDirty: false,
34
28
  hasDeletedServerStages: false,
@@ -1,7 +1,8 @@
1
+ import set from 'lodash/set';
1
2
  import * as yup from 'yup';
2
3
 
3
- export function getWorkflowValidationSchema({ formatMessage }) {
4
- return yup.object({
4
+ export async function validateWorkflow({ values, formatMessage }) {
5
+ const schema = yup.object({
5
6
  contentTypes: yup.array().of(yup.string()),
6
7
  name: yup
7
8
  .string()
@@ -32,6 +33,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
32
33
  id: 'Settings.review-workflows.validation.stage.max-length',
33
34
  defaultMessage: 'Name can not be longer than 255 characters',
34
35
  })
36
+ )
37
+ .test(
38
+ 'unique-name',
39
+ formatMessage({
40
+ id: 'Settings.review-workflows.validation.stage.duplicate',
41
+ defaultMessage: 'Stage name must be unique',
42
+ }),
43
+ function (stageName) {
44
+ const {
45
+ options: { context },
46
+ } = this;
47
+
48
+ return context.stages.filter((stage) => stage.name === stageName).length === 1;
49
+ }
35
50
  ),
36
51
  color: yup
37
52
  .string()
@@ -46,4 +61,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
46
61
  )
47
62
  .min(1),
48
63
  });
64
+
65
+ try {
66
+ await schema.validate(values, { abortEarly: false, context: values });
67
+
68
+ return true;
69
+ } catch (error) {
70
+ let errors = {};
71
+
72
+ if (error instanceof yup.ValidationError) {
73
+ error.inner.forEach((error) => {
74
+ set(errors, error.path, error.message);
75
+ });
76
+ }
77
+
78
+ return errors;
79
+ }
49
80
  }
@@ -6,11 +6,14 @@ const schema = yup.object().shape({
6
6
  defaultRole: yup.mixed().when('autoRegister', (value, initSchema) => {
7
7
  return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
8
8
  }),
9
- ssoLockedRoles: yup.array().of(
10
- yup.mixed().when('ssoLockedRoles', (value, initSchema) => {
11
- return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
12
- })
13
- ),
9
+ ssoLockedRoles: yup
10
+ .array()
11
+ .nullable()
12
+ .of(
13
+ yup.mixed().when('ssoLockedRoles', (value, initSchema) => {
14
+ return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
15
+ })
16
+ ),
14
17
  });
15
18
 
16
19
  export default schema;
@@ -10,4 +10,4 @@ function UserListPageEE() {
10
10
  return <UserListPageCE />;
11
11
  }
12
12
 
13
- export default UserListPageEE;
13
+ export { UserListPageEE };
@@ -14,5 +14,6 @@ module.exports = {
14
14
  'You’ve reached the limit of workflows in your plan. Delete a workflow or contact Sales to enable more workflows.',
15
15
  STAGES_LIMIT:
16
16
  'You’ve reached the limit of stages for this workflow in your plan. Try deleting some stages or contact Sales to enable more stages.',
17
+ DUPLICATED_STAGE_NAME: 'Stage names must be unique.',
17
18
  },
18
19
  };
@@ -22,7 +22,7 @@ const MAX_JOIN_TABLE_NAME_SUFFIX =
22
22
  const MAX_CONTENT_TYPE_NAME_LEN = MAX_DB_TABLE_NAME_LEN - MAX_JOIN_TABLE_NAME_SUFFIX;
23
23
 
24
24
  const DEFAULT_OPTIONS = {
25
- workflows: MAX_WORKFLOWS,
25
+ numberOfWorkflows: MAX_WORKFLOWS,
26
26
  stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW,
27
27
  };
28
28
 
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { uniq } = require('lodash/fp');
3
4
  const { ValidationError } = require('@strapi/utils').errors;
4
5
  const { getService } = require('../../utils');
5
6
  const { ERRORS, MAX_WORKFLOWS, MAX_STAGES_PER_WORKFLOW } = require('../../constants/workflows');
@@ -8,12 +9,14 @@ const { clampMaxWorkflows, clampMaxStagesPerWorkflow } = require('../../utils/re
8
9
  module.exports = ({ strapi }) => {
9
10
  return {
10
11
  limits: {
11
- workflows: MAX_WORKFLOWS,
12
+ numberOfWorkflows: MAX_WORKFLOWS,
12
13
  stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW,
13
14
  },
14
- register({ workflows, stagesPerWorkflow }) {
15
+ register({ numberOfWorkflows, stagesPerWorkflow }) {
15
16
  if (!Object.isFrozen(this.limits)) {
16
- this.limits.workflows = clampMaxWorkflows(workflows || this.limits.workflows);
17
+ this.limits.numberOfWorkflows = clampMaxWorkflows(
18
+ numberOfWorkflows || this.limits.numberOfWorkflows
19
+ );
17
20
  this.limits.stagesPerWorkflow = clampMaxStagesPerWorkflow(
18
21
  stagesPerWorkflow || this.limits.stagesPerWorkflow
19
22
  );
@@ -32,6 +35,11 @@ module.exports = ({ strapi }) => {
32
35
  if (stages.length > this.limits.stagesPerWorkflow) {
33
36
  throw new ValidationError(ERRORS.STAGES_LIMIT);
34
37
  }
38
+ // Validate stage names are not duplicated
39
+ const stageNames = stages.map((stage) => stage.name);
40
+ if (uniq(stageNames).length !== stageNames.length) {
41
+ throw new ValidationError(ERRORS.DUPLICATED_STAGE_NAME);
42
+ }
35
43
  },
36
44
 
37
45
  async validateWorkflowCountStages(workflowId, countAddedStages = 0) {
@@ -52,7 +60,7 @@ module.exports = ({ strapi }) => {
52
60
  async validateWorkflowCount(countAddedWorkflows = 0) {
53
61
  const workflowsService = getService('workflows', { strapi });
54
62
  const countWorkflows = await workflowsService.count();
55
- if (countWorkflows + countAddedWorkflows > this.limits.workflows) {
63
+ if (countWorkflows + countAddedWorkflows > this.limits.numberOfWorkflows) {
56
64
  throw new ValidationError(ERRORS.WORKFLOWS_LIMIT);
57
65
  }
58
66
  },
@@ -30,26 +30,35 @@ module.exports = ({ strapi }) => {
30
30
  * @param {Workflow.Stage} options.stageId - The new stage to assign the entities to
31
31
  */
32
32
  async migrate({ srcContentTypes = [], destContentTypes, stageId }) {
33
- // Workflows service is using this content-types service, to avoid an infinite loop, we need to get the service in the method
34
33
  const workflowsService = getService('workflows', { strapi });
35
34
  const { created, deleted } = diffContentTypes(srcContentTypes, destContentTypes);
36
35
 
37
- await mapAsync(created, async (uid) => {
38
- // If it was assigned to another workflow, transfer it from the previous workflow
39
- const srcWorkflow = await workflowsService.getAssignedWorkflow(uid);
40
- if (srcWorkflow) {
41
- // Updates all existing entities stages links to the new stage
42
- await stagesService.updateEntitiesStage(uid, { toStageId: stageId });
43
- return this.transferContentType(srcWorkflow, uid);
44
- }
45
- await updateContentTypeConfig(uid, true);
36
+ await mapAsync(
37
+ created,
38
+ async (uid) => {
39
+ // Content Types should only be assigned to one workflow
40
+ // However, edge cases can happen, and this handles them
41
+ const srcWorkflows = await workflowsService._getAssignedWorkflows(uid, {});
46
42
 
47
- // Create new stages links to the new stage
48
- return stagesService.updateEntitiesStage(uid, {
49
- fromStageId: null,
50
- toStageId: stageId,
51
- });
52
- });
43
+ if (srcWorkflows.length) {
44
+ // Updates all existing entities stages links to the new stage
45
+ await stagesService.updateEntitiesStage(uid, { toStageId: stageId });
46
+ // Transfer content types from the previous workflow(s)
47
+ await mapAsync(srcWorkflows, (srcWorkflow) =>
48
+ this.transferContentTypes(srcWorkflow, uid)
49
+ );
50
+ }
51
+ await updateContentTypeConfig(uid, true);
52
+
53
+ // Create new stages links to the new stage
54
+ return stagesService.updateEntitiesStage(uid, {
55
+ fromStageId: null,
56
+ toStageId: stageId,
57
+ });
58
+ },
59
+ // transferContentTypes can cause race conditions if called in parallel when updating the same workflow
60
+ { concurrency: 1 }
61
+ );
53
62
 
54
63
  await mapAsync(deleted, async (uid) => {
55
64
  await updateContentTypeConfig(uid, false);
@@ -58,15 +67,15 @@ module.exports = ({ strapi }) => {
58
67
  },
59
68
 
60
69
  /**
61
- * Filters the content types assigned to the previous workflow.
70
+ * Filters the content types assigned to a workflow
62
71
  * @param {Workflow} srcWorkflow - The workflow to transfer from
63
72
  * @param {string} uid - The content type uid
64
73
  */
65
- async transferContentType(srcWorkflow, uid) {
74
+ async transferContentTypes(srcWorkflow, uid) {
66
75
  // Update assignedContentTypes of the previous workflow
67
76
  await strapi.entityService.update(WORKFLOW_MODEL_UID, srcWorkflow.id, {
68
77
  data: {
69
- contentTypes: srcWorkflow.contentTypes.filter((ct) => ct !== uid),
78
+ contentTypes: srcWorkflow.contentTypes.filter((contentType) => contentType !== uid),
70
79
  },
71
80
  });
72
81
  },
@@ -163,11 +163,23 @@ module.exports = ({ strapi }) => {
163
163
  * @returns {Promise<object|null>} - Assigned workflow object if found, or null.
164
164
  */
165
165
  async getAssignedWorkflow(uid, opts = {}) {
166
- const workflows = await this.find({
166
+ const workflows = await this._getAssignedWorkflows(uid, opts);
167
+ return workflows.length > 0 ? workflows[0] : null;
168
+ },
169
+
170
+ /**
171
+ * Finds all the assigned workflows for a given content type ID.
172
+ * Normally, there should only be one workflow assigned to a content type.
173
+ * However, edge cases can occur where a content type is assigned to multiple workflows.
174
+ * @param {string} uid - Content type ID to find the assigned workflows for.
175
+ * @param {object} opts - Options for the query.
176
+ * @returns {Promise<object[]>} - List of assigned workflow objects.
177
+ */
178
+ async _getAssignedWorkflows(uid, opts = {}) {
179
+ return this.find({
167
180
  ...opts,
168
181
  filters: { contentTypes: getWorkflowContentTypeFilter({ strapi }, uid) },
169
182
  });
170
- return workflows.length > 0 ? workflows[0] : null;
171
183
  },
172
184
 
173
185
  /**
@@ -10,14 +10,20 @@ const providerOptionsUpdateSchema = yup.object().shape({
10
10
  .test('is-valid-role', 'You must submit a valid default role', (roleId) => {
11
11
  return strapi.admin.services.role.exists({ id: roleId });
12
12
  }),
13
- ssoLockedRoles: yup.array().of(
14
- yup
15
- .strapiID()
16
- .required()
17
- .test('is-valid-role', 'You must submit a valid role for the SSO Locked roles', (roleId) => {
18
- return strapi.admin.services.role.exists({ id: roleId });
19
- })
20
- ),
13
+ ssoLockedRoles: yup
14
+ .array()
15
+ .nullable()
16
+ .of(
17
+ yup
18
+ .strapiID()
19
+ .test(
20
+ 'is-valid-role',
21
+ 'You must submit a valid role for the SSO Locked roles',
22
+ (roleId) => {
23
+ return strapi.admin.services.role.exists({ id: roleId });
24
+ }
25
+ )
26
+ ),
21
27
  });
22
28
 
23
29
  module.exports = {
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ /* eslint-disable func-names */
4
+
3
5
  const { yup, validateYupSchema } = require('@strapi/utils');
4
6
  const { hasStageAttribute } = require('../utils/review-workflows');
5
7
 
@@ -42,10 +44,11 @@ const validateContentTypes = yup.array().of(
42
44
  );
43
45
 
44
46
  const validateWorkflowCreateSchema = yup.object().shape({
45
- name: yup.string().max(255).required(),
47
+ name: yup.string().max(255).min(1, 'Workflow name can not be empty').required(),
46
48
  stages: yup
47
49
  .array()
48
50
  .of(stageObject)
51
+ .uniqueProperty('name', 'Stage name must be unique')
49
52
  .min(1, 'Can not create a workflow without stages')
50
53
  .max(200, 'Can not have more than 200 stages')
51
54
  .required('Can not create a workflow without stages'),
@@ -53,10 +56,11 @@ const validateWorkflowCreateSchema = yup.object().shape({
53
56
  });
54
57
 
55
58
  const validateWorkflowUpdateSchema = yup.object().shape({
56
- name: yup.string().max(255),
59
+ name: yup.string().max(255).min(1, 'Workflow name can not be empty'),
57
60
  stages: yup
58
61
  .array()
59
62
  .of(stageObject)
63
+ .uniqueProperty('name', 'Stage name must be unique')
60
64
  .min(1, 'Can not update a workflow without stages')
61
65
  .max(200, 'Can not have more than 200 stages'),
62
66
  contentTypes: validateContentTypes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/admin",
3
- "version": "4.12.0-beta.1",
3
+ "version": "4.12.0-beta.4",
4
4
  "description": "Strapi Admin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,14 +42,14 @@
42
42
  "dependencies": {
43
43
  "@casl/ability": "^5.4.3",
44
44
  "@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
45
- "@strapi/data-transfer": "4.12.0-beta.1",
45
+ "@strapi/data-transfer": "4.12.0-beta.4",
46
46
  "@strapi/design-system": "1.8.2",
47
- "@strapi/helper-plugin": "4.12.0-beta.1",
47
+ "@strapi/helper-plugin": "4.12.0-beta.4",
48
48
  "@strapi/icons": "1.8.2",
49
- "@strapi/permissions": "4.12.0-beta.1",
50
- "@strapi/provider-audit-logs-local": "4.12.0-beta.1",
51
- "@strapi/typescript-utils": "4.12.0-beta.1",
52
- "@strapi/utils": "4.12.0-beta.1",
49
+ "@strapi/permissions": "4.12.0-beta.4",
50
+ "@strapi/provider-audit-logs-local": "4.12.0-beta.4",
51
+ "@strapi/typescript-utils": "4.12.0-beta.4",
52
+ "@strapi/utils": "4.12.0-beta.4",
53
53
  "axios": "1.4.0",
54
54
  "bcryptjs": "2.4.3",
55
55
  "browserslist": "^4.17.3",
@@ -117,7 +117,7 @@
117
117
  "reselect": "^4.1.7",
118
118
  "rimraf": "3.0.2",
119
119
  "sanitize-html": "2.10.0",
120
- "semver": "7.5.1",
120
+ "semver": "7.5.2",
121
121
  "sift": "16.0.1",
122
122
  "style-loader": "3.3.1",
123
123
  "styled-components": "5.3.3",
@@ -154,5 +154,5 @@
154
154
  }
155
155
  }
156
156
  },
157
- "gitHead": "27ed22ba71f95d728726d39712c3f576536b7f21"
157
+ "gitHead": "edcf86b496a7bee5189cdbfeb16f5a5c0abccc81"
158
158
  }