@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.
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
- package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +8 -1
- package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
- package/admin/src/content-manager/components/{AttributeFilter/Filters.js → Filter/Filter.js} +5 -7
- package/admin/src/content-manager/components/Filter/index.js +1 -0
- package/admin/src/content-manager/hooks/useAllowedAttributes.js +47 -0
- package/admin/src/content-manager/hooks/useSyncRbac/index.js +10 -2
- package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
- package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +2 -2
- package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
- package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
- package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
- package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +74 -0
- package/admin/src/content-manager/pages/ListView/index.js +254 -68
- package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +2 -2
- package/admin/src/content-manager/utils/getDisplayName.js +33 -0
- package/admin/src/content-manager/utils/index.js +1 -0
- package/admin/src/translations/en.json +3 -1
- package/admin/src/translations/zh-Hans.json +918 -902
- package/build/1227.32fe57ce.chunk.js +1 -0
- package/build/4174.fa8f9954.chunk.js +1 -0
- package/build/4546.ff09eeda.chunk.js +1 -0
- package/build/4724.baf7c5b1.chunk.js +6 -0
- package/build/6158.c3c13c20.chunk.js +1 -0
- package/build/78.dcc6df5c.chunk.js +1 -0
- package/build/{9806.3392505e.chunk.js → 9806.5d5a0e8d.chunk.js} +16 -16
- package/build/{Admin-authenticatedApp.f5ece8ff.chunk.js → Admin-authenticatedApp.53a24d28.chunk.js} +2 -2
- package/build/audit-logs-settings-page.0f73ccf8.chunk.js +1 -0
- package/build/content-manager.7f96a2f1.chunk.js +1097 -0
- package/build/{content-type-builder.40534de5.chunk.js → content-type-builder.cd999f6e.chunk.js} +2 -2
- package/build/{en-json.08c05fcf.chunk.js → en-json.4f06fe03.chunk.js} +1 -1
- package/build/i18n-translation-ru-json.a3dbc125.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/main.40b94779.js +2859 -0
- package/build/{runtime~main.bb4efc54.js → runtime~main.b16af570.js} +2 -2
- package/build/users-permissions-translation-zh-Hans-json.8d82c809.chunk.js +1 -0
- package/build/{users-roles-settings-page.3f9f063e.chunk.js → users-roles-settings-page.9d9a1eff.chunk.js} +1 -1
- package/build/zh-Hans-json.97efd015.chunk.js +1 -0
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +149 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +241 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
- package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +51 -0
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
- package/ee/server/constants/workflows.js +1 -0
- package/ee/server/controllers/index.js +1 -0
- package/ee/server/controllers/workflows/assignees/index.js +44 -0
- package/ee/server/routes/review-workflows.js +17 -0
- package/ee/server/services/index.js +1 -0
- package/ee/server/services/review-workflows/assignees.js +54 -0
- package/ee/server/services/review-workflows/metrics/index.js +5 -0
- package/ee/server/services/review-workflows/review-workflows.js +20 -11
- package/ee/server/validation/review-workflows.js +8 -0
- package/package.json +10 -10
- package/server/services/permission/permissions-manager/sanitize.js +12 -0
- package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
- package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
- package/build/3984.dda474f7.chunk.js +0 -1
- package/build/4546.cfafae68.chunk.js +0 -1
- package/build/5483.6dd2e776.chunk.js +0 -6
- package/build/6158.c974fd83.chunk.js +0 -1
- package/build/audit-logs-settings-page.4b422831.chunk.js +0 -1
- package/build/content-manager.2af15f57.chunk.js +0 -1099
- package/build/i18n-translation-ru-json.401bc498.chunk.js +0 -1
- package/build/main.f13fc96c.js +0 -2856
- package/build/users-permissions-translation-zh-Hans-json.6ab714ee.chunk.js +0 -1
- package/build/zh-Hans-json.937b395b.chunk.js +0 -1
package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
SingleSelect,
|
|
5
|
+
SingleSelectOption,
|
|
6
|
+
Field,
|
|
7
|
+
FieldError,
|
|
8
|
+
Flex,
|
|
9
|
+
Loader,
|
|
10
|
+
Typography,
|
|
11
|
+
} from '@strapi/design-system';
|
|
12
|
+
import {
|
|
13
|
+
useCMEditViewDataManager,
|
|
14
|
+
useAPIErrorHandler,
|
|
15
|
+
useFetchClient,
|
|
16
|
+
useNotification,
|
|
17
|
+
} from '@strapi/helper-plugin';
|
|
18
|
+
import { useIntl } from 'react-intl';
|
|
19
|
+
import { useMutation } from 'react-query';
|
|
20
|
+
|
|
21
|
+
import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
|
|
22
|
+
import * as LimitsModal from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
|
|
23
|
+
import {
|
|
24
|
+
CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
|
|
25
|
+
CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
|
|
26
|
+
} from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
|
|
27
|
+
import { useReviewWorkflows } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
|
|
28
|
+
import { getStageColorByHex } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
|
|
29
|
+
import { STAGE_ATTRIBUTE_NAME } from '../../constants';
|
|
30
|
+
|
|
31
|
+
export function StageSelect() {
|
|
32
|
+
const {
|
|
33
|
+
initialData,
|
|
34
|
+
layout: { uid },
|
|
35
|
+
isSingleType,
|
|
36
|
+
onChange,
|
|
37
|
+
} = useCMEditViewDataManager();
|
|
38
|
+
const { put } = useFetchClient();
|
|
39
|
+
const { formatMessage } = useIntl();
|
|
40
|
+
const { formatAPIError } = useAPIErrorHandler();
|
|
41
|
+
const toggleNotification = useNotification();
|
|
42
|
+
const {
|
|
43
|
+
meta,
|
|
44
|
+
workflows: [workflow],
|
|
45
|
+
isLoading,
|
|
46
|
+
} = useReviewWorkflows({ filters: { contentTypes: uid } });
|
|
47
|
+
const { getFeature } = useLicenseLimits();
|
|
48
|
+
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
|
49
|
+
|
|
50
|
+
const limits = getFeature('review-workflows');
|
|
51
|
+
// it is possible to rely on initialData here, because it always will
|
|
52
|
+
// be updated at the same time when modifiedData is updated, otherwise
|
|
53
|
+
// the entity is flagged as modified
|
|
54
|
+
const activeWorkflowStage = initialData?.[STAGE_ATTRIBUTE_NAME] ?? null;
|
|
55
|
+
|
|
56
|
+
const mutation = useMutation(
|
|
57
|
+
async ({ entityId, stageId, uid }) => {
|
|
58
|
+
const typeSlug = isSingleType ? 'single-types' : 'collection-types';
|
|
59
|
+
|
|
60
|
+
const {
|
|
61
|
+
data: { data: createdEntity },
|
|
62
|
+
} = await put(`/admin/content-manager/${typeSlug}/${uid}/${entityId}/stage`, {
|
|
63
|
+
data: { id: stageId },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// initialData and modifiedData have to stay in sync, otherwise the entity would be flagged
|
|
67
|
+
// as modified, which is what the boolean flag is for
|
|
68
|
+
onChange(
|
|
69
|
+
{ target: { name: STAGE_ATTRIBUTE_NAME, value: createdEntity[STAGE_ATTRIBUTE_NAME] } },
|
|
70
|
+
true
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return createdEntity;
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
onSuccess() {
|
|
77
|
+
toggleNotification({
|
|
78
|
+
type: 'success',
|
|
79
|
+
message: {
|
|
80
|
+
id: 'content-manager.reviewWorkflows.stage.notification.saved',
|
|
81
|
+
defaultMessage: 'Review stage updated',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const handleChange = async ({ value: stageId }) => {
|
|
89
|
+
try {
|
|
90
|
+
/**
|
|
91
|
+
* If the current license has a limit:
|
|
92
|
+
* check if the total count of workflows exceeds that limit and display
|
|
93
|
+
* the limits modal.
|
|
94
|
+
*
|
|
95
|
+
* If the current license does not have a limit (e.g. offline license):
|
|
96
|
+
* do nothing (for now).
|
|
97
|
+
*
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
102
|
+
parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10) < meta.workflowCount
|
|
103
|
+
) {
|
|
104
|
+
setShowLimitModal('workflow');
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* If the current license has a limit:
|
|
108
|
+
* check if the total count of stages exceeds that limit and display
|
|
109
|
+
* the limits modal.
|
|
110
|
+
*
|
|
111
|
+
* If the current license does not have a limit (e.g. offline license):
|
|
112
|
+
* do nothing (for now).
|
|
113
|
+
*
|
|
114
|
+
*/
|
|
115
|
+
} else if (
|
|
116
|
+
limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
117
|
+
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10) < workflow.stages.length
|
|
118
|
+
) {
|
|
119
|
+
setShowLimitModal('stage');
|
|
120
|
+
} else {
|
|
121
|
+
mutation.mutateAsync({
|
|
122
|
+
entityId: initialData.id,
|
|
123
|
+
stageId,
|
|
124
|
+
uid,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// react-query@v3: the error doesn't have to be handled here
|
|
129
|
+
// see: https://github.com/TanStack/query/issues/121
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const { themeColorName } = activeWorkflowStage?.color
|
|
134
|
+
? getStageColorByHex(activeWorkflowStage?.color)
|
|
135
|
+
: {};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<>
|
|
139
|
+
<Field name={STAGE_ATTRIBUTE_NAME} id={STAGE_ATTRIBUTE_NAME}>
|
|
140
|
+
<Flex direction="column" gap={2} alignItems="stretch">
|
|
141
|
+
<SingleSelect
|
|
142
|
+
error={(mutation.error && formatAPIError(mutation.error)) || null}
|
|
143
|
+
name={STAGE_ATTRIBUTE_NAME}
|
|
144
|
+
id={STAGE_ATTRIBUTE_NAME}
|
|
145
|
+
value={activeWorkflowStage?.id}
|
|
146
|
+
onChange={(value) => handleChange({ value })}
|
|
147
|
+
label={formatMessage({
|
|
148
|
+
id: 'content-manager.reviewWorkflows.stage.label',
|
|
149
|
+
defaultMessage: 'Review stage',
|
|
150
|
+
})}
|
|
151
|
+
startIcon={
|
|
152
|
+
<Flex
|
|
153
|
+
as="span"
|
|
154
|
+
height={2}
|
|
155
|
+
background={activeWorkflowStage?.color}
|
|
156
|
+
borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
|
|
157
|
+
hasRadius
|
|
158
|
+
shrink={0}
|
|
159
|
+
width={2}
|
|
160
|
+
marginRight="-3px"
|
|
161
|
+
/>
|
|
162
|
+
}
|
|
163
|
+
// eslint-disable-next-line react/no-unstable-nested-components
|
|
164
|
+
customizeContent={() => (
|
|
165
|
+
<Flex as="span" justifyContent="space-between" alignItems="center" width="100%">
|
|
166
|
+
<Typography textColor="neutral800" ellipsis>
|
|
167
|
+
{activeWorkflowStage?.name}
|
|
168
|
+
</Typography>
|
|
169
|
+
{isLoading ? <Loader small style={{ display: 'flex' }} /> : null}
|
|
170
|
+
</Flex>
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
{workflow
|
|
174
|
+
? workflow.stages.map(({ id, color, name }) => {
|
|
175
|
+
const { themeColorName } = getStageColorByHex(color);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<SingleSelectOption
|
|
179
|
+
startIcon={
|
|
180
|
+
<Flex
|
|
181
|
+
height={2}
|
|
182
|
+
background={color}
|
|
183
|
+
borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
|
|
184
|
+
hasRadius
|
|
185
|
+
shrink={0}
|
|
186
|
+
width={2}
|
|
187
|
+
/>
|
|
188
|
+
}
|
|
189
|
+
value={id}
|
|
190
|
+
textValue={name}
|
|
191
|
+
>
|
|
192
|
+
{name}
|
|
193
|
+
</SingleSelectOption>
|
|
194
|
+
);
|
|
195
|
+
})
|
|
196
|
+
: []}
|
|
197
|
+
</SingleSelect>
|
|
198
|
+
<FieldError />
|
|
199
|
+
</Flex>
|
|
200
|
+
</Field>
|
|
201
|
+
|
|
202
|
+
<LimitsModal.Root
|
|
203
|
+
isOpen={showLimitModal === 'workflow'}
|
|
204
|
+
onClose={() => setShowLimitModal(false)}
|
|
205
|
+
>
|
|
206
|
+
<LimitsModal.Title>
|
|
207
|
+
{formatMessage({
|
|
208
|
+
id: 'content-manager.reviewWorkflows.workflows.limit.title',
|
|
209
|
+
defaultMessage: 'You’ve reached the limit of workflows in your plan',
|
|
210
|
+
})}
|
|
211
|
+
</LimitsModal.Title>
|
|
212
|
+
|
|
213
|
+
<LimitsModal.Body>
|
|
214
|
+
{formatMessage({
|
|
215
|
+
id: 'content-manager.reviewWorkflows.workflows.limit.body',
|
|
216
|
+
defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
|
|
217
|
+
})}
|
|
218
|
+
</LimitsModal.Body>
|
|
219
|
+
</LimitsModal.Root>
|
|
220
|
+
|
|
221
|
+
<LimitsModal.Root
|
|
222
|
+
isOpen={showLimitModal === 'stage'}
|
|
223
|
+
onClose={() => setShowLimitModal(false)}
|
|
224
|
+
>
|
|
225
|
+
<LimitsModal.Title>
|
|
226
|
+
{formatMessage({
|
|
227
|
+
id: 'content-manager.reviewWorkflows.stages.limit.title',
|
|
228
|
+
defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
|
|
229
|
+
})}
|
|
230
|
+
</LimitsModal.Title>
|
|
231
|
+
|
|
232
|
+
<LimitsModal.Body>
|
|
233
|
+
{formatMessage({
|
|
234
|
+
id: 'content-manager.reviewWorkflows.stages.limit.body',
|
|
235
|
+
defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
|
|
236
|
+
})}
|
|
237
|
+
</LimitsModal.Body>
|
|
238
|
+
</LimitsModal.Root>
|
|
239
|
+
</>
|
|
240
|
+
);
|
|
241
|
+
}
|
package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './StageSelect';
|
package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Typography } from '@strapi/design-system';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
6
|
+
|
|
7
|
+
import getTrad from '../../../../../../admin/src/content-manager/utils/getTrad';
|
|
8
|
+
|
|
9
|
+
export function ReviewWorkflowsAssigneeEE({ firstname, lastname, displayname }) {
|
|
10
|
+
const { formatMessage } = useIntl();
|
|
11
|
+
|
|
12
|
+
// TODO align with changes from this PR, using the getDisplayName util
|
|
13
|
+
// https://github.com/strapi/strapi/pull/17043/
|
|
14
|
+
if (displayname) {
|
|
15
|
+
return (
|
|
16
|
+
<Typography textColor="neutral800">
|
|
17
|
+
{formatMessage(
|
|
18
|
+
{
|
|
19
|
+
id: getTrad(`containers.ListPage.reviewWorkflows.assignee`),
|
|
20
|
+
defaultMessage: '{displayname}',
|
|
21
|
+
},
|
|
22
|
+
{ displayname }
|
|
23
|
+
)}
|
|
24
|
+
</Typography>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Typography textColor="neutral800">
|
|
30
|
+
{formatMessage(
|
|
31
|
+
{
|
|
32
|
+
id: getTrad(`containers.ListPage.reviewWorkflows.assignee`),
|
|
33
|
+
defaultMessage: '{firstname} {lastname}',
|
|
34
|
+
},
|
|
35
|
+
{ firstname, lastname }
|
|
36
|
+
)}
|
|
37
|
+
</Typography>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ReviewWorkflowsAssigneeEE.defaultProps = {
|
|
42
|
+
firstname: '',
|
|
43
|
+
lastname: '',
|
|
44
|
+
displayname: '',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ReviewWorkflowsAssigneeEE.propTypes = {
|
|
48
|
+
firstname: PropTypes.string,
|
|
49
|
+
lastname: PropTypes.string,
|
|
50
|
+
displayname: PropTypes.string,
|
|
51
|
+
};
|
|
@@ -1,24 +1,51 @@
|
|
|
1
1
|
import getTrad from '../../../../../../admin/src/content-manager/utils/getTrad';
|
|
2
|
+
import {
|
|
3
|
+
ASSIGNEE_ATTRIBUTE_NAME,
|
|
4
|
+
STAGE_ATTRIBUTE_NAME,
|
|
5
|
+
} from '../../EditView/InformationBox/constants';
|
|
2
6
|
|
|
3
|
-
export const REVIEW_WORKFLOW_COLUMNS_EE =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
export const REVIEW_WORKFLOW_COLUMNS_EE = [
|
|
8
|
+
{
|
|
9
|
+
key: `__${STAGE_ATTRIBUTE_NAME}_temp_key__`,
|
|
10
|
+
name: STAGE_ATTRIBUTE_NAME,
|
|
11
|
+
fieldSchema: {
|
|
12
|
+
type: 'relation',
|
|
13
|
+
},
|
|
14
|
+
metadatas: {
|
|
15
|
+
// formatMessage() will be applied when the column is rendered
|
|
16
|
+
label: {
|
|
17
|
+
id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
|
|
18
|
+
defaultMessage: 'Review stage',
|
|
19
|
+
},
|
|
20
|
+
searchable: false,
|
|
21
|
+
sortable: true,
|
|
22
|
+
mainField: {
|
|
23
|
+
name: 'name',
|
|
24
|
+
schema: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
8
29
|
},
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
30
|
+
{
|
|
31
|
+
key: `__${ASSIGNEE_ATTRIBUTE_NAME}_temp_key__`,
|
|
32
|
+
name: ASSIGNEE_ATTRIBUTE_NAME,
|
|
33
|
+
fieldSchema: {
|
|
34
|
+
type: 'relation',
|
|
14
35
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
metadatas: {
|
|
37
|
+
label: {
|
|
38
|
+
id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.assignee`),
|
|
39
|
+
defaultMessage: 'Assignee',
|
|
40
|
+
},
|
|
41
|
+
searchable: false,
|
|
42
|
+
sortable: true,
|
|
43
|
+
mainField: {
|
|
44
|
+
name: 'firstname',
|
|
45
|
+
schema: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
},
|
|
21
48
|
},
|
|
22
49
|
},
|
|
23
50
|
},
|
|
24
|
-
|
|
51
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getService } = require('../../../utils');
|
|
4
|
+
const { validateUpdateAssigneeOnEntity } = require('../../../validation/review-workflows');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
/**
|
|
8
|
+
* Updates an entity's assignee.
|
|
9
|
+
* @async
|
|
10
|
+
* @param {Object} ctx - The Koa context object.
|
|
11
|
+
* @param {Object} ctx.params - An object containing the parameters from the request URL.
|
|
12
|
+
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
13
|
+
* @param {string} ctx.params.id - The ID of the entity to update.
|
|
14
|
+
* @param {Object} ctx.request.body.data - Optional data object containing the new assignee ID for the entity.
|
|
15
|
+
* @param {string} ctx.request.body.data.id - The ID of the new assignee for the entity.
|
|
16
|
+
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
17
|
+
* @throws {ValidationError} If the `data` object in the request body fails to pass validation.
|
|
18
|
+
* @returns {Promise<void>} A promise that resolves when the entity's assignee has been updated.
|
|
19
|
+
*/
|
|
20
|
+
async updateEntity(ctx) {
|
|
21
|
+
const assigneeService = getService('assignees');
|
|
22
|
+
const workflowService = getService('workflows');
|
|
23
|
+
|
|
24
|
+
const { model_uid: model, id } = ctx.params;
|
|
25
|
+
|
|
26
|
+
const { sanitizeOutput } = strapi
|
|
27
|
+
.plugin('content-manager')
|
|
28
|
+
.service('permission-checker')
|
|
29
|
+
.create({ userAbility: ctx.state.userAbility, model });
|
|
30
|
+
|
|
31
|
+
// TODO: check if user has update permission on the entity
|
|
32
|
+
|
|
33
|
+
const { id: assigneeId } = await validateUpdateAssigneeOnEntity(
|
|
34
|
+
ctx.request?.body?.data,
|
|
35
|
+
'You should pass a valid id to the body of the put request.'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
await workflowService.assertContentTypeBelongsToWorkflow(model);
|
|
39
|
+
|
|
40
|
+
const entity = await assigneeService.updateEntityAssignee(id, model, assigneeId);
|
|
41
|
+
|
|
42
|
+
ctx.body = { data: await sanitizeOutput(entity) };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -142,5 +142,22 @@ module.exports = {
|
|
|
142
142
|
],
|
|
143
143
|
},
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
method: 'PUT',
|
|
147
|
+
path: '/content-manager/(collection|single)-types/:model_uid/:id/assignee',
|
|
148
|
+
handler: 'assignees.updateEntity',
|
|
149
|
+
config: {
|
|
150
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
151
|
+
policies: [
|
|
152
|
+
'admin::isAuthenticatedAdmin',
|
|
153
|
+
{
|
|
154
|
+
name: 'admin::hasPermissions',
|
|
155
|
+
config: {
|
|
156
|
+
actions: ['admin::users.read', 'admin::review-workflows.read'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
145
162
|
],
|
|
146
163
|
};
|
|
@@ -8,6 +8,7 @@ module.exports = {
|
|
|
8
8
|
'seat-enforcement': require('./seat-enforcement'),
|
|
9
9
|
workflows: require('./review-workflows/workflows'),
|
|
10
10
|
stages: require('./review-workflows/stages'),
|
|
11
|
+
assignees: require('./review-workflows/assignees'),
|
|
11
12
|
'review-workflows': require('./review-workflows/review-workflows'),
|
|
12
13
|
'review-workflows-validation': require('./review-workflows/validation'),
|
|
13
14
|
'review-workflows-decorator': require('./review-workflows/entity-service-decorator'),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ApplicationError } = require('@strapi/utils').errors;
|
|
4
|
+
const { isNil } = require('lodash/fp');
|
|
5
|
+
const { ENTITY_ASSIGNEE_ATTRIBUTE } = require('../../constants/workflows');
|
|
6
|
+
const { getService } = require('../../utils');
|
|
7
|
+
|
|
8
|
+
module.exports = ({ strapi }) => {
|
|
9
|
+
const metrics = getService('review-workflows-metrics', { strapi });
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
async findEntityAssigneeId(id, model) {
|
|
13
|
+
const entity = await strapi.entityService.findOne(model, id, {
|
|
14
|
+
populate: [ENTITY_ASSIGNEE_ATTRIBUTE],
|
|
15
|
+
fields: [],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return entity?.[ENTITY_ASSIGNEE_ATTRIBUTE]?.id ?? null;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Update the assignee of an entity
|
|
23
|
+
*/
|
|
24
|
+
async updateEntityAssignee(id, model, assigneeId) {
|
|
25
|
+
if (isNil(assigneeId)) {
|
|
26
|
+
return this.deleteEntityAssignee(id, model);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const userExists = await getService('user', { strapi }).exists({ id: assigneeId });
|
|
30
|
+
|
|
31
|
+
if (!userExists) {
|
|
32
|
+
throw new ApplicationError(`Selected user does not exist`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
metrics.sendDidEditAssignee(await this.findEntityAssigneeId(id, model), assigneeId);
|
|
36
|
+
|
|
37
|
+
return strapi.entityService.update(model, id, {
|
|
38
|
+
data: { [ENTITY_ASSIGNEE_ATTRIBUTE]: assigneeId },
|
|
39
|
+
populate: [ENTITY_ASSIGNEE_ATTRIBUTE],
|
|
40
|
+
fields: [],
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async deleteEntityAssignee(id, model) {
|
|
45
|
+
metrics.sendDidEditAssignee(await this.findEntityAssigneeId(id, model), null);
|
|
46
|
+
|
|
47
|
+
return strapi.entityService.update(model, id, {
|
|
48
|
+
data: { [ENTITY_ASSIGNEE_ATTRIBUTE]: null },
|
|
49
|
+
populate: [ENTITY_ASSIGNEE_ATTRIBUTE],
|
|
50
|
+
fields: [],
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -24,6 +24,10 @@ const sendDidEditWorkflow = async () => {
|
|
|
24
24
|
strapi.telemetry.send('didEditWorkflow', {});
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
const sendDidEditAssignee = async (fromId, toId) => {
|
|
28
|
+
strapi.telemetry.send('didEditAssignee', { from: fromId, to: toId });
|
|
29
|
+
};
|
|
30
|
+
|
|
27
31
|
const sendDidSendReviewWorkflowPropertiesOnceAWeek = async (
|
|
28
32
|
numberOfActiveWorkflows,
|
|
29
33
|
avgStagesCount,
|
|
@@ -48,4 +52,5 @@ module.exports = {
|
|
|
48
52
|
sendDidCreateWorkflow,
|
|
49
53
|
sendDidEditWorkflow,
|
|
50
54
|
sendDidSendReviewWorkflowPropertiesOnceAWeek,
|
|
55
|
+
sendDidEditAssignee,
|
|
51
56
|
};
|
|
@@ -8,6 +8,8 @@ const defaultStages = require('../../constants/default-stages.json');
|
|
|
8
8
|
const defaultWorkflow = require('../../constants/default-workflow.json');
|
|
9
9
|
const {
|
|
10
10
|
ENTITY_STAGE_ATTRIBUTE,
|
|
11
|
+
ENTITY_ASSIGNEE_ATTRIBUTE,
|
|
12
|
+
STAGE_MODEL_UID,
|
|
11
13
|
MAX_WORKFLOWS,
|
|
12
14
|
MAX_STAGES_PER_WORKFLOW,
|
|
13
15
|
} = require('../../constants/workflows');
|
|
@@ -52,19 +54,26 @@ function extendReviewWorkflowContentTypes({ strapi }) {
|
|
|
52
54
|
);
|
|
53
55
|
return contentType;
|
|
54
56
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
|
|
58
|
+
const setRelation = (path, target) =>
|
|
59
|
+
set(path, {
|
|
60
|
+
writable: true,
|
|
61
|
+
private: false,
|
|
62
|
+
configurable: false,
|
|
63
|
+
visible: false,
|
|
64
|
+
useJoinTable: true, // We want a join table to persist data when downgrading to CE
|
|
65
|
+
type: 'relation',
|
|
66
|
+
relation: 'oneToOne',
|
|
67
|
+
target,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const setReviewWorkflowAttributes = pipe([
|
|
71
|
+
setRelation(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, STAGE_MODEL_UID),
|
|
72
|
+
setRelation(`attributes.${ENTITY_ASSIGNEE_ATTRIBUTE}`, 'admin::user'),
|
|
73
|
+
]);
|
|
65
74
|
|
|
66
75
|
const extendContentTypeIfCompatible = cond([
|
|
67
|
-
[assertContentTypeCompatibility,
|
|
76
|
+
[assertContentTypeCompatibility, setReviewWorkflowAttributes],
|
|
68
77
|
[stubTrue, incompatibleContentTypeAlert],
|
|
69
78
|
]);
|
|
70
79
|
strapi.container.get('content-types').extend(contentTypeUID, extendContentTypeIfCompatible);
|
|
@@ -66,8 +66,16 @@ const validateWorkflowUpdateSchema = yup.object().shape({
|
|
|
66
66
|
contentTypes: validateContentTypes,
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
const validateUpdateAssigneeOnEntity = yup
|
|
70
|
+
.object()
|
|
71
|
+
.shape({
|
|
72
|
+
id: yup.number().integer().min(1).nullable(),
|
|
73
|
+
})
|
|
74
|
+
.required();
|
|
75
|
+
|
|
69
76
|
module.exports = {
|
|
70
77
|
validateWorkflowCreate: validateYupSchema(validateWorkflowCreateSchema),
|
|
71
78
|
validateUpdateStageOnEntity: validateYupSchema(validateUpdateStageOnEntity),
|
|
79
|
+
validateUpdateAssigneeOnEntity: validateYupSchema(validateUpdateAssigneeOnEntity),
|
|
72
80
|
validateWorkflowUpdate: validateYupSchema(validateWorkflowUpdateSchema),
|
|
73
81
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/admin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0-beta.0",
|
|
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.
|
|
46
|
-
"@strapi/design-system": "1.
|
|
47
|
-
"@strapi/helper-plugin": "4.
|
|
48
|
-
"@strapi/icons": "1.
|
|
49
|
-
"@strapi/permissions": "4.
|
|
50
|
-
"@strapi/provider-audit-logs-local": "4.
|
|
51
|
-
"@strapi/typescript-utils": "4.
|
|
52
|
-
"@strapi/utils": "4.
|
|
45
|
+
"@strapi/data-transfer": "4.13.0-beta.0",
|
|
46
|
+
"@strapi/design-system": "1.9.0",
|
|
47
|
+
"@strapi/helper-plugin": "4.13.0-beta.0",
|
|
48
|
+
"@strapi/icons": "1.9.0",
|
|
49
|
+
"@strapi/permissions": "4.13.0-beta.0",
|
|
50
|
+
"@strapi/provider-audit-logs-local": "4.13.0-beta.0",
|
|
51
|
+
"@strapi/typescript-utils": "4.13.0-beta.0",
|
|
52
|
+
"@strapi/utils": "4.13.0-beta.0",
|
|
53
53
|
"axios": "1.4.0",
|
|
54
54
|
"bcryptjs": "2.4.3",
|
|
55
55
|
"browserslist": "^4.17.3",
|
|
@@ -154,5 +154,5 @@
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
},
|
|
157
|
-
"gitHead": "
|
|
157
|
+
"gitHead": "f1b8431a6a0b7f9bd9a8444adb56217bba91ec07"
|
|
158
158
|
}
|