@strapi/admin 4.13.3 → 4.14.0-alpha.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/hooks/useAdminRoles/index.js +17 -7
- package/admin/src/hooks/useAdminUsers/useAdminUsers.js +16 -7
- package/admin/src/hooks/useContentTypes/useContentTypes.js +18 -7
- package/build/1227.ec336799.chunk.js +1 -0
- package/build/{3483.19381b40.chunk.js → 3483.f6b2439f.chunk.js} +1 -1
- package/build/4174.4587c7f6.chunk.js +1 -0
- package/build/6266.53be9ea3.chunk.js +124 -0
- package/build/7897.eac204a4.chunk.js +6 -0
- package/build/{Admin-authenticatedApp.796792a8.chunk.js → Admin-authenticatedApp.d200a4ee.chunk.js} +1 -1
- package/build/{admin-app.2a8615ab.chunk.js → admin-app.582877a3.chunk.js} +11 -11
- package/build/admin-edit-roles-page.0aa65505.chunk.js +267 -0
- package/build/admin-edit-users.9215912a.chunk.js +10 -0
- package/build/admin-roles-list.824a50de.chunk.js +22 -0
- package/build/admin-users.f6b3c643.chunk.js +11 -0
- package/build/audit-logs-settings-page.be2cb4dd.chunk.js +1 -0
- package/build/{content-manager.f448efdf.chunk.js → content-manager.06a2f7ec.chunk.js} +39 -39
- package/build/index.html +1 -1
- package/build/review-workflows-settings-create-view.604cffa0.chunk.js +1 -0
- package/build/review-workflows-settings-edit-view.73c57f07.chunk.js +1 -0
- package/build/review-workflows-settings-list-view.7e300ecb.chunk.js +56 -0
- package/build/{runtime~main.d2b8d4a1.js → runtime~main.9de029f4.js} +2 -2
- package/build/sso-settings-page.94373f78.chunk.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +65 -53
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +50 -5
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +227 -19
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +8 -23
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +17 -7
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowsStages.js +36 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +68 -19
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +105 -35
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +68 -27
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/selectors.js +45 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/validateWorkflow.js +20 -0
- package/ee/server/config/admin-actions.js +6 -0
- package/ee/server/constants/workflows.js +13 -0
- package/ee/server/content-types/workflow-stage/index.js +6 -0
- package/ee/server/controllers/workflows/index.js +41 -16
- package/ee/server/controllers/workflows/stages/index.js +93 -6
- package/ee/server/routes/review-workflows.js +10 -9
- package/ee/server/services/index.js +1 -0
- package/ee/server/services/review-workflows/stage-permissions.js +60 -0
- package/ee/server/services/review-workflows/stages.js +83 -12
- package/ee/server/services/review-workflows/workflows/index.js +20 -7
- package/ee/server/validation/review-workflows.js +11 -0
- package/package.json +8 -8
- package/scripts/build.js +2 -3
- package/scripts/create-dev-plugins-file.js +2 -3
- package/server/content-types/Permission.js +6 -0
- package/server/domain/permission/index.js +11 -2
- package/server/services/role.js +12 -4
- package/server/validation/action-provider.js +1 -1
- package/server/validation/common-validators.js +92 -100
- package/server/validation/permission.js +0 -3
- package/utils/create-cache-dir.js +5 -102
- package/utils/plugins.js +217 -0
- package/webpack.config.js +2 -2
- package/build/1227.9f37e1dc.chunk.js +0 -1
- package/build/2237.b832ae6e.chunk.js +0 -114
- package/build/4174.f1f39e40.chunk.js +0 -1
- package/build/4724.aea5c8c1.chunk.js +0 -6
- package/build/admin-edit-roles-page.38a6c863.chunk.js +0 -267
- package/build/admin-edit-users.545fc882.chunk.js +0 -10
- package/build/admin-roles-list.1e2e814d.chunk.js +0 -22
- package/build/admin-users.b8ea5677.chunk.js +0 -11
- package/build/audit-logs-settings-page.96f9d608.chunk.js +0 -1
- package/build/review-workflows-settings-create-view.4a156a19.chunk.js +0 -1
- package/build/review-workflows-settings-edit-view.ce984d1f.chunk.js +0 -1
- package/build/review-workflows-settings-list-view.419b8deb.chunk.js +0 -56
- package/build/sso-settings-page.45153df5.chunk.js +0 -1
- package/utils/create-plugins-exclude-path.js +0 -20
- package/utils/get-plugins.js +0 -110
|
@@ -16,11 +16,19 @@ import { useMutation } from 'react-query';
|
|
|
16
16
|
import { useSelector, useDispatch } from 'react-redux';
|
|
17
17
|
import { useParams } from 'react-router-dom';
|
|
18
18
|
|
|
19
|
+
import { useAdminRoles } from '../../../../../../../../admin/src/hooks/useAdminRoles';
|
|
19
20
|
import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
|
|
20
21
|
import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
|
|
21
22
|
import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
|
|
22
23
|
import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
resetWorkflow,
|
|
26
|
+
setIsLoading,
|
|
27
|
+
setWorkflow,
|
|
28
|
+
setContentTypes,
|
|
29
|
+
setRoles,
|
|
30
|
+
setWorkflows,
|
|
31
|
+
} from '../../actions';
|
|
24
32
|
import * as Layout from '../../components/Layout';
|
|
25
33
|
import * as LimitsModal from '../../components/LimitsModal';
|
|
26
34
|
import { Stages } from '../../components/Stages';
|
|
@@ -31,7 +39,15 @@ import {
|
|
|
31
39
|
REDUX_NAMESPACE,
|
|
32
40
|
} from '../../constants';
|
|
33
41
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
|
34
|
-
import { reducer
|
|
42
|
+
import { reducer } from '../../reducer';
|
|
43
|
+
import {
|
|
44
|
+
selectIsWorkflowDirty,
|
|
45
|
+
selectCurrentWorkflow,
|
|
46
|
+
selectHasDeletedServerStages,
|
|
47
|
+
selectIsLoading,
|
|
48
|
+
selectRoles,
|
|
49
|
+
selectServerState,
|
|
50
|
+
} from '../../selectors';
|
|
35
51
|
import { validateWorkflow } from '../../utils/validateWorkflow';
|
|
36
52
|
|
|
37
53
|
export function ReviewWorkflowsEditView() {
|
|
@@ -42,29 +58,22 @@ export function ReviewWorkflowsEditView() {
|
|
|
42
58
|
const { put } = useFetchClient();
|
|
43
59
|
const { formatAPIError } = useAPIErrorHandler();
|
|
44
60
|
const toggleNotification = useNotification();
|
|
45
|
-
const {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const {
|
|
54
|
-
status,
|
|
55
|
-
clientState: {
|
|
56
|
-
currentWorkflow: {
|
|
57
|
-
data: currentWorkflow,
|
|
58
|
-
isDirty: currentWorkflowIsDirty,
|
|
59
|
-
hasDeletedServerStages,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
|
|
61
|
+
const { isLoading: isLoadingWorkflow, meta, workflows, refetch } = useReviewWorkflows();
|
|
62
|
+
const { collectionTypes, singleTypes, isLoading: isLoadingContentTypes } = useContentTypes();
|
|
63
|
+
const serverState = useSelector(selectServerState);
|
|
64
|
+
const currentWorkflowIsDirty = useSelector(selectIsWorkflowDirty);
|
|
65
|
+
const currentWorkflow = useSelector(selectCurrentWorkflow);
|
|
66
|
+
const hasDeletedServerStages = useSelector(selectHasDeletedServerStages);
|
|
67
|
+
const roles = useSelector(selectRoles);
|
|
68
|
+
const isLoading = useSelector(selectIsLoading);
|
|
63
69
|
const {
|
|
64
70
|
allowedActions: { canDelete, canUpdate },
|
|
65
71
|
} = useRBAC(permissions.settings['review-workflows']);
|
|
66
72
|
const [savePrompts, setSavePrompts] = React.useState({});
|
|
67
73
|
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
|
|
74
|
+
const { isLoading: isLoadingRoles, roles: serverRoles } = useAdminRoles(undefined, {
|
|
75
|
+
retry: false,
|
|
76
|
+
});
|
|
68
77
|
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
|
69
78
|
const [initialErrors, setInitialErrors] = React.useState(null);
|
|
70
79
|
|
|
@@ -73,7 +82,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
73
82
|
.filter((workflow) => workflow.id !== parseInt(workflowId, 10))
|
|
74
83
|
.flatMap((workflow) => workflow.contentTypes);
|
|
75
84
|
|
|
76
|
-
const { mutateAsync, isLoading } = useMutation(
|
|
85
|
+
const { mutateAsync, isLoading: isLoadingMutation } = useMutation(
|
|
77
86
|
async ({ workflow }) => {
|
|
78
87
|
const {
|
|
79
88
|
data: { data },
|
|
@@ -98,7 +107,37 @@ export function ReviewWorkflowsEditView() {
|
|
|
98
107
|
setInitialErrors(null);
|
|
99
108
|
|
|
100
109
|
try {
|
|
101
|
-
const res = await mutateAsync({
|
|
110
|
+
const res = await mutateAsync({
|
|
111
|
+
workflow: {
|
|
112
|
+
...workflow,
|
|
113
|
+
|
|
114
|
+
// compare permissions of stages and only submit them if at least one has
|
|
115
|
+
// changed; this enables partial updates e.g. for users who don't have
|
|
116
|
+
// permissions to see roles
|
|
117
|
+
stages: workflow.stages.map((stage) => {
|
|
118
|
+
let hasUpdatedPermissions = true;
|
|
119
|
+
const serverStage = serverState.workflow.stages.find(
|
|
120
|
+
(serverStage) => serverStage.id === stage?.id
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (serverStage) {
|
|
124
|
+
hasUpdatedPermissions =
|
|
125
|
+
serverStage.permissions?.length !== stage.permission?.length ||
|
|
126
|
+
!serverStage.permissions.every(
|
|
127
|
+
(serverPermission) =>
|
|
128
|
+
!!stage.permissions.find(
|
|
129
|
+
(permission) => permission.role === serverPermission.role
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
...stage,
|
|
136
|
+
permissions: hasUpdatedPermissions ? stage.permissions : undefined,
|
|
137
|
+
};
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
102
141
|
|
|
103
142
|
return res;
|
|
104
143
|
} catch (error) {
|
|
@@ -194,14 +233,37 @@ export function ReviewWorkflowsEditView() {
|
|
|
194
233
|
const limits = getFeature('review-workflows');
|
|
195
234
|
|
|
196
235
|
React.useEffect(() => {
|
|
197
|
-
|
|
236
|
+
if (!isLoadingWorkflow) {
|
|
237
|
+
dispatch(setWorkflow({ workflow }));
|
|
238
|
+
dispatch(setWorkflows({ workflows }));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!isLoadingContentTypes) {
|
|
242
|
+
dispatch(setContentTypes({ collectionTypes, singleTypes }));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!isLoadingRoles) {
|
|
246
|
+
dispatch(setRoles(serverRoles));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
dispatch(setIsLoading(isLoadingWorkflow || isLoadingContentTypes || isLoadingRoles));
|
|
198
250
|
|
|
199
251
|
// reset the state to the initial state to avoid flashes if a user
|
|
200
252
|
// navigates from an edit-view to a create-view
|
|
201
253
|
return () => {
|
|
202
254
|
dispatch(resetWorkflow());
|
|
203
255
|
};
|
|
204
|
-
}, [
|
|
256
|
+
}, [
|
|
257
|
+
collectionTypes,
|
|
258
|
+
dispatch,
|
|
259
|
+
isLoadingContentTypes,
|
|
260
|
+
isLoadingWorkflow,
|
|
261
|
+
isLoadingRoles,
|
|
262
|
+
serverRoles,
|
|
263
|
+
singleTypes,
|
|
264
|
+
workflow,
|
|
265
|
+
workflows,
|
|
266
|
+
]);
|
|
205
267
|
|
|
206
268
|
/**
|
|
207
269
|
* If the current license has a limit:
|
|
@@ -217,7 +279,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
217
279
|
*/
|
|
218
280
|
|
|
219
281
|
React.useEffect(() => {
|
|
220
|
-
if (!
|
|
282
|
+
if (!isLoadingWorkflow && !isLicenseLoading) {
|
|
221
283
|
if (
|
|
222
284
|
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
223
285
|
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
@@ -234,12 +296,25 @@ export function ReviewWorkflowsEditView() {
|
|
|
234
296
|
}, [
|
|
235
297
|
currentWorkflow.stages.length,
|
|
236
298
|
isLicenseLoading,
|
|
237
|
-
|
|
299
|
+
isLoadingWorkflow,
|
|
238
300
|
limits,
|
|
239
301
|
meta?.workflowCount,
|
|
240
302
|
meta.workflowsTotal,
|
|
241
303
|
]);
|
|
242
304
|
|
|
305
|
+
React.useEffect(() => {
|
|
306
|
+
if (!isLoading && roles.length === 0) {
|
|
307
|
+
toggleNotification({
|
|
308
|
+
blockTransition: true,
|
|
309
|
+
type: 'warning',
|
|
310
|
+
message: formatMessage({
|
|
311
|
+
id: 'Settings.review-workflows.stage.permissions.noPermissions.description',
|
|
312
|
+
defaultMessage: 'You don’t have the permission to see roles',
|
|
313
|
+
}),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}, [formatMessage, isLoading, roles, toggleNotification]);
|
|
317
|
+
|
|
243
318
|
// TODO: redirect back to list-view if workflow is not found?
|
|
244
319
|
|
|
245
320
|
return (
|
|
@@ -259,7 +334,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
259
334
|
disabled={!currentWorkflowIsDirty}
|
|
260
335
|
// if the confirm dialog is open the loading state is on
|
|
261
336
|
// the confirm button already
|
|
262
|
-
loading={!Object.keys(savePrompts).length > 0 &&
|
|
337
|
+
loading={!Object.keys(savePrompts).length > 0 && isLoadingMutation}
|
|
263
338
|
>
|
|
264
339
|
{formatMessage({
|
|
265
340
|
id: 'global.save',
|
|
@@ -269,7 +344,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
269
344
|
)
|
|
270
345
|
}
|
|
271
346
|
subtitle={
|
|
272
|
-
|
|
347
|
+
!isLoading &&
|
|
273
348
|
formatMessage(
|
|
274
349
|
{
|
|
275
350
|
id: 'Settings.review-workflows.page.subtitle',
|
|
@@ -282,7 +357,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
282
357
|
/>
|
|
283
358
|
|
|
284
359
|
<Layout.Root>
|
|
285
|
-
{
|
|
360
|
+
{isLoading ? (
|
|
286
361
|
<Flex justifyContent="center">
|
|
287
362
|
<Loader>
|
|
288
363
|
{formatMessage({
|
|
@@ -293,12 +368,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
293
368
|
</Flex>
|
|
294
369
|
) : (
|
|
295
370
|
<Flex alignItems="stretch" direction="column" gap={7}>
|
|
296
|
-
<WorkflowAttributes
|
|
297
|
-
canUpdate={canUpdate}
|
|
298
|
-
contentTypes={{ collectionTypes, singleTypes }}
|
|
299
|
-
currentWorkflow={currentWorkflow}
|
|
300
|
-
workflows={workflows}
|
|
301
|
-
/>
|
|
371
|
+
<WorkflowAttributes canUpdate={canUpdate} />
|
|
302
372
|
<Stages
|
|
303
373
|
canDelete={canDelete}
|
|
304
374
|
canUpdate={canUpdate}
|
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import isEqual from 'lodash/isEqual';
|
|
1
|
+
import { produce } from 'immer';
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
ACTION_ADD_STAGE,
|
|
5
|
+
ACTION_CLONE_STAGE,
|
|
6
6
|
ACTION_DELETE_STAGE,
|
|
7
7
|
ACTION_RESET_WORKFLOW,
|
|
8
|
+
ACTION_SET_CONTENT_TYPES,
|
|
9
|
+
ACTION_SET_IS_LOADING,
|
|
10
|
+
ACTION_SET_ROLES,
|
|
8
11
|
ACTION_SET_WORKFLOW,
|
|
12
|
+
ACTION_SET_WORKFLOWS,
|
|
9
13
|
ACTION_UPDATE_STAGE,
|
|
14
|
+
ACTION_UPDATE_STAGES,
|
|
10
15
|
ACTION_UPDATE_STAGE_POSITION,
|
|
11
16
|
ACTION_UPDATE_WORKFLOW,
|
|
12
17
|
STAGE_COLOR_DEFAULT,
|
|
13
18
|
} from '../constants';
|
|
14
19
|
|
|
15
20
|
export const initialState = {
|
|
16
|
-
status: 'loading',
|
|
17
21
|
serverState: {
|
|
22
|
+
contentTypes: {
|
|
23
|
+
collectionTypes: [],
|
|
24
|
+
singleTypes: [],
|
|
25
|
+
},
|
|
26
|
+
roles: [],
|
|
18
27
|
workflow: null,
|
|
28
|
+
workflows: [],
|
|
19
29
|
},
|
|
20
30
|
clientState: {
|
|
21
31
|
currentWorkflow: {
|
|
@@ -23,10 +33,10 @@ export const initialState = {
|
|
|
23
33
|
name: '',
|
|
24
34
|
contentTypes: [],
|
|
25
35
|
stages: [],
|
|
36
|
+
permissions: undefined,
|
|
26
37
|
},
|
|
27
|
-
isDirty: false,
|
|
28
|
-
hasDeletedServerStages: false,
|
|
29
38
|
},
|
|
39
|
+
isLoading: true,
|
|
30
40
|
},
|
|
31
41
|
};
|
|
32
42
|
|
|
@@ -35,10 +45,23 @@ export function reducer(state = initialState, action) {
|
|
|
35
45
|
const { payload } = action;
|
|
36
46
|
|
|
37
47
|
switch (action.type) {
|
|
38
|
-
case
|
|
39
|
-
|
|
48
|
+
case ACTION_SET_CONTENT_TYPES: {
|
|
49
|
+
draft.serverState.contentTypes = payload;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
case ACTION_SET_IS_LOADING: {
|
|
54
|
+
draft.clientState.isLoading = payload;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case ACTION_SET_ROLES: {
|
|
59
|
+
draft.serverState.roles = payload;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case ACTION_SET_WORKFLOW: {
|
|
64
|
+
const workflow = payload;
|
|
42
65
|
|
|
43
66
|
if (workflow) {
|
|
44
67
|
draft.serverState.workflow = workflow;
|
|
@@ -47,18 +70,21 @@ export function reducer(state = initialState, action) {
|
|
|
47
70
|
stages: workflow.stages.map((stage) => ({
|
|
48
71
|
...stage,
|
|
49
72
|
// A safety net in case a stage does not have a color assigned;
|
|
50
|
-
// this
|
|
73
|
+
// this should not happen
|
|
51
74
|
color: stage?.color ?? STAGE_COLOR_DEFAULT,
|
|
52
75
|
})),
|
|
53
76
|
};
|
|
54
77
|
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
55
80
|
|
|
56
|
-
|
|
81
|
+
case ACTION_SET_WORKFLOWS: {
|
|
82
|
+
draft.serverState.workflows = payload;
|
|
57
83
|
break;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
86
|
case ACTION_RESET_WORKFLOW: {
|
|
61
|
-
draft.clientState
|
|
87
|
+
draft.clientState = initialState.clientState;
|
|
62
88
|
draft.serverState = initialState.serverState;
|
|
63
89
|
break;
|
|
64
90
|
}
|
|
@@ -71,12 +97,6 @@ export function reducer(state = initialState, action) {
|
|
|
71
97
|
(stage) => (stage?.id ?? stage.__temp_key__) !== stageId
|
|
72
98
|
);
|
|
73
99
|
|
|
74
|
-
if (!currentWorkflow.hasDeletedServerStages) {
|
|
75
|
-
draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
|
|
76
|
-
state.serverState.workflow?.stages ?? []
|
|
77
|
-
).find((stage) => stage.id === stageId);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
100
|
break;
|
|
81
101
|
}
|
|
82
102
|
|
|
@@ -100,6 +120,24 @@ export function reducer(state = initialState, action) {
|
|
|
100
120
|
break;
|
|
101
121
|
}
|
|
102
122
|
|
|
123
|
+
case ACTION_CLONE_STAGE: {
|
|
124
|
+
const { currentWorkflow } = state.clientState;
|
|
125
|
+
const { id } = payload;
|
|
126
|
+
|
|
127
|
+
const sourceStageIndex = currentWorkflow.data.stages.findIndex(
|
|
128
|
+
(stage) => (stage?.id ?? stage?.__temp_key__) === id
|
|
129
|
+
);
|
|
130
|
+
const sourceStage = currentWorkflow.data.stages[sourceStageIndex];
|
|
131
|
+
|
|
132
|
+
draft.clientState.currentWorkflow.data.stages.splice(sourceStageIndex + 1, 0, {
|
|
133
|
+
...sourceStage,
|
|
134
|
+
id: undefined,
|
|
135
|
+
__temp_key__: getMaxTempKey(draft.clientState.currentWorkflow.data.stages),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
103
141
|
case ACTION_UPDATE_STAGE: {
|
|
104
142
|
const { currentWorkflow } = state.clientState;
|
|
105
143
|
const { stageId, ...modified } = payload;
|
|
@@ -116,6 +154,19 @@ export function reducer(state = initialState, action) {
|
|
|
116
154
|
break;
|
|
117
155
|
}
|
|
118
156
|
|
|
157
|
+
case ACTION_UPDATE_STAGES: {
|
|
158
|
+
const { currentWorkflow } = state.clientState;
|
|
159
|
+
|
|
160
|
+
draft.clientState.currentWorkflow.data.stages = currentWorkflow.data.stages.map(
|
|
161
|
+
(stage) => ({
|
|
162
|
+
...stage,
|
|
163
|
+
...payload,
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
119
170
|
case ACTION_UPDATE_STAGE_POSITION: {
|
|
120
171
|
const {
|
|
121
172
|
currentWorkflow: {
|
|
@@ -149,16 +200,6 @@ export function reducer(state = initialState, action) {
|
|
|
149
200
|
default:
|
|
150
201
|
break;
|
|
151
202
|
}
|
|
152
|
-
|
|
153
|
-
if (state.clientState.currentWorkflow.data && draft.serverState.workflow) {
|
|
154
|
-
draft.clientState.currentWorkflow.isDirty = !isEqual(
|
|
155
|
-
current(draft.clientState.currentWorkflow).data,
|
|
156
|
-
draft.serverState.workflow
|
|
157
|
-
);
|
|
158
|
-
} else {
|
|
159
|
-
// if there is no workflow on the server, the workflow is awalys considered dirty
|
|
160
|
-
draft.clientState.currentWorkflow.isDirty = true;
|
|
161
|
-
}
|
|
162
203
|
});
|
|
163
204
|
}
|
|
164
205
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual';
|
|
2
|
+
import { createSelector } from 'reselect';
|
|
3
|
+
|
|
4
|
+
import { REDUX_NAMESPACE } from './constants';
|
|
5
|
+
import { initialState } from './reducer';
|
|
6
|
+
|
|
7
|
+
export const selectNamespace = (state) => state[REDUX_NAMESPACE] ?? initialState;
|
|
8
|
+
|
|
9
|
+
export const selectContentTypes = createSelector(
|
|
10
|
+
selectNamespace,
|
|
11
|
+
({ serverState: { contentTypes } }) => contentTypes
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const selectRoles = createSelector(selectNamespace, ({ serverState: { roles } }) => roles);
|
|
15
|
+
|
|
16
|
+
export const selectCurrentWorkflow = createSelector(
|
|
17
|
+
selectNamespace,
|
|
18
|
+
({ clientState: { currentWorkflow } }) => currentWorkflow.data
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const selectWorkflows = createSelector(
|
|
22
|
+
selectNamespace,
|
|
23
|
+
({ serverState: { workflows } }) => workflows
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const selectIsWorkflowDirty = createSelector(
|
|
27
|
+
selectNamespace,
|
|
28
|
+
({ serverState, clientState: { currentWorkflow } }) =>
|
|
29
|
+
!isEqual(serverState.workflow, currentWorkflow.data)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export const selectHasDeletedServerStages = createSelector(
|
|
33
|
+
selectNamespace,
|
|
34
|
+
({ serverState, clientState: { currentWorkflow } }) =>
|
|
35
|
+
!(serverState.workflow?.stages ?? []).every(
|
|
36
|
+
(stage) => !!currentWorkflow.data.stages.find(({ id }) => id === stage.id)
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const selectIsLoading = createSelector(
|
|
41
|
+
selectNamespace,
|
|
42
|
+
({ clientState: { isLoading } }) => isLoading
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const selectServerState = createSelector(selectNamespace, ({ serverState }) => serverState);
|
|
@@ -57,6 +57,26 @@ export async function validateWorkflow({ values, formatMessage }) {
|
|
|
57
57
|
})
|
|
58
58
|
)
|
|
59
59
|
.matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
|
|
60
|
+
|
|
61
|
+
permissions: yup
|
|
62
|
+
.array(
|
|
63
|
+
yup.object({
|
|
64
|
+
role: yup
|
|
65
|
+
.number()
|
|
66
|
+
.strict()
|
|
67
|
+
.typeError(
|
|
68
|
+
formatMessage({
|
|
69
|
+
id: 'Settings.review-workflows.validation.stage.permissions.role.number',
|
|
70
|
+
defaultMessage: 'Role must be of type number',
|
|
71
|
+
})
|
|
72
|
+
).required,
|
|
73
|
+
action: yup.string().required({
|
|
74
|
+
id: 'Settings.review-workflows.validation.stage.permissions.action.required',
|
|
75
|
+
defaultMessage: 'Action is a required argument',
|
|
76
|
+
}),
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
.strict(),
|
|
60
80
|
})
|
|
61
81
|
)
|
|
62
82
|
.min(1),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
WORKFLOW_MODEL_UID: 'admin::workflow',
|
|
6
6
|
STAGE_MODEL_UID: 'admin::workflow-stage',
|
|
7
|
+
STAGE_TRANSITION_UID: 'admin::review-workflows.stage.transition',
|
|
7
8
|
STAGE_DEFAULT_COLOR: '#4945FF',
|
|
8
9
|
ENTITY_STAGE_ATTRIBUTE: 'strapi_stage',
|
|
9
10
|
ENTITY_ASSIGNEE_ATTRIBUTE: 'strapi_assignee',
|
|
@@ -17,4 +18,16 @@ module.exports = {
|
|
|
17
18
|
'You’ve reached the limit of stages for this workflow in your plan. Try deleting some stages or contact Sales to enable more stages.',
|
|
18
19
|
DUPLICATED_STAGE_NAME: 'Stage names must be unique.',
|
|
19
20
|
},
|
|
21
|
+
WORKFLOW_POPULATE: {
|
|
22
|
+
stages: {
|
|
23
|
+
populate: {
|
|
24
|
+
permissions: {
|
|
25
|
+
fields: ['action', 'actionParameters'],
|
|
26
|
+
populate: {
|
|
27
|
+
role: { fields: ['id', 'name'] },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
20
33
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { update, map, property } = require('lodash/fp');
|
|
3
4
|
const { mapAsync } = require('@strapi/utils');
|
|
4
5
|
const { getService } = require('../../utils');
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ const {
|
|
|
7
8
|
validateWorkflowCreate,
|
|
8
9
|
validateWorkflowUpdate,
|
|
9
10
|
} = require('../../validation/review-workflows');
|
|
10
|
-
const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
|
|
11
|
+
const { WORKFLOW_MODEL_UID, WORKFLOW_POPULATE } = require('../../constants/workflows');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
*
|
|
@@ -22,6 +23,21 @@ function getWorkflowsPermissionChecker({ strapi }, userAbility) {
|
|
|
22
23
|
.create({ userAbility, model: WORKFLOW_MODEL_UID });
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Transforms workflow to an admin UI format.
|
|
28
|
+
* Some attributes (like permissions) are presented in a different format in the admin UI.
|
|
29
|
+
* @param {Workflow} workflow
|
|
30
|
+
*/
|
|
31
|
+
function formatWorkflowToAdmin(workflow) {
|
|
32
|
+
if (!workflow) return;
|
|
33
|
+
if (!workflow.stages) return workflow;
|
|
34
|
+
|
|
35
|
+
// Transform permissions roles to be the id string instead of an object
|
|
36
|
+
const transformPermissions = map(update('role', property('id')));
|
|
37
|
+
const transformStages = map(update('permissions', transformPermissions));
|
|
38
|
+
return update('stages', transformStages, workflow);
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
module.exports = {
|
|
26
42
|
/**
|
|
27
43
|
* Create a new workflow
|
|
@@ -38,10 +54,12 @@ module.exports = {
|
|
|
38
54
|
const workflowBody = await validateWorkflowCreate(body.data);
|
|
39
55
|
|
|
40
56
|
const workflowService = getService('workflows');
|
|
41
|
-
const createdWorkflow = await workflowService
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
const createdWorkflow = await workflowService
|
|
58
|
+
.create({
|
|
59
|
+
data: await sanitizeCreateInput(workflowBody),
|
|
60
|
+
populate,
|
|
61
|
+
})
|
|
62
|
+
.then(formatWorkflowToAdmin);
|
|
45
63
|
|
|
46
64
|
ctx.body = {
|
|
47
65
|
data: await sanitizeOutput(createdWorkflow),
|
|
@@ -61,22 +79,27 @@ module.exports = {
|
|
|
61
79
|
ctx.state.userAbility
|
|
62
80
|
);
|
|
63
81
|
const { populate } = await sanitizedQuery.update(query);
|
|
64
|
-
|
|
65
82
|
const workflowBody = await validateWorkflowUpdate(body.data);
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
// Find if workflow exists
|
|
85
|
+
const workflow = await workflowService.findById(id, { populate: WORKFLOW_POPULATE });
|
|
68
86
|
if (!workflow) {
|
|
69
87
|
return ctx.notFound();
|
|
70
88
|
}
|
|
71
|
-
const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
|
|
72
89
|
|
|
90
|
+
// Sanitize input data
|
|
91
|
+
const getPermittedFieldToUpdate = sanitizeUpdateInput(workflow);
|
|
73
92
|
const dataToUpdate = await getPermittedFieldToUpdate(workflowBody);
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
// Update workflow
|
|
95
|
+
const updatedWorkflow = await workflowService
|
|
96
|
+
.update(workflow, {
|
|
97
|
+
data: dataToUpdate,
|
|
98
|
+
populate,
|
|
99
|
+
})
|
|
100
|
+
.then(formatWorkflowToAdmin);
|
|
79
101
|
|
|
102
|
+
// Send sanitized response
|
|
80
103
|
ctx.body = {
|
|
81
104
|
data: await sanitizeOutput(updatedWorkflow),
|
|
82
105
|
};
|
|
@@ -96,12 +119,14 @@ module.exports = {
|
|
|
96
119
|
);
|
|
97
120
|
const { populate } = await sanitizedQuery.delete(query);
|
|
98
121
|
|
|
99
|
-
const workflow = await workflowService.findById(id, { populate:
|
|
122
|
+
const workflow = await workflowService.findById(id, { populate: WORKFLOW_POPULATE });
|
|
100
123
|
if (!workflow) {
|
|
101
124
|
return ctx.notFound("Workflow doesn't exist");
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
const deletedWorkflow = await workflowService
|
|
127
|
+
const deletedWorkflow = await workflowService
|
|
128
|
+
.delete(workflow, { populate })
|
|
129
|
+
.then(formatWorkflowToAdmin);
|
|
105
130
|
|
|
106
131
|
ctx.body = {
|
|
107
132
|
data: await sanitizeOutput(deletedWorkflow),
|
|
@@ -122,7 +147,7 @@ module.exports = {
|
|
|
122
147
|
const { populate, filters, sort } = await sanitizedQuery.read(query);
|
|
123
148
|
|
|
124
149
|
const [workflows, workflowCount] = await Promise.all([
|
|
125
|
-
workflowService.find({ populate, filters, sort }),
|
|
150
|
+
workflowService.find({ populate, filters, sort }).then(map(formatWorkflowToAdmin)),
|
|
126
151
|
workflowService.count(),
|
|
127
152
|
]);
|
|
128
153
|
|
|
@@ -151,7 +176,7 @@ module.exports = {
|
|
|
151
176
|
const workflowService = getService('workflows');
|
|
152
177
|
|
|
153
178
|
const [workflow, workflowCount] = await Promise.all([
|
|
154
|
-
workflowService.findById(id, { populate }),
|
|
179
|
+
workflowService.findById(id, { populate }).then(formatWorkflowToAdmin),
|
|
155
180
|
workflowService.count(),
|
|
156
181
|
]);
|
|
157
182
|
|