@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
|
@@ -5,29 +5,69 @@ import {
|
|
|
5
5
|
AccordionContent,
|
|
6
6
|
AccordionToggle,
|
|
7
7
|
Box,
|
|
8
|
+
Button,
|
|
8
9
|
Flex,
|
|
9
10
|
Grid,
|
|
10
11
|
GridItem,
|
|
11
12
|
IconButton,
|
|
13
|
+
MultiSelect,
|
|
14
|
+
MultiSelectGroup,
|
|
15
|
+
MultiSelectOption,
|
|
12
16
|
SingleSelect,
|
|
13
17
|
SingleSelectOption,
|
|
14
18
|
TextInput,
|
|
19
|
+
Typography,
|
|
15
20
|
VisuallyHidden,
|
|
16
21
|
} from '@strapi/design-system';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
22
|
+
import { Menu, MenuItem } from '@strapi/design-system/v2';
|
|
23
|
+
import {
|
|
24
|
+
ConfirmDialog,
|
|
25
|
+
useNotification,
|
|
26
|
+
NotAllowedInput,
|
|
27
|
+
useTracking,
|
|
28
|
+
} from '@strapi/helper-plugin';
|
|
29
|
+
import { Drag, More } from '@strapi/icons';
|
|
19
30
|
import { useField } from 'formik';
|
|
20
31
|
import PropTypes from 'prop-types';
|
|
21
32
|
import { getEmptyImage } from 'react-dnd-html5-backend';
|
|
22
33
|
import { useIntl } from 'react-intl';
|
|
23
|
-
import { useDispatch } from 'react-redux';
|
|
34
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
35
|
+
import styled from 'styled-components';
|
|
24
36
|
|
|
25
37
|
import { useDragAndDrop } from '../../../../../../../../../admin/src/content-manager/hooks';
|
|
26
38
|
import { composeRefs } from '../../../../../../../../../admin/src/content-manager/utils';
|
|
27
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
cloneStage,
|
|
41
|
+
deleteStage,
|
|
42
|
+
updateStage,
|
|
43
|
+
updateStagePosition,
|
|
44
|
+
updateStages,
|
|
45
|
+
} from '../../../actions';
|
|
28
46
|
import { DRAG_DROP_TYPES } from '../../../constants';
|
|
47
|
+
import { selectRoles } from '../../../selectors';
|
|
29
48
|
import { getAvailableStageColors, getStageColorByHex } from '../../../utils/colors';
|
|
30
49
|
|
|
50
|
+
const NestedOption = styled(MultiSelectOption)`
|
|
51
|
+
padding-left: ${({ theme }) => theme.spaces[7]};
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
// Grow the size of the permission Select
|
|
55
|
+
const PermissionWrapper = styled(Flex)`
|
|
56
|
+
> * {
|
|
57
|
+
flex-grow: 1;
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
// Make sure the apply to all stages button doesn't collapse, when the Select
|
|
62
|
+
// contains more tags than it can fit into one line
|
|
63
|
+
const ApplyToAllStages = styled(Button)`
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const DeleteMenuItem = styled(MenuItem)`
|
|
68
|
+
color: ${({ theme }) => theme.colors.danger600};
|
|
69
|
+
`;
|
|
70
|
+
|
|
31
71
|
const AVAILABLE_COLORS = getAvailableStageColors();
|
|
32
72
|
|
|
33
73
|
function StageDropPreview() {
|
|
@@ -137,13 +177,23 @@ export function Stage({
|
|
|
137
177
|
dispatch(updateStagePosition(oldIndex, newIndex));
|
|
138
178
|
};
|
|
139
179
|
|
|
180
|
+
const handleApplyPermissionsToAllStages = () => {
|
|
181
|
+
setIsApplyAllConfirmationOpen(true);
|
|
182
|
+
};
|
|
183
|
+
|
|
140
184
|
const [liveText, setLiveText] = React.useState(null);
|
|
141
185
|
const { formatMessage } = useIntl();
|
|
142
186
|
const { trackUsage } = useTracking();
|
|
143
187
|
const dispatch = useDispatch();
|
|
188
|
+
const toggleNotification = useNotification();
|
|
144
189
|
const [isOpen, setIsOpen] = React.useState(isOpenDefault);
|
|
190
|
+
const [isApplyAllConfirmationOpen, setIsApplyAllConfirmationOpen] = React.useState(false);
|
|
145
191
|
const [nameField, nameMeta, nameHelper] = useField(`stages.${index}.name`);
|
|
146
192
|
const [colorField, colorMeta, colorHelper] = useField(`stages.${index}.color`);
|
|
193
|
+
const [permissionsField, permissionsMeta, permissionsHelper] = useField(
|
|
194
|
+
`stages.${index}.permissions`
|
|
195
|
+
);
|
|
196
|
+
const roles = useSelector(selectRoles);
|
|
147
197
|
const [{ handlerId, isDragging, handleKeyDown }, stageRef, dropRef, dragRef, dragPreviewRef] =
|
|
148
198
|
useDragAndDrop(canReorder, {
|
|
149
199
|
index,
|
|
@@ -171,12 +221,17 @@ export function Stage({
|
|
|
171
221
|
color: hex,
|
|
172
222
|
}));
|
|
173
223
|
|
|
224
|
+
const { themeColorName } = getStageColorByHex(colorField.value) ?? {};
|
|
225
|
+
|
|
226
|
+
const filteredRoles = roles
|
|
227
|
+
// Super admins always have permissions to do everything and therefore
|
|
228
|
+
// there is no point for this role to show up in the role combobox
|
|
229
|
+
.filter((role) => role.code !== 'strapi-super-admin');
|
|
230
|
+
|
|
174
231
|
React.useEffect(() => {
|
|
175
232
|
dragPreviewRef(getEmptyImage(), { captureDraggingState: false });
|
|
176
233
|
}, [dragPreviewRef, index]);
|
|
177
234
|
|
|
178
|
-
const { themeColorName } = getStageColorByHex(colorField.value) ?? {};
|
|
179
|
-
|
|
180
235
|
return (
|
|
181
236
|
<Box ref={composedRef}>
|
|
182
237
|
{liveText && <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>}
|
|
@@ -196,7 +251,7 @@ export function Stage({
|
|
|
196
251
|
}}
|
|
197
252
|
expanded={isOpen}
|
|
198
253
|
shadow="tableShadow"
|
|
199
|
-
error={nameMeta.error ?? colorMeta?.error ?? false}
|
|
254
|
+
error={nameMeta.error ?? colorMeta?.error ?? permissionsMeta?.error ?? false}
|
|
200
255
|
hasErrorMessage={false}
|
|
201
256
|
>
|
|
202
257
|
<AccordionToggle
|
|
@@ -205,18 +260,41 @@ export function Stage({
|
|
|
205
260
|
action={
|
|
206
261
|
(canDelete || canUpdate) && (
|
|
207
262
|
<Flex>
|
|
208
|
-
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
<Menu.Root>
|
|
264
|
+
<Menu.Trigger size="S" endIcon={null} paddingLeft={2} paddingRight={2}>
|
|
265
|
+
<More aria-hidden focusable={false} />
|
|
266
|
+
<VisuallyHidden as="span">
|
|
267
|
+
{formatMessage({
|
|
268
|
+
id: '[tbdb].components.DynamicZone.more-actions',
|
|
269
|
+
defaultMessage: 'More actions',
|
|
270
|
+
})}
|
|
271
|
+
</VisuallyHidden>
|
|
272
|
+
</Menu.Trigger>
|
|
273
|
+
{/* z-index needs to be as big as the one defined for the wrapper in Stages, otherwise the menu
|
|
274
|
+
* disappears behind the accordion
|
|
275
|
+
*/}
|
|
276
|
+
<Menu.Content popoverPlacement="bottom-end" zIndex={2}>
|
|
277
|
+
<Menu.SubRoot>
|
|
278
|
+
{canUpdate && (
|
|
279
|
+
<MenuItem onClick={() => dispatch(cloneStage(id))}>
|
|
280
|
+
{formatMessage({
|
|
281
|
+
id: 'Settings.review-workflows.stage.delete',
|
|
282
|
+
defaultMessage: 'Duplicate stage',
|
|
283
|
+
})}
|
|
284
|
+
</MenuItem>
|
|
285
|
+
)}
|
|
286
|
+
|
|
287
|
+
{canDelete && (
|
|
288
|
+
<DeleteMenuItem onClick={() => dispatch(deleteStage(id))}>
|
|
289
|
+
{formatMessage({
|
|
290
|
+
id: 'Settings.review-workflows.stage.delete',
|
|
291
|
+
defaultMessage: 'Delete',
|
|
292
|
+
})}
|
|
293
|
+
</DeleteMenuItem>
|
|
294
|
+
)}
|
|
295
|
+
</Menu.SubRoot>
|
|
296
|
+
</Menu.Content>
|
|
297
|
+
</Menu.Root>
|
|
220
298
|
|
|
221
299
|
{canUpdate && (
|
|
222
300
|
<IconButton
|
|
@@ -315,10 +393,140 @@ export function Stage({
|
|
|
315
393
|
})}
|
|
316
394
|
</SingleSelect>
|
|
317
395
|
</GridItem>
|
|
396
|
+
|
|
397
|
+
<GridItem col={6}>
|
|
398
|
+
{filteredRoles.length === 0 ? (
|
|
399
|
+
<NotAllowedInput
|
|
400
|
+
description={{
|
|
401
|
+
id: 'Settings.review-workflows.stage.permissions.noPermissions.description',
|
|
402
|
+
defaultMessage: 'You don’t have the permission to see roles',
|
|
403
|
+
}}
|
|
404
|
+
intlLabel={{
|
|
405
|
+
id: 'Settings.review-workflows.stage.permissions.label',
|
|
406
|
+
defaultMessage: 'Roles that can change this stage',
|
|
407
|
+
}}
|
|
408
|
+
name={permissionsField.name}
|
|
409
|
+
/>
|
|
410
|
+
) : (
|
|
411
|
+
<Flex alignItems="flex-end" gap={3}>
|
|
412
|
+
<PermissionWrapper grow={1}>
|
|
413
|
+
<MultiSelect
|
|
414
|
+
{...permissionsField}
|
|
415
|
+
disabled={!canUpdate}
|
|
416
|
+
error={permissionsMeta.error ?? false}
|
|
417
|
+
id={permissionsField.name}
|
|
418
|
+
label={formatMessage({
|
|
419
|
+
id: 'Settings.review-workflows.stage.permissions.label',
|
|
420
|
+
defaultMessage: 'Roles that can change this stage',
|
|
421
|
+
})}
|
|
422
|
+
onChange={(values) => {
|
|
423
|
+
// Because the select components expects strings for values, but
|
|
424
|
+
// the yup schema validates we are sending full permission objects to the API,
|
|
425
|
+
// we must coerce the string value back to an object
|
|
426
|
+
const permissions = values.map((value) => ({
|
|
427
|
+
role: parseInt(value, 10),
|
|
428
|
+
action: 'admin::review-workflows.stage.transition',
|
|
429
|
+
}));
|
|
430
|
+
|
|
431
|
+
permissionsHelper.setValue(permissions);
|
|
432
|
+
dispatch(updateStage(id, { permissions }));
|
|
433
|
+
}}
|
|
434
|
+
placeholder={formatMessage({
|
|
435
|
+
id: 'Settings.review-workflows.stage.permissions.placeholder',
|
|
436
|
+
defaultMessage: 'Select a role',
|
|
437
|
+
})}
|
|
438
|
+
required
|
|
439
|
+
// The Select component expects strings for values
|
|
440
|
+
value={(permissionsField.value ?? []).map(
|
|
441
|
+
(permission) => `${permission.role}`
|
|
442
|
+
)}
|
|
443
|
+
withTags
|
|
444
|
+
>
|
|
445
|
+
{[
|
|
446
|
+
{
|
|
447
|
+
label: formatMessage({
|
|
448
|
+
id: 'Settings.review-workflows.stage.permissions.allRoles.label',
|
|
449
|
+
defaultMessage: 'All roles',
|
|
450
|
+
}),
|
|
451
|
+
|
|
452
|
+
children: filteredRoles.map((role) => ({
|
|
453
|
+
value: `${role.id}`,
|
|
454
|
+
label: role.name,
|
|
455
|
+
})),
|
|
456
|
+
},
|
|
457
|
+
].map((role) => {
|
|
458
|
+
if ('children' in role) {
|
|
459
|
+
return (
|
|
460
|
+
<MultiSelectGroup
|
|
461
|
+
key={role.label}
|
|
462
|
+
label={role.label}
|
|
463
|
+
values={role.children.map((child) => child.value)}
|
|
464
|
+
>
|
|
465
|
+
{role.children.map((role) => {
|
|
466
|
+
return (
|
|
467
|
+
<NestedOption key={role.value} value={role.value}>
|
|
468
|
+
{role.label}
|
|
469
|
+
</NestedOption>
|
|
470
|
+
);
|
|
471
|
+
})}
|
|
472
|
+
</MultiSelectGroup>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return (
|
|
477
|
+
<MultiSelectOption key={role.value} value={role.value}>
|
|
478
|
+
{role.label}
|
|
479
|
+
</MultiSelectOption>
|
|
480
|
+
);
|
|
481
|
+
})}
|
|
482
|
+
</MultiSelect>
|
|
483
|
+
</PermissionWrapper>
|
|
484
|
+
|
|
485
|
+
<ApplyToAllStages
|
|
486
|
+
disabled={!canUpdate}
|
|
487
|
+
size="L"
|
|
488
|
+
type="button"
|
|
489
|
+
variant="secondary"
|
|
490
|
+
onClick={() => handleApplyPermissionsToAllStages(permissionsField.value)}
|
|
491
|
+
>
|
|
492
|
+
{formatMessage({
|
|
493
|
+
id: 'Settings.review-workflows.stage.permissions.apply.label',
|
|
494
|
+
defaultMessage: 'Apply to all stages',
|
|
495
|
+
})}
|
|
496
|
+
</ApplyToAllStages>
|
|
497
|
+
</Flex>
|
|
498
|
+
)}
|
|
499
|
+
</GridItem>
|
|
318
500
|
</Grid>
|
|
319
501
|
</AccordionContent>
|
|
320
502
|
</Accordion>
|
|
321
503
|
)}
|
|
504
|
+
|
|
505
|
+
<ConfirmDialog.Root
|
|
506
|
+
isOpen={isApplyAllConfirmationOpen}
|
|
507
|
+
onToggleDialog={() => setIsApplyAllConfirmationOpen(false)}
|
|
508
|
+
onConfirm={() => {
|
|
509
|
+
dispatch(updateStages({ permissions: permissionsField.value }));
|
|
510
|
+
setIsApplyAllConfirmationOpen(false);
|
|
511
|
+
toggleNotification({
|
|
512
|
+
type: 'success',
|
|
513
|
+
message: formatMessage({
|
|
514
|
+
id: 'Settings.review-workflows.page.edit.confirm.stages.permissions.copy.success',
|
|
515
|
+
defaultMessage: 'Applied roles to all other stages of the workflow',
|
|
516
|
+
}),
|
|
517
|
+
});
|
|
518
|
+
}}
|
|
519
|
+
>
|
|
520
|
+
<ConfirmDialog.Body>
|
|
521
|
+
<Typography textAlign="center" variant="omega">
|
|
522
|
+
{formatMessage({
|
|
523
|
+
id: 'Settings.review-workflows.page.edit.confirm.stages.permissions.copy',
|
|
524
|
+
defaultMessage:
|
|
525
|
+
'Roles that can change that stage will be applied to all the other stages.',
|
|
526
|
+
})}
|
|
527
|
+
</Typography>
|
|
528
|
+
</ConfirmDialog.Body>
|
|
529
|
+
</ConfirmDialog.Root>
|
|
322
530
|
</Box>
|
|
323
531
|
);
|
|
324
532
|
}
|
|
@@ -13,10 +13,11 @@ import { useCollator } from '@strapi/helper-plugin';
|
|
|
13
13
|
import { useField } from 'formik';
|
|
14
14
|
import PropTypes from 'prop-types';
|
|
15
15
|
import { useIntl } from 'react-intl';
|
|
16
|
-
import { useDispatch } from 'react-redux';
|
|
16
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
17
17
|
import styled from 'styled-components';
|
|
18
18
|
|
|
19
19
|
import { updateWorkflow } from '../../actions';
|
|
20
|
+
import { selectContentTypes, selectCurrentWorkflow, selectWorkflows } from '../../selectors';
|
|
20
21
|
|
|
21
22
|
const NestedOption = styled(MultiSelectOption)`
|
|
22
23
|
padding-left: ${({ theme }) => theme.spaces[7]};
|
|
@@ -26,14 +27,12 @@ const ContentTypeTakeNotice = styled(Typography)`
|
|
|
26
27
|
font-style: italic;
|
|
27
28
|
`;
|
|
28
29
|
|
|
29
|
-
export function WorkflowAttributes({
|
|
30
|
-
canUpdate,
|
|
31
|
-
contentTypes: { collectionTypes, singleTypes },
|
|
32
|
-
currentWorkflow,
|
|
33
|
-
workflows,
|
|
34
|
-
}) {
|
|
30
|
+
export function WorkflowAttributes({ canUpdate }) {
|
|
35
31
|
const { formatMessage, locale } = useIntl();
|
|
36
32
|
const dispatch = useDispatch();
|
|
33
|
+
const { collectionTypes, singleTypes } = useSelector(selectContentTypes);
|
|
34
|
+
const currentWorkflow = useSelector(selectCurrentWorkflow);
|
|
35
|
+
const workflows = useSelector(selectWorkflows);
|
|
37
36
|
const [nameField, nameMeta, nameHelper] = useField('name');
|
|
38
37
|
const [contentTypesField, contentTypesMeta, contentTypesHelper] = useField('contentTypes');
|
|
39
38
|
const formatter = useCollator(locale, {
|
|
@@ -97,7 +96,7 @@ export function WorkflowAttributes({
|
|
|
97
96
|
id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
|
|
98
97
|
defaultMessage: 'Collection Types',
|
|
99
98
|
}),
|
|
100
|
-
children: collectionTypes
|
|
99
|
+
children: [...collectionTypes]
|
|
101
100
|
.sort((a, b) => formatter.compare(a.info.displayName, b.info.displayName))
|
|
102
101
|
.map((contentType) => ({
|
|
103
102
|
label: contentType.info.displayName,
|
|
@@ -114,7 +113,7 @@ export function WorkflowAttributes({
|
|
|
114
113
|
id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
|
|
115
114
|
defaultMessage: 'Single Types',
|
|
116
115
|
}),
|
|
117
|
-
children: singleTypes.map((contentType) => ({
|
|
116
|
+
children: [...singleTypes].map((contentType) => ({
|
|
118
117
|
label: contentType.info.displayName,
|
|
119
118
|
value: contentType.uid,
|
|
120
119
|
})),
|
|
@@ -178,24 +177,10 @@ export function WorkflowAttributes({
|
|
|
178
177
|
);
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
const ContentTypeType = PropTypes.shape({
|
|
182
|
-
uid: PropTypes.string.isRequired,
|
|
183
|
-
info: PropTypes.shape({
|
|
184
|
-
displayName: PropTypes.string.isRequired,
|
|
185
|
-
}).isRequired,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
180
|
WorkflowAttributes.defaultProps = {
|
|
189
181
|
canUpdate: true,
|
|
190
|
-
currentWorkflow: undefined,
|
|
191
182
|
};
|
|
192
183
|
|
|
193
184
|
WorkflowAttributes.propTypes = {
|
|
194
185
|
canUpdate: PropTypes.bool,
|
|
195
|
-
contentTypes: PropTypes.shape({
|
|
196
|
-
collectionTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
|
197
|
-
singleTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
|
|
198
|
-
}).isRequired,
|
|
199
|
-
currentWorkflow: PropTypes.object,
|
|
200
|
-
workflows: PropTypes.array.isRequired,
|
|
201
186
|
};
|
|
@@ -3,10 +3,16 @@ import { lightTheme } from '@strapi/design-system';
|
|
|
3
3
|
export const REDUX_NAMESPACE = 'settings_review-workflows';
|
|
4
4
|
|
|
5
5
|
export const ACTION_RESET_WORKFLOW = `Settings/Review_Workflows/RESET_WORKFLOW`;
|
|
6
|
+
export const ACTION_SET_CONTENT_TYPES = `Settings/Review_Workflows/SET_CONTENT_TYPES`;
|
|
7
|
+
export const ACTION_SET_IS_LOADING = `Settings/Review_Workflows/SET_IS_LOADING`;
|
|
8
|
+
export const ACTION_SET_ROLES = `Settings/Review_Workflows/SET_ROLES`;
|
|
6
9
|
export const ACTION_SET_WORKFLOW = `Settings/Review_Workflows/SET_WORKFLOW`;
|
|
10
|
+
export const ACTION_SET_WORKFLOWS = `Settings/Review_Workflows/SET_WORKFLOWS`;
|
|
7
11
|
export const ACTION_DELETE_STAGE = `Settings/Review_Workflows/WORKFLOW_DELETE_STAGE`;
|
|
8
12
|
export const ACTION_ADD_STAGE = `Settings/Review_Workflows/WORKFLOW_ADD_STAGE`;
|
|
13
|
+
export const ACTION_CLONE_STAGE = `Settings/Review_Workflows/WORKFLOW_CLONE_STAGE`;
|
|
9
14
|
export const ACTION_UPDATE_STAGE = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE`;
|
|
15
|
+
export const ACTION_UPDATE_STAGES = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGES`;
|
|
10
16
|
export const ACTION_UPDATE_STAGE_POSITION = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE_POSITION`;
|
|
11
17
|
export const ACTION_UPDATE_WORKFLOW = `Settings/Review_Workflows/WORKFLOW_UPDATE`;
|
|
12
18
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
1
3
|
import { useFetchClient } from '@strapi/helper-plugin';
|
|
2
4
|
import { useQuery } from 'react-query';
|
|
3
5
|
|
|
@@ -20,18 +22,26 @@ export function useReviewWorkflows(params = {}) {
|
|
|
20
22
|
}
|
|
21
23
|
);
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
// the return value needs to be memoized, because intantiating
|
|
26
|
+
// an empty array as default value would lead to an unstable return
|
|
27
|
+
// value, which later on triggers infinite loops if used in the
|
|
28
|
+
// dependency arrays of other hooks
|
|
29
|
+
|
|
30
|
+
const workflows = React.useMemo(() => {
|
|
31
|
+
if (id && data?.data) {
|
|
32
|
+
return [data.data];
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(data?.data)) {
|
|
35
|
+
return data.data;
|
|
36
|
+
}
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} else if (Array.isArray(data?.data)) {
|
|
28
|
-
workflows = data.data;
|
|
29
|
-
}
|
|
38
|
+
return [];
|
|
39
|
+
}, [data?.data, id]);
|
|
30
40
|
|
|
31
41
|
return {
|
|
32
42
|
// meta contains e.g. the total of all workflows. we can not use
|
|
33
43
|
// the pagination object here, because the list is not paginated.
|
|
34
|
-
meta: data?.meta ?? {},
|
|
44
|
+
meta: React.useMemo(() => data?.meta ?? {}, [data?.meta]),
|
|
35
45
|
workflows,
|
|
36
46
|
isLoading,
|
|
37
47
|
status,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useFetchClient } from '@strapi/helper-plugin';
|
|
4
|
+
import { useQuery } from 'react-query';
|
|
5
|
+
|
|
6
|
+
export function useReviewWorkflowsStages({ id, layout } = {}, queryOptions = {}) {
|
|
7
|
+
const { kind, uid } = layout;
|
|
8
|
+
const slug = kind === 'collectionType' ? 'collection-types' : 'single-types';
|
|
9
|
+
|
|
10
|
+
const { get } = useFetchClient();
|
|
11
|
+
|
|
12
|
+
const { data, isLoading, refetch } = useQuery(
|
|
13
|
+
['content-manager', slug, layout.uid, id, 'stages'],
|
|
14
|
+
async () => {
|
|
15
|
+
const { data } = await get(`/admin/content-manager/${slug}/${uid}/${id}/stages`);
|
|
16
|
+
|
|
17
|
+
return data;
|
|
18
|
+
},
|
|
19
|
+
queryOptions
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// these return values need to be memoized, because the default value
|
|
23
|
+
// would lead to infinite rendering loops when used in a dependency array
|
|
24
|
+
// on an effect
|
|
25
|
+
const meta = React.useMemo(() => data?.meta ?? {}, [data?.meta]);
|
|
26
|
+
const stages = React.useMemo(() => data?.data ?? [], [data?.data]);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
// meta contains e.g. the total of all workflows. we can not use
|
|
30
|
+
// the pagination object here, because the list is not paginated.
|
|
31
|
+
meta,
|
|
32
|
+
stages,
|
|
33
|
+
isLoading,
|
|
34
|
+
refetch,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -15,10 +15,18 @@ import { useMutation } from 'react-query';
|
|
|
15
15
|
import { useDispatch, useSelector } from 'react-redux';
|
|
16
16
|
import { useHistory } from 'react-router-dom';
|
|
17
17
|
|
|
18
|
+
import { useAdminRoles } from '../../../../../../../../admin/src/hooks/useAdminRoles';
|
|
18
19
|
import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
|
|
19
20
|
import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
|
|
20
21
|
import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
addStage,
|
|
24
|
+
resetWorkflow,
|
|
25
|
+
setContentTypes,
|
|
26
|
+
setIsLoading,
|
|
27
|
+
setRoles,
|
|
28
|
+
setWorkflows,
|
|
29
|
+
} from '../../actions';
|
|
22
30
|
import * as Layout from '../../components/Layout';
|
|
23
31
|
import * as LimitsModal from '../../components/LimitsModal';
|
|
24
32
|
import { Stages } from '../../components/Stages';
|
|
@@ -29,7 +37,13 @@ import {
|
|
|
29
37
|
REDUX_NAMESPACE,
|
|
30
38
|
} from '../../constants';
|
|
31
39
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
|
32
|
-
import { reducer
|
|
40
|
+
import { reducer } from '../../reducer';
|
|
41
|
+
import {
|
|
42
|
+
selectIsLoading,
|
|
43
|
+
selectIsWorkflowDirty,
|
|
44
|
+
selectCurrentWorkflow,
|
|
45
|
+
selectRoles,
|
|
46
|
+
} from '../../selectors';
|
|
33
47
|
import { validateWorkflow } from '../../utils/validateWorkflow';
|
|
34
48
|
|
|
35
49
|
export function ReviewWorkflowsCreateView() {
|
|
@@ -39,13 +53,15 @@ export function ReviewWorkflowsCreateView() {
|
|
|
39
53
|
const { formatAPIError } = useAPIErrorHandler();
|
|
40
54
|
const dispatch = useDispatch();
|
|
41
55
|
const toggleNotification = useNotification();
|
|
42
|
-
const { collectionTypes, singleTypes, isLoading:
|
|
43
|
-
const { isLoading:
|
|
44
|
-
const {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
const { collectionTypes, singleTypes, isLoading: isLoadingContentTypes } = useContentTypes();
|
|
57
|
+
const { isLoading: isLoadingWorkflow, meta, workflows } = useReviewWorkflows();
|
|
58
|
+
const { isLoading: isLoadingRoles, roles: serverRoles } = useAdminRoles(undefined, {
|
|
59
|
+
retry: false,
|
|
60
|
+
});
|
|
61
|
+
const isLoading = useSelector(selectIsLoading);
|
|
62
|
+
const currentWorkflowIsDirty = useSelector(selectIsWorkflowDirty);
|
|
63
|
+
const currentWorkflow = useSelector(selectCurrentWorkflow);
|
|
64
|
+
const roles = useSelector(selectRoles);
|
|
49
65
|
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
|
50
66
|
const { isLoading: isLicenseLoading, getFeature } = useLicenseLimits();
|
|
51
67
|
const [initialErrors, setInitialErrors] = React.useState(null);
|
|
@@ -54,7 +70,7 @@ export function ReviewWorkflowsCreateView() {
|
|
|
54
70
|
const limits = getFeature('review-workflows');
|
|
55
71
|
const contentTypesFromOtherWorkflows = workflows.flatMap((workflow) => workflow.contentTypes);
|
|
56
72
|
|
|
57
|
-
const { mutateAsync, isLoading } = useMutation(
|
|
73
|
+
const { mutateAsync, isLoading: isLoadingMutation } = useMutation(
|
|
58
74
|
async ({ workflow }) => {
|
|
59
75
|
const {
|
|
60
76
|
data: { data },
|
|
@@ -167,13 +183,36 @@ export function ReviewWorkflowsCreateView() {
|
|
|
167
183
|
React.useEffect(() => {
|
|
168
184
|
dispatch(resetWorkflow());
|
|
169
185
|
|
|
186
|
+
if (!isLoadingWorkflow) {
|
|
187
|
+
dispatch(setWorkflows({ workflows }));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!isLoadingContentTypes) {
|
|
191
|
+
dispatch(setContentTypes({ collectionTypes, singleTypes }));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!isLoadingRoles) {
|
|
195
|
+
dispatch(setRoles(serverRoles));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
dispatch(setIsLoading(isLoadingContentTypes || isLoadingRoles));
|
|
199
|
+
|
|
170
200
|
// Create an empty default stage
|
|
171
201
|
dispatch(
|
|
172
202
|
addStage({
|
|
173
203
|
name: '',
|
|
174
204
|
})
|
|
175
205
|
);
|
|
176
|
-
}, [
|
|
206
|
+
}, [
|
|
207
|
+
collectionTypes,
|
|
208
|
+
dispatch,
|
|
209
|
+
isLoadingContentTypes,
|
|
210
|
+
isLoadingRoles,
|
|
211
|
+
isLoadingWorkflow,
|
|
212
|
+
serverRoles,
|
|
213
|
+
singleTypes,
|
|
214
|
+
workflows,
|
|
215
|
+
]);
|
|
177
216
|
|
|
178
217
|
/**
|
|
179
218
|
* If the current license has a limit:
|
|
@@ -189,7 +228,7 @@ export function ReviewWorkflowsCreateView() {
|
|
|
189
228
|
*/
|
|
190
229
|
|
|
191
230
|
React.useEffect(() => {
|
|
192
|
-
if (!
|
|
231
|
+
if (!isLoadingWorkflow && !isLicenseLoading) {
|
|
193
232
|
if (
|
|
194
233
|
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
195
234
|
meta?.workflowsTotal >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
@@ -205,12 +244,25 @@ export function ReviewWorkflowsCreateView() {
|
|
|
205
244
|
}
|
|
206
245
|
}, [
|
|
207
246
|
isLicenseLoading,
|
|
208
|
-
|
|
247
|
+
isLoadingWorkflow,
|
|
209
248
|
limits,
|
|
210
249
|
meta?.workflowsTotal,
|
|
211
250
|
currentWorkflow.stages.length,
|
|
212
251
|
]);
|
|
213
252
|
|
|
253
|
+
React.useEffect(() => {
|
|
254
|
+
if (!isLoading && roles.length === 0) {
|
|
255
|
+
toggleNotification({
|
|
256
|
+
blockTransition: true,
|
|
257
|
+
type: 'warning',
|
|
258
|
+
message: formatMessage({
|
|
259
|
+
id: 'Settings.review-workflows.stage.permissions.noPermissions.description',
|
|
260
|
+
defaultMessage: 'You don’t have the permission to see roles',
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}, [formatMessage, isLoading, roles, toggleNotification]);
|
|
265
|
+
|
|
214
266
|
return (
|
|
215
267
|
<>
|
|
216
268
|
<Layout.DragLayerRendered />
|
|
@@ -225,7 +277,7 @@ export function ReviewWorkflowsCreateView() {
|
|
|
225
277
|
type="submit"
|
|
226
278
|
size="M"
|
|
227
279
|
disabled={!currentWorkflowIsDirty}
|
|
228
|
-
isLoading={
|
|
280
|
+
isLoading={isLoadingMutation}
|
|
229
281
|
>
|
|
230
282
|
{formatMessage({
|
|
231
283
|
id: 'global.save',
|
|
@@ -247,7 +299,7 @@ export function ReviewWorkflowsCreateView() {
|
|
|
247
299
|
/>
|
|
248
300
|
<Layout.Root>
|
|
249
301
|
<Flex alignItems="stretch" direction="column" gap={7}>
|
|
250
|
-
{
|
|
302
|
+
{isLoading ? (
|
|
251
303
|
<Loader>
|
|
252
304
|
{formatMessage({
|
|
253
305
|
id: 'Settings.review-workflows.page.isLoading',
|
|
@@ -256,10 +308,7 @@ export function ReviewWorkflowsCreateView() {
|
|
|
256
308
|
</Loader>
|
|
257
309
|
) : (
|
|
258
310
|
<Flex alignItems="stretch" direction="column" gap={7}>
|
|
259
|
-
<WorkflowAttributes
|
|
260
|
-
contentTypes={{ collectionTypes, singleTypes }}
|
|
261
|
-
workflows={workflows}
|
|
262
|
-
/>
|
|
311
|
+
<WorkflowAttributes />
|
|
263
312
|
<Stages stages={formik.values?.stages} />
|
|
264
313
|
</Flex>
|
|
265
314
|
)}
|