@strapi/admin 4.12.4 → 4.13.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 (74) hide show
  1. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
  2. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +8 -1
  3. package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
  4. package/admin/src/content-manager/components/{AttributeFilter/Filters.js → Filter/Filter.js} +5 -7
  5. package/admin/src/content-manager/components/Filter/index.js +1 -0
  6. package/admin/src/content-manager/hooks/useAllowedAttributes.js +47 -0
  7. package/admin/src/content-manager/hooks/useSyncRbac/index.js +10 -2
  8. package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
  9. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +2 -2
  10. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
  11. package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
  12. package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
  13. package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +74 -0
  14. package/admin/src/content-manager/pages/ListView/index.js +254 -68
  15. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +2 -2
  16. package/admin/src/content-manager/utils/getDisplayName.js +33 -0
  17. package/admin/src/content-manager/utils/index.js +1 -0
  18. package/admin/src/translations/en.json +3 -1
  19. package/admin/src/translations/zh-Hans.json +918 -902
  20. package/build/1227.32fe57ce.chunk.js +1 -0
  21. package/build/4174.fa8f9954.chunk.js +1 -0
  22. package/build/4546.ff09eeda.chunk.js +1 -0
  23. package/build/4724.baf7c5b1.chunk.js +6 -0
  24. package/build/6158.c3c13c20.chunk.js +1 -0
  25. package/build/78.dcc6df5c.chunk.js +1 -0
  26. package/build/{9806.3392505e.chunk.js → 9806.5d5a0e8d.chunk.js} +16 -16
  27. package/build/{Admin-authenticatedApp.f5ece8ff.chunk.js → Admin-authenticatedApp.53a24d28.chunk.js} +2 -2
  28. package/build/audit-logs-settings-page.0f73ccf8.chunk.js +1 -0
  29. package/build/content-manager.7f96a2f1.chunk.js +1097 -0
  30. package/build/{content-type-builder.40534de5.chunk.js → content-type-builder.cd999f6e.chunk.js} +2 -2
  31. package/build/{en-json.08c05fcf.chunk.js → en-json.4f06fe03.chunk.js} +1 -1
  32. package/build/i18n-translation-ru-json.a3dbc125.chunk.js +1 -0
  33. package/build/index.html +1 -1
  34. package/build/main.40b94779.js +2859 -0
  35. package/build/{runtime~main.bb4efc54.js → runtime~main.b16af570.js} +2 -2
  36. package/build/users-permissions-translation-zh-Hans-json.8d82c809.chunk.js +1 -0
  37. package/build/{users-roles-settings-page.3f9f063e.chunk.js → users-roles-settings-page.9d9a1eff.chunk.js} +1 -1
  38. package/build/zh-Hans-json.97efd015.chunk.js +1 -0
  39. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
  40. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
  41. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
  42. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
  43. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +149 -0
  44. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
  45. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +241 -0
  46. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
  47. package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
  48. package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
  49. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +51 -0
  50. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
  51. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  52. package/ee/server/constants/workflows.js +1 -0
  53. package/ee/server/controllers/index.js +1 -0
  54. package/ee/server/controllers/workflows/assignees/index.js +44 -0
  55. package/ee/server/routes/review-workflows.js +17 -0
  56. package/ee/server/services/index.js +1 -0
  57. package/ee/server/services/review-workflows/assignees.js +54 -0
  58. package/ee/server/services/review-workflows/metrics/index.js +5 -0
  59. package/ee/server/services/review-workflows/review-workflows.js +20 -11
  60. package/ee/server/validation/review-workflows.js +8 -0
  61. package/package.json +10 -10
  62. package/server/services/permission/permissions-manager/sanitize.js +12 -0
  63. package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
  64. package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
  65. package/build/3984.dda474f7.chunk.js +0 -1
  66. package/build/4546.cfafae68.chunk.js +0 -1
  67. package/build/5483.6dd2e776.chunk.js +0 -6
  68. package/build/6158.c974fd83.chunk.js +0 -1
  69. package/build/audit-logs-settings-page.4b422831.chunk.js +0 -1
  70. package/build/content-manager.2af15f57.chunk.js +0 -1099
  71. package/build/i18n-translation-ru-json.401bc498.chunk.js +0 -1
  72. package/build/main.f13fc96c.js +0 -2856
  73. package/build/users-permissions-translation-zh-Hans-json.6ab714ee.chunk.js +0 -1
  74. package/build/zh-Hans-json.937b395b.chunk.js +0 -1
@@ -0,0 +1,71 @@
1
+ import { getTrad } from '../../../../../../../admin/src/content-manager/utils';
2
+
3
+ import { AssigneeFilter } from './AssigneeFilter';
4
+ import { StageFilter } from './StageFilter';
5
+
6
+ export const REVIEW_WORKFLOW_FILTERS = [
7
+ {
8
+ fieldSchema: {
9
+ type: 'relation',
10
+ mainField: {
11
+ name: 'name',
12
+
13
+ schema: {
14
+ type: 'string',
15
+ },
16
+ },
17
+ },
18
+
19
+ metadatas: {
20
+ customInput: StageFilter,
21
+
22
+ label: {
23
+ id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
24
+ defaultMessage: 'Review stage',
25
+ },
26
+ },
27
+
28
+ name: 'strapi_stage',
29
+ },
30
+
31
+ {
32
+ fieldSchema: {
33
+ type: 'relation',
34
+ mainField: {
35
+ name: 'id',
36
+
37
+ schema: {
38
+ type: 'int',
39
+ },
40
+ },
41
+ },
42
+
43
+ metadatas: {
44
+ customInput: AssigneeFilter,
45
+
46
+ customOperators: [
47
+ {
48
+ intlLabel: {
49
+ id: 'components.FilterOptions.FILTER_TYPES.$eq',
50
+ defaultMessage: 'is',
51
+ },
52
+ value: '$eq',
53
+ },
54
+ {
55
+ intlLabel: {
56
+ id: 'components.FilterOptions.FILTER_TYPES.$ne',
57
+ defaultMessage: 'is not',
58
+ },
59
+ value: '$ne',
60
+ },
61
+ ],
62
+
63
+ label: {
64
+ id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.assignee.label`),
65
+ defaultMessage: 'Assignee',
66
+ },
67
+ },
68
+
69
+ name: 'strapi_assignee',
70
+ },
71
+ ];
@@ -1,238 +1,30 @@
1
1
  import React from 'react';
2
2
 
3
- import { Flex, Loader, SingleSelect, SingleSelectOption, Typography } from '@strapi/design-system';
4
- import {
5
- useAPIErrorHandler,
6
- useCMEditViewDataManager,
7
- useFetchClient,
8
- useNotification,
9
- } from '@strapi/helper-plugin';
10
- import { useIntl } from 'react-intl';
11
- import { useMutation } from 'react-query';
3
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
12
4
 
13
5
  import { Information } from '../../../../../../admin/src/content-manager/pages/EditView/Information';
14
- import { useLicenseLimits } from '../../../../hooks/useLicenseLimits';
15
- import * as LimitsModal from '../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
16
- import {
17
- CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
18
- CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
19
- } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
20
- import { useReviewWorkflows } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
21
- import { getStageColorByHex } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
22
6
 
23
- const ATTRIBUTE_NAME = 'strapi_stage';
7
+ import { AssigneeSelect } from './components/AssigneeSelect';
8
+ import { StageSelect } from './components/StageSelect';
24
9
 
25
10
  export function InformationBoxEE() {
26
11
  const {
27
- initialData,
28
12
  isCreatingEntry,
29
- layout: { uid, options },
30
- isSingleType,
31
- onChange,
13
+ layout: { options },
32
14
  } = useCMEditViewDataManager();
33
- const { put } = useFetchClient();
34
- // it is possible to rely on initialData here, because it always will
35
- // be updated at the same time when modifiedData is updated, otherwise
36
- // the entity is flagged as modified
37
- const activeWorkflowStage = initialData?.[ATTRIBUTE_NAME] ?? null;
38
- const hasReviewWorkflowsEnabled = options?.reviewWorkflows ?? false;
39
- const { formatMessage } = useIntl();
40
- const { formatAPIError } = useAPIErrorHandler();
41
- const toggleNotification = useNotification();
42
- const { getFeature } = useLicenseLimits();
43
- const [showLimitModal, setShowLimitModal] = React.useState(false);
44
-
45
- const {
46
- meta,
47
- workflows: [workflow],
48
- isLoading: isWorkflowLoading,
49
- } = useReviewWorkflows({ filters: { contentTypes: uid } });
50
-
51
- const { error, isLoading, mutateAsync } = useMutation(
52
- async ({ entityId, stageId, uid }) => {
53
- const typeSlug = isSingleType ? 'single-types' : 'collection-types';
54
-
55
- const {
56
- data: { data: createdEntity },
57
- } = await put(`/admin/content-manager/${typeSlug}/${uid}/${entityId}/stage`, {
58
- data: { id: stageId },
59
- });
60
-
61
- // initialData and modifiedData have to stay in sync, otherwise the entity would be flagged
62
- // as modified, which is what the boolean flag is for
63
- onChange({ target: { name: ATTRIBUTE_NAME, value: createdEntity[ATTRIBUTE_NAME] } }, true);
64
-
65
- return createdEntity;
66
- },
67
- {
68
- onSuccess() {
69
- toggleNotification({
70
- type: 'success',
71
- message: {
72
- id: 'content-manager.reviewWorkflows.stage.notification.saved',
73
- defaultMessage: 'Review stage updated',
74
- },
75
- });
76
- },
77
- }
78
- );
79
-
80
- const limits = getFeature('review-workflows');
81
- const formattedError = (error && formatAPIError(error)) || null;
82
-
83
- const handleStageChange = async ({ value: stageId }) => {
84
- try {
85
- /**
86
- * If the current license has a limit:
87
- * check if the total count of workflows exceeds that limit and display
88
- * the limits modal.
89
- *
90
- * If the current license does not have a limit (e.g. offline license):
91
- * do nothing (for now).
92
- *
93
- */
94
15
 
95
- if (
96
- limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
97
- parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10) < meta.workflowCount
98
- ) {
99
- setShowLimitModal('workflow');
100
-
101
- /**
102
- * If the current license has a limit:
103
- * check if the total count of stages exceeds that limit and display
104
- * the limits modal.
105
- *
106
- * If the current license does not have a limit (e.g. offline license):
107
- * do nothing (for now).
108
- *
109
- */
110
- } else if (
111
- limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
112
- parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10) <
113
- workflow.stages.length
114
- ) {
115
- setShowLimitModal('stage');
116
- } else {
117
- await mutateAsync({
118
- entityId: initialData.id,
119
- stageId,
120
- uid,
121
- });
122
- }
123
- } catch (error) {
124
- // react-query@v3: the error doesn't have to be handled here
125
- // see: https://github.com/TanStack/query/issues/121
126
- }
127
- };
128
-
129
- const { themeColorName } = activeWorkflowStage?.color
130
- ? getStageColorByHex(activeWorkflowStage?.color)
131
- : {};
16
+ const hasReviewWorkflowsEnabled = options?.reviewWorkflows ?? false;
132
17
 
133
18
  return (
134
19
  <Information.Root>
135
20
  <Information.Title />
136
-
137
21
  {hasReviewWorkflowsEnabled && !isCreatingEntry && (
138
- <SingleSelect
139
- error={formattedError}
140
- name={ATTRIBUTE_NAME}
141
- id={ATTRIBUTE_NAME}
142
- value={activeWorkflowStage?.id}
143
- onChange={(value) => handleStageChange({ value })}
144
- label={formatMessage({
145
- id: 'content-manager.reviewWorkflows.stage.label',
146
- defaultMessage: 'Review stage',
147
- })}
148
- startIcon={
149
- <Flex
150
- as="span"
151
- height={2}
152
- background={activeWorkflowStage?.color}
153
- borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
154
- hasRadius
155
- shrink={0}
156
- width={2}
157
- marginRight="-3px"
158
- />
159
- }
160
- // eslint-disable-next-line react/no-unstable-nested-components
161
- customizeContent={() => (
162
- <Flex as="span" justifyContent="space-between" alignItems="center" width="100%">
163
- <Typography textColor="neutral800" ellipsis>
164
- {activeWorkflowStage?.name}
165
- </Typography>
166
- {isWorkflowLoading || isLoading ? <Loader small style={{ display: 'flex' }} /> : null}
167
- </Flex>
168
- )}
169
- >
170
- {workflow
171
- ? workflow.stages.map(({ id, color, name }) => {
172
- const { themeColorName } = getStageColorByHex(color);
173
-
174
- return (
175
- <SingleSelectOption
176
- startIcon={
177
- <Flex
178
- height={2}
179
- background={color}
180
- borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
181
- hasRadius
182
- shrink={0}
183
- width={2}
184
- />
185
- }
186
- value={id}
187
- textValue={name}
188
- >
189
- {name}
190
- </SingleSelectOption>
191
- );
192
- })
193
- : []}
194
- </SingleSelect>
22
+ <>
23
+ <StageSelect />
24
+ <AssigneeSelect />
25
+ </>
195
26
  )}
196
-
197
27
  <Information.Body />
198
-
199
- <LimitsModal.Root
200
- isOpen={showLimitModal === 'workflow'}
201
- onClose={() => setShowLimitModal(false)}
202
- >
203
- <LimitsModal.Title>
204
- {formatMessage({
205
- id: 'content-manager.reviewWorkflows.workflows.limit.title',
206
- defaultMessage: 'You’ve reached the limit of workflows in your plan',
207
- })}
208
- </LimitsModal.Title>
209
-
210
- <LimitsModal.Body>
211
- {formatMessage({
212
- id: 'content-manager.reviewWorkflows.workflows.limit.body',
213
- defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
214
- })}
215
- </LimitsModal.Body>
216
- </LimitsModal.Root>
217
-
218
- <LimitsModal.Root
219
- isOpen={showLimitModal === 'stage'}
220
- onClose={() => setShowLimitModal(false)}
221
- >
222
- <LimitsModal.Title>
223
- {formatMessage({
224
- id: 'content-manager.reviewWorkflows.stages.limit.title',
225
- defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
226
- })}
227
- </LimitsModal.Title>
228
-
229
- <LimitsModal.Body>
230
- {formatMessage({
231
- id: 'content-manager.reviewWorkflows.stages.limit.body',
232
- defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
233
- })}
234
- </LimitsModal.Body>
235
- </LimitsModal.Root>
236
28
  </Information.Root>
237
29
  );
238
30
  }
@@ -0,0 +1,149 @@
1
+ import * as React from 'react';
2
+
3
+ import { Field, FieldLabel, FieldError, Flex, Loader } from '@strapi/design-system';
4
+ import {
5
+ // eslint-disable-next-line no-restricted-imports
6
+ ReactSelect,
7
+ useCMEditViewDataManager,
8
+ useAPIErrorHandler,
9
+ useFetchClient,
10
+ useNotification,
11
+ } from '@strapi/helper-plugin';
12
+ import { useIntl } from 'react-intl';
13
+ import { useMutation } from 'react-query';
14
+
15
+ import { useAdminUsers } from '../../../../../../../../admin/src/hooks/useAdminUsers';
16
+ import { ASSIGNEE_ATTRIBUTE_NAME } from '../../constants';
17
+
18
+ export function AssigneeSelect() {
19
+ const {
20
+ initialData,
21
+ layout: { uid },
22
+ isSingleType,
23
+ onChange,
24
+ } = useCMEditViewDataManager();
25
+ const { formatMessage } = useIntl();
26
+ const { formatAPIError } = useAPIErrorHandler();
27
+ const toggleNotification = useNotification();
28
+ const { put } = useFetchClient();
29
+ const { users, isLoading, isError } = useAdminUsers();
30
+
31
+ const currentAssignee = initialData?.[ASSIGNEE_ATTRIBUTE_NAME] ?? null;
32
+
33
+ const handleChange = async ({ value: assigneeId }) => {
34
+ mutation.mutate({
35
+ entityId: initialData.id,
36
+ assigneeId,
37
+ uid,
38
+ });
39
+ };
40
+
41
+ const mutation = useMutation(
42
+ async ({ entityId, assigneeId, uid }) => {
43
+ const typeSlug = isSingleType ? 'single-types' : 'collection-types';
44
+
45
+ const {
46
+ data: { data: createdEntity },
47
+ } = await put(`/admin/content-manager/${typeSlug}/${uid}/${entityId}/assignee`, {
48
+ data: { id: assigneeId },
49
+ });
50
+
51
+ // initialData and modifiedData have to stay in sync, otherwise the entity would be flagged
52
+ // as modified, which is what the boolean flag is for
53
+ onChange(
54
+ {
55
+ target: { name: ASSIGNEE_ATTRIBUTE_NAME, value: createdEntity[ASSIGNEE_ATTRIBUTE_NAME] },
56
+ },
57
+ true
58
+ );
59
+
60
+ return createdEntity;
61
+ },
62
+ {
63
+ onSuccess() {
64
+ toggleNotification({
65
+ type: 'success',
66
+ message: {
67
+ id: 'content-manager.reviewWorkflows.assignee.notification.saved',
68
+ defaultMessage: 'Success: Assignee updated',
69
+ },
70
+ });
71
+ },
72
+ }
73
+ );
74
+
75
+ const formattedError =
76
+ (isError &&
77
+ formatMessage({
78
+ id: 'content-manager.reviewWorkflows.assignee.error',
79
+ defaultMessage: 'An error occurred while fetching users',
80
+ })) ||
81
+ (mutation.error && formatAPIError(mutation.error));
82
+
83
+ return (
84
+ <Field error={formattedError} name={ASSIGNEE_ATTRIBUTE_NAME} id={ASSIGNEE_ATTRIBUTE_NAME}>
85
+ <Flex direction="column" gap={2} alignItems="stretch">
86
+ <FieldLabel>
87
+ {formatMessage({
88
+ id: 'content-manager.reviewWorkflows.assignee.label',
89
+ defaultMessage: 'Assignee',
90
+ })}
91
+ </FieldLabel>
92
+
93
+ <ReactSelect
94
+ components={{
95
+ LoadingIndicator: () => <Loader data-testid="loader" small />,
96
+ }}
97
+ disabled={isError}
98
+ error={formattedError}
99
+ inputId={ASSIGNEE_ATTRIBUTE_NAME}
100
+ isLoading={isLoading || mutation.isLoading}
101
+ isSearchable
102
+ isClearable
103
+ name={ASSIGNEE_ATTRIBUTE_NAME}
104
+ onChange={(selectedOption, triggeredAction) => {
105
+ if (triggeredAction.action === 'clear') {
106
+ handleChange({ value: null });
107
+
108
+ return;
109
+ }
110
+
111
+ handleChange({ value: selectedOption.value });
112
+ }}
113
+ options={users.map(({ id, firstname, lastname }) => ({
114
+ value: id,
115
+ label: formatMessage(
116
+ {
117
+ id: 'content-manager.reviewWorkflows.assignee.name',
118
+ defaultMessage: '{firstname} {lastname}',
119
+ },
120
+ {
121
+ firstname,
122
+ lastname,
123
+ }
124
+ ),
125
+ }))}
126
+ value={
127
+ currentAssignee
128
+ ? {
129
+ value: currentAssignee.id,
130
+ label: formatMessage(
131
+ {
132
+ id: 'content-manager.reviewWorkflows.assignee.name',
133
+ defaultMessage: '{firstname} {lastname}',
134
+ },
135
+ {
136
+ firstname: currentAssignee.firstname,
137
+ lastname: currentAssignee.lastname,
138
+ }
139
+ ),
140
+ }
141
+ : null
142
+ }
143
+ />
144
+
145
+ <FieldError />
146
+ </Flex>
147
+ </Field>
148
+ );
149
+ }