@strapi/admin 4.12.0-beta.3 → 4.12.0-beta.5
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/CollectionTypeFormWrapper/index.js +13 -35
- package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +304 -0
- package/admin/src/pages/AuthPage/components/Register/index.js +4 -0
- package/build/2379.0ca87a89.chunk.js +1 -0
- package/build/2395.df7a044a.chunk.js +26 -0
- package/build/2801.b1140c9b.chunk.js +1 -0
- package/build/{3100.21c343fa.chunk.js → 3100.2ba4df95.chunk.js} +1 -1
- package/build/{3483.ddd2d6df.chunk.js → 3483.e2ee2547.chunk.js} +1 -1
- package/build/3984.dda474f7.chunk.js +1 -0
- package/build/502.8ae8ef60.chunk.js +1 -0
- package/build/5483.6dd2e776.chunk.js +6 -0
- package/build/7065.ec811562.chunk.js +114 -0
- package/build/7464.8a6c1e6c.chunk.js +1 -0
- package/build/8276.6c7b8e6e.chunk.js +26 -0
- package/build/{9806.91360bb6.chunk.js → 9806.aa25371d.chunk.js} +10 -10
- package/build/{Admin-authenticatedApp.36b3826c.chunk.js → Admin-authenticatedApp.6b8dfa45.chunk.js} +1 -1
- package/build/{admin-app.1c3f7fd6.chunk.js → admin-app.c2e4e128.chunk.js} +8 -8
- package/build/content-manager.8772445b.chunk.js +1103 -0
- package/build/index.html +1 -1
- package/build/{main.a12c4c0f.js → main.af84ad9c.js} +281 -281
- package/build/review-workflows-settings-create-view.05758184.chunk.js +1 -0
- package/build/review-workflows-settings-edit-view.c33f7c58.chunk.js +1 -0
- package/build/review-workflows-settings-list-view.3ee9190d.chunk.js +56 -0
- package/build/{runtime~main.d197f488.js → runtime~main.a65ca6fb.js} +2 -2
- package/build/sso-settings-page.7c9b2fd9.chunk.js +1 -0
- package/ee/admin/constants.js +3 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +12 -4
- package/ee/admin/hooks/index.js +1 -1
- package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
- package/ee/admin/hooks/useLicenseLimits/__mocks__/index.js +8 -0
- package/ee/admin/hooks/useLicenseLimits/index.js +1 -3
- package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +3 -13
- package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +19 -4
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +7 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +20 -16
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +120 -29
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +108 -31
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +8 -3
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +138 -61
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +8 -3
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +72 -55
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +8 -3
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +2 -8
- package/ee/admin/pages/SettingsPage/pages/SingleSignOn/index.js +2 -8
- package/ee/admin/pages/SettingsPage/pages/SingleSignOn/utils/schema.js +8 -5
- package/ee/server/services/review-workflows/review-workflows.js +1 -1
- package/ee/server/services/review-workflows/validation.js +6 -4
- package/ee/server/services/review-workflows/workflows/content-types.js +28 -19
- package/ee/server/services/review-workflows/workflows/index.js +14 -2
- package/ee/server/validation/authentication.js +20 -9
- package/package.json +9 -9
- package/build/2379.d33a2e16.chunk.js +0 -1
- package/build/2395.b0419a54.chunk.js +0 -26
- package/build/2801.18f38baf.chunk.js +0 -1
- package/build/3984.ea7b8036.chunk.js +0 -1
- package/build/502.ccb38223.chunk.js +0 -1
- package/build/5483.ed2c7efa.chunk.js +0 -6
- package/build/7464.c6d0565c.chunk.js +0 -1
- package/build/8276.23e0763b.chunk.js +0 -26
- package/build/8298.fd253c9f.chunk.js +0 -117
- package/build/content-manager.b8d593d4.chunk.js +0 -1103
- package/build/review-workflows-settings-create-view.dfd87e1f.chunk.js +0 -1
- package/build/review-workflows-settings-edit-view.53c00afe.chunk.js +0 -1
- package/build/review-workflows-settings-list-view.a34be805.chunk.js +0 -56
- package/build/sso-settings-page.ed6f3f15.chunk.js +0 -1
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +0 -21
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import { Button, Flex, Loader } from '@strapi/design-system';
|
|
3
|
+
import { Button, Flex, Loader, Typography } from '@strapi/design-system';
|
|
4
4
|
import {
|
|
5
5
|
ConfirmDialog,
|
|
6
6
|
useAPIErrorHandler,
|
|
7
7
|
useFetchClient,
|
|
8
8
|
useNotification,
|
|
9
|
+
useRBAC,
|
|
9
10
|
} from '@strapi/helper-plugin';
|
|
10
11
|
import { Check } from '@strapi/icons';
|
|
11
12
|
import { useFormik, Form, FormikProvider } from 'formik';
|
|
@@ -17,19 +18,25 @@ import { useParams } from 'react-router-dom';
|
|
|
17
18
|
|
|
18
19
|
import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
|
|
19
20
|
import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
|
|
21
|
+
import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
|
|
20
22
|
import { useLicenseLimits } from '../../../../../../hooks';
|
|
21
|
-
import { setWorkflow } from '../../actions';
|
|
23
|
+
import { resetWorkflow, setWorkflow } from '../../actions';
|
|
22
24
|
import * as Layout from '../../components/Layout';
|
|
23
25
|
import * as LimitsModal from '../../components/LimitsModal';
|
|
24
26
|
import { Stages } from '../../components/Stages';
|
|
25
27
|
import { WorkflowAttributes } from '../../components/WorkflowAttributes';
|
|
26
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
|
|
30
|
+
CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
|
|
31
|
+
REDUX_NAMESPACE,
|
|
32
|
+
} from '../../constants';
|
|
27
33
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
|
28
34
|
import { reducer, initialState } from '../../reducer';
|
|
29
35
|
import { validateWorkflow } from '../../utils/validateWorkflow';
|
|
30
36
|
|
|
31
37
|
export function ReviewWorkflowsEditView() {
|
|
32
38
|
const { workflowId } = useParams();
|
|
39
|
+
const permissions = useSelector(selectAdminPermissions);
|
|
33
40
|
const { formatMessage } = useIntl();
|
|
34
41
|
const dispatch = useDispatch();
|
|
35
42
|
const { put } = useFetchClient();
|
|
@@ -38,10 +45,10 @@ export function ReviewWorkflowsEditView() {
|
|
|
38
45
|
const {
|
|
39
46
|
isLoading: isWorkflowLoading,
|
|
40
47
|
meta,
|
|
41
|
-
workflows
|
|
48
|
+
workflows,
|
|
42
49
|
status: workflowStatus,
|
|
43
50
|
refetch,
|
|
44
|
-
} = useReviewWorkflows(
|
|
51
|
+
} = useReviewWorkflows();
|
|
45
52
|
const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
|
|
46
53
|
const {
|
|
47
54
|
status,
|
|
@@ -49,15 +56,23 @@ export function ReviewWorkflowsEditView() {
|
|
|
49
56
|
currentWorkflow: {
|
|
50
57
|
data: currentWorkflow,
|
|
51
58
|
isDirty: currentWorkflowIsDirty,
|
|
52
|
-
hasDeletedServerStages
|
|
59
|
+
hasDeletedServerStages,
|
|
53
60
|
},
|
|
54
61
|
},
|
|
55
62
|
} = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
|
|
56
|
-
const
|
|
63
|
+
const {
|
|
64
|
+
allowedActions: { canDelete, canUpdate },
|
|
65
|
+
} = useRBAC(permissions.settings['review-workflows']);
|
|
66
|
+
const [savePrompts, setSavePrompts] = React.useState({});
|
|
57
67
|
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
|
|
58
68
|
const [showLimitModal, setShowLimitModal] = React.useState(false);
|
|
59
69
|
const [initialErrors, setInitialErrors] = React.useState(null);
|
|
60
70
|
|
|
71
|
+
const workflow = workflows.find((workflow) => workflow.id === parseInt(workflowId, 10));
|
|
72
|
+
const contentTypesFromOtherWorkflows = workflows
|
|
73
|
+
.filter((workflow) => workflow.id !== parseInt(workflowId, 10))
|
|
74
|
+
.flatMap((workflow) => workflow.contentTypes);
|
|
75
|
+
|
|
61
76
|
const { mutateAsync, isLoading } = useMutation(
|
|
62
77
|
async ({ workflow }) => {
|
|
63
78
|
const {
|
|
@@ -87,20 +102,12 @@ export function ReviewWorkflowsEditView() {
|
|
|
87
102
|
|
|
88
103
|
return res;
|
|
89
104
|
} catch (error) {
|
|
90
|
-
// TODO:
|
|
91
|
-
//
|
|
92
|
-
// several times. What we want instead in these scenarios is to print only the error summary and
|
|
93
|
-
// display the individual error messages for each field. This is a workaround, until we change the
|
|
94
|
-
// implementation of `formatAPIError`.
|
|
105
|
+
// TODO: this would benefit from a utility to get a formik error
|
|
106
|
+
// representation from an API error
|
|
95
107
|
if (
|
|
96
108
|
error.response.data?.error?.name === 'ValidationError' &&
|
|
97
109
|
error.response.data?.error?.details?.errors?.length > 0
|
|
98
110
|
) {
|
|
99
|
-
toggleNotification({
|
|
100
|
-
type: 'warning',
|
|
101
|
-
message: error.response.data.error.message,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
111
|
setInitialErrors(
|
|
105
112
|
error.response.data?.error?.details?.errors.reduce((acc, error) => {
|
|
106
113
|
set(acc, error.path, error.message);
|
|
@@ -108,13 +115,13 @@ export function ReviewWorkflowsEditView() {
|
|
|
108
115
|
return acc;
|
|
109
116
|
}, {})
|
|
110
117
|
);
|
|
111
|
-
} else {
|
|
112
|
-
toggleNotification({
|
|
113
|
-
type: 'warning',
|
|
114
|
-
message: formatAPIError(error),
|
|
115
|
-
});
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
toggleNotification({
|
|
121
|
+
type: 'warning',
|
|
122
|
+
message: formatAPIError(error),
|
|
123
|
+
});
|
|
124
|
+
|
|
118
125
|
return null;
|
|
119
126
|
}
|
|
120
127
|
};
|
|
@@ -123,15 +130,15 @@ export function ReviewWorkflowsEditView() {
|
|
|
123
130
|
await updateWorkflow(currentWorkflow);
|
|
124
131
|
await refetch();
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
setSavePrompts({});
|
|
127
134
|
};
|
|
128
135
|
|
|
129
136
|
const handleConfirmDeleteDialog = async () => {
|
|
130
137
|
await submitForm();
|
|
131
138
|
};
|
|
132
139
|
|
|
133
|
-
const
|
|
134
|
-
|
|
140
|
+
const handleConfirmClose = () => {
|
|
141
|
+
setSavePrompts({});
|
|
135
142
|
};
|
|
136
143
|
|
|
137
144
|
const formik = useFormik({
|
|
@@ -139,9 +146,14 @@ export function ReviewWorkflowsEditView() {
|
|
|
139
146
|
initialErrors,
|
|
140
147
|
initialValues: currentWorkflow,
|
|
141
148
|
async onSubmit() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
const isContentTypeReassignment = currentWorkflow.contentTypes.some((contentType) =>
|
|
150
|
+
contentTypesFromOtherWorkflows.includes(contentType)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
155
|
+
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
156
|
+
) {
|
|
145
157
|
/**
|
|
146
158
|
* If the current license has a limit, check if the total count of workflows
|
|
147
159
|
* exceeds that limit and display the limits modal instead of sending the
|
|
@@ -155,10 +167,19 @@ export function ReviewWorkflowsEditView() {
|
|
|
155
167
|
* update, because it would throw an API error.
|
|
156
168
|
*/
|
|
157
169
|
} else if (
|
|
158
|
-
limits?.
|
|
159
|
-
currentWorkflow.stages.length >
|
|
170
|
+
limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
171
|
+
currentWorkflow.stages.length >
|
|
172
|
+
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
160
173
|
) {
|
|
161
174
|
setShowLimitModal('stage');
|
|
175
|
+
} else if (hasDeletedServerStages || isContentTypeReassignment) {
|
|
176
|
+
if (hasDeletedServerStages) {
|
|
177
|
+
setSavePrompts((prev) => ({ ...prev, hasDeletedServerStages: true }));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isContentTypeReassignment) {
|
|
181
|
+
setSavePrompts((prev) => ({ ...prev, hasReassignedContentTypes: true }));
|
|
182
|
+
}
|
|
162
183
|
} else {
|
|
163
184
|
submitForm();
|
|
164
185
|
}
|
|
@@ -174,6 +195,12 @@ export function ReviewWorkflowsEditView() {
|
|
|
174
195
|
|
|
175
196
|
React.useEffect(() => {
|
|
176
197
|
dispatch(setWorkflow({ status: workflowStatus, data: workflow }));
|
|
198
|
+
|
|
199
|
+
// reset the state to the initial state to avoid flashes if a user
|
|
200
|
+
// navigates from an edit-view to a create-view
|
|
201
|
+
return () => {
|
|
202
|
+
dispatch(resetWorkflow());
|
|
203
|
+
};
|
|
177
204
|
}, [workflowStatus, workflow, dispatch]);
|
|
178
205
|
|
|
179
206
|
/**
|
|
@@ -191,11 +218,15 @@ export function ReviewWorkflowsEditView() {
|
|
|
191
218
|
|
|
192
219
|
React.useEffect(() => {
|
|
193
220
|
if (!isWorkflowLoading && !isLicenseLoading) {
|
|
194
|
-
if (
|
|
221
|
+
if (
|
|
222
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
223
|
+
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
224
|
+
) {
|
|
195
225
|
setShowLimitModal('workflow');
|
|
196
226
|
} else if (
|
|
197
|
-
limits?.
|
|
198
|
-
currentWorkflow.stages.length >
|
|
227
|
+
limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
228
|
+
currentWorkflow.stages.length >
|
|
229
|
+
parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
199
230
|
) {
|
|
200
231
|
setShowLimitModal('stage');
|
|
201
232
|
}
|
|
@@ -204,8 +235,7 @@ export function ReviewWorkflowsEditView() {
|
|
|
204
235
|
currentWorkflow.stages.length,
|
|
205
236
|
isLicenseLoading,
|
|
206
237
|
isWorkflowLoading,
|
|
207
|
-
limits
|
|
208
|
-
limits.workflows,
|
|
238
|
+
limits,
|
|
209
239
|
meta?.workflowCount,
|
|
210
240
|
meta.workflowsTotal,
|
|
211
241
|
]);
|
|
@@ -225,10 +255,10 @@ export function ReviewWorkflowsEditView() {
|
|
|
225
255
|
startIcon={<Check />}
|
|
226
256
|
type="submit"
|
|
227
257
|
size="M"
|
|
228
|
-
disabled={!currentWorkflowIsDirty}
|
|
258
|
+
disabled={!currentWorkflowIsDirty || !canUpdate}
|
|
229
259
|
// if the confirm dialog is open the loading state is on
|
|
230
260
|
// the confirm button already
|
|
231
|
-
loading={!
|
|
261
|
+
loading={!Object.keys(savePrompts).length > 0 && isLoading}
|
|
232
262
|
>
|
|
233
263
|
{formatMessage({
|
|
234
264
|
id: 'global.save',
|
|
@@ -236,45 +266,92 @@ export function ReviewWorkflowsEditView() {
|
|
|
236
266
|
})}
|
|
237
267
|
</Button>
|
|
238
268
|
}
|
|
239
|
-
subtitle={
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
269
|
+
subtitle={
|
|
270
|
+
currentWorkflow.stages.length > 0 &&
|
|
271
|
+
formatMessage(
|
|
272
|
+
{
|
|
273
|
+
id: 'Settings.review-workflows.page.subtitle',
|
|
274
|
+
defaultMessage: '{count, plural, one {# stage} other {# stages}}',
|
|
275
|
+
},
|
|
276
|
+
{ count: currentWorkflow.stages.length }
|
|
277
|
+
)
|
|
278
|
+
}
|
|
246
279
|
title={currentWorkflow.name}
|
|
247
280
|
/>
|
|
248
281
|
|
|
249
282
|
<Layout.Root>
|
|
250
283
|
{isLoadingModels || status === 'loading' ? (
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
284
|
+
<Flex justifyContent="center">
|
|
285
|
+
<Loader>
|
|
286
|
+
{formatMessage({
|
|
287
|
+
id: 'Settings.review-workflows.page.isLoading',
|
|
288
|
+
defaultMessage: 'Workflow is loading',
|
|
289
|
+
})}
|
|
290
|
+
</Loader>
|
|
291
|
+
</Flex>
|
|
257
292
|
) : (
|
|
258
293
|
<Flex alignItems="stretch" direction="column" gap={7}>
|
|
259
|
-
<WorkflowAttributes
|
|
260
|
-
|
|
294
|
+
<WorkflowAttributes
|
|
295
|
+
canUpdate={canUpdate}
|
|
296
|
+
contentTypes={{ collectionTypes, singleTypes }}
|
|
297
|
+
currentWorkflow={currentWorkflow}
|
|
298
|
+
workflows={workflows}
|
|
299
|
+
/>
|
|
300
|
+
<Stages
|
|
301
|
+
canDelete={canDelete}
|
|
302
|
+
canUpdate={canUpdate}
|
|
303
|
+
stages={formik.values?.stages}
|
|
304
|
+
/>
|
|
261
305
|
</Flex>
|
|
262
306
|
)}
|
|
263
307
|
</Layout.Root>
|
|
264
308
|
</Form>
|
|
265
309
|
</FormikProvider>
|
|
266
310
|
|
|
267
|
-
<ConfirmDialog
|
|
268
|
-
bodyText={{
|
|
269
|
-
id: 'Settings.review-workflows.page.delete.confirm.body',
|
|
270
|
-
defaultMessage:
|
|
271
|
-
'All entries assigned to deleted stages will be moved to the previous stage. Are you sure you want to save?',
|
|
272
|
-
}}
|
|
311
|
+
<ConfirmDialog.Root
|
|
273
312
|
isConfirmButtonLoading={isLoading}
|
|
274
|
-
isOpen={
|
|
275
|
-
onToggleDialog={
|
|
313
|
+
isOpen={Object.keys(savePrompts).length > 0}
|
|
314
|
+
onToggleDialog={handleConfirmClose}
|
|
276
315
|
onConfirm={handleConfirmDeleteDialog}
|
|
277
|
-
|
|
316
|
+
>
|
|
317
|
+
<ConfirmDialog.Body>
|
|
318
|
+
<Flex direction="column" gap={5}>
|
|
319
|
+
{savePrompts.hasDeletedServerStages && (
|
|
320
|
+
<Typography textAlign="center" variant="omega">
|
|
321
|
+
{formatMessage({
|
|
322
|
+
id: 'Settings.review-workflows.page.delete.confirm.stages.body',
|
|
323
|
+
defaultMessage:
|
|
324
|
+
'All entries assigned to deleted stages will be moved to the previous stage.',
|
|
325
|
+
})}
|
|
326
|
+
</Typography>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{savePrompts.hasReassignedContentTypes && (
|
|
330
|
+
<Typography textAlign="center" variant="omega">
|
|
331
|
+
{formatMessage(
|
|
332
|
+
{
|
|
333
|
+
id: 'Settings.review-workflows.page.delete.confirm.contentType.body',
|
|
334
|
+
defaultMessage:
|
|
335
|
+
'{count} {count, plural, one {content-type} other {content-types}} {count, plural, one {is} other {are}} already mapped to {count, plural, one {another workflow} other {other workflows}}. If you save changes, {count, plural, one {this} other {these}} {count, plural, one {content-type} other {{count} content-types}} will no more be mapped to the {count, plural, one {another workflow} other {other workflows}} and all corresponding information will be removed.',
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
count: contentTypesFromOtherWorkflows.filter((contentType) =>
|
|
339
|
+
currentWorkflow.contentTypes.includes(contentType)
|
|
340
|
+
).length,
|
|
341
|
+
}
|
|
342
|
+
)}
|
|
343
|
+
</Typography>
|
|
344
|
+
)}
|
|
345
|
+
|
|
346
|
+
<Typography textAlign="center" variant="omega">
|
|
347
|
+
{formatMessage({
|
|
348
|
+
id: 'Settings.review-workflows.page.delete.confirm.confirm',
|
|
349
|
+
defaultMessage: 'Are you sure you want to save?',
|
|
350
|
+
})}
|
|
351
|
+
</Typography>
|
|
352
|
+
</Flex>
|
|
353
|
+
</ConfirmDialog.Body>
|
|
354
|
+
</ConfirmDialog.Root>
|
|
278
355
|
|
|
279
356
|
<LimitsModal.Root
|
|
280
357
|
isOpen={showLimitModal === 'workflow'}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CheckPagePermissions } from '@strapi/helper-plugin';
|
|
4
|
+
import { useSelector } from 'react-redux';
|
|
5
|
+
|
|
6
|
+
import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
|
|
4
7
|
|
|
5
8
|
import { ReviewWorkflowsEditView } from './EditView';
|
|
6
9
|
|
|
7
10
|
export default function () {
|
|
11
|
+
const permissions = useSelector(selectAdminPermissions);
|
|
12
|
+
|
|
8
13
|
return (
|
|
9
|
-
<
|
|
14
|
+
<CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
|
|
10
15
|
<ReviewWorkflowsEditView />
|
|
11
|
-
</
|
|
16
|
+
</CheckPagePermissions>
|
|
12
17
|
);
|
|
13
18
|
}
|
|
@@ -23,18 +23,22 @@ import {
|
|
|
23
23
|
useAPIErrorHandler,
|
|
24
24
|
useFetchClient,
|
|
25
25
|
useNotification,
|
|
26
|
+
useRBAC,
|
|
26
27
|
useTracking,
|
|
27
28
|
} from '@strapi/helper-plugin';
|
|
28
29
|
import { Pencil, Plus, Trash } from '@strapi/icons';
|
|
29
30
|
import { useIntl } from 'react-intl';
|
|
30
31
|
import { useMutation } from 'react-query';
|
|
32
|
+
import { useSelector } from 'react-redux';
|
|
31
33
|
import { useHistory } from 'react-router-dom';
|
|
32
34
|
import styled from 'styled-components';
|
|
33
35
|
|
|
34
36
|
import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
|
|
37
|
+
import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
|
|
35
38
|
import { useLicenseLimits } from '../../../../../../hooks';
|
|
36
39
|
import * as Layout from '../../components/Layout';
|
|
37
40
|
import * as LimitsModal from '../../components/LimitsModal';
|
|
41
|
+
import { CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME } from '../../constants';
|
|
38
42
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
|
39
43
|
|
|
40
44
|
const ActionLink = styled(Link)`
|
|
@@ -76,6 +80,10 @@ export function ReviewWorkflowsListView() {
|
|
|
76
80
|
const toggleNotification = useNotification();
|
|
77
81
|
const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
|
|
78
82
|
const { trackUsage } = useTracking();
|
|
83
|
+
const permissions = useSelector(selectAdminPermissions);
|
|
84
|
+
const {
|
|
85
|
+
allowedActions: { canCreate, canDelete },
|
|
86
|
+
} = useRBAC(permissions.settings['review-workflows']);
|
|
79
87
|
|
|
80
88
|
const limits = getFeature('review-workflows');
|
|
81
89
|
|
|
@@ -148,24 +156,21 @@ export function ReviewWorkflowsListView() {
|
|
|
148
156
|
|
|
149
157
|
React.useEffect(() => {
|
|
150
158
|
if (!isLoading && !isLicenseLoading) {
|
|
151
|
-
if (
|
|
159
|
+
if (
|
|
160
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
161
|
+
meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
162
|
+
) {
|
|
152
163
|
setShowLimitModal(true);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
|
-
}, [
|
|
156
|
-
isLicenseLoading,
|
|
157
|
-
isLoading,
|
|
158
|
-
limits.stagesPerWorkflow,
|
|
159
|
-
limits.workflows,
|
|
160
|
-
meta?.workflowCount,
|
|
161
|
-
meta.workflowsTotal,
|
|
162
|
-
]);
|
|
166
|
+
}, [isLicenseLoading, isLoading, limits, meta?.workflowCount, meta.workflowsTotal]);
|
|
163
167
|
|
|
164
168
|
return (
|
|
165
169
|
<>
|
|
166
170
|
<Layout.Header
|
|
167
171
|
primaryAction={
|
|
168
172
|
<LinkButton
|
|
173
|
+
disabled={!canCreate}
|
|
169
174
|
startIcon={<Plus />}
|
|
170
175
|
size="S"
|
|
171
176
|
to="/settings/review-workflows/create"
|
|
@@ -180,7 +185,10 @@ export function ReviewWorkflowsListView() {
|
|
|
180
185
|
* current hard-limit of 200 they will see an error thrown by the API.
|
|
181
186
|
*/
|
|
182
187
|
|
|
183
|
-
if (
|
|
188
|
+
if (
|
|
189
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
190
|
+
meta?.workflowCount >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
191
|
+
) {
|
|
184
192
|
event.preventDefault();
|
|
185
193
|
setShowLimitModal(true);
|
|
186
194
|
} else {
|
|
@@ -207,42 +215,50 @@ export function ReviewWorkflowsListView() {
|
|
|
207
215
|
|
|
208
216
|
<Layout.Root>
|
|
209
217
|
{isLoading || isLoadingModels ? (
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
<Flex justifyContent="center">
|
|
219
|
+
<Loader>
|
|
220
|
+
{formatMessage({
|
|
221
|
+
id: 'Settings.review-workflows.page.list.isLoading',
|
|
222
|
+
defaultMessage: 'Workflows are loading',
|
|
223
|
+
})}
|
|
224
|
+
</Loader>
|
|
225
|
+
</Flex>
|
|
216
226
|
) : (
|
|
217
227
|
<Table
|
|
218
228
|
colCount={3}
|
|
219
229
|
footer={
|
|
220
230
|
// TODO: we should be able to use a link here instead of an (inaccessible onClick) handler
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
canCreate && (
|
|
232
|
+
<TFooter
|
|
233
|
+
icon={<Plus />}
|
|
234
|
+
onClick={() => {
|
|
235
|
+
/**
|
|
236
|
+
* If the current license has a workflow limit:
|
|
237
|
+
* check if the total count of workflows exceeds that limit
|
|
238
|
+
*
|
|
239
|
+
* If the current license does not have a limit (e.g. offline license):
|
|
240
|
+
* allow the user to navigate to the create-view. In case they exceed the
|
|
241
|
+
* current hard-limit of 200 they will see an error thrown by the API.
|
|
242
|
+
*/
|
|
232
243
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
if (
|
|
245
|
+
limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
|
|
246
|
+
meta?.workflowCount >=
|
|
247
|
+
parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
|
|
248
|
+
) {
|
|
249
|
+
setShowLimitModal(true);
|
|
250
|
+
} else {
|
|
251
|
+
push('/settings/review-workflows/create');
|
|
252
|
+
trackUsage('willCreateWorkflow');
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{formatMessage({
|
|
257
|
+
id: 'Settings.review-workflows.list.page.create',
|
|
258
|
+
defaultMessage: 'Create new workflow',
|
|
259
|
+
})}
|
|
260
|
+
</TFooter>
|
|
261
|
+
)
|
|
246
262
|
}
|
|
247
263
|
rowCount={1}
|
|
248
264
|
>
|
|
@@ -326,21 +342,22 @@ export function ReviewWorkflowsListView() {
|
|
|
326
342
|
<Pencil />
|
|
327
343
|
</ActionLink>
|
|
328
344
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
{
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
345
|
+
{workflows.length > 1 && canDelete && (
|
|
346
|
+
<IconButton
|
|
347
|
+
aria-label={formatMessage(
|
|
348
|
+
{
|
|
349
|
+
id: 'Settings.review-workflows.list.page.list.column.actions.delete.label',
|
|
350
|
+
defaultMessage: 'Delete {name}',
|
|
351
|
+
},
|
|
352
|
+
{ name: 'Default workflow' }
|
|
353
|
+
)}
|
|
354
|
+
icon={<Trash />}
|
|
355
|
+
noBorder
|
|
356
|
+
onClick={() => {
|
|
357
|
+
handleDeleteWorkflow(workflow.id);
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
)}
|
|
344
361
|
</Flex>
|
|
345
362
|
</Td>
|
|
346
363
|
</Tr>
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CheckPagePermissions } from '@strapi/helper-plugin';
|
|
4
|
+
import { useSelector } from 'react-redux';
|
|
5
|
+
|
|
6
|
+
import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
|
|
4
7
|
|
|
5
8
|
import { ReviewWorkflowsListView } from './ListView';
|
|
6
9
|
|
|
7
10
|
export default function () {
|
|
11
|
+
const permissions = useSelector(selectAdminPermissions);
|
|
12
|
+
|
|
8
13
|
return (
|
|
9
|
-
<
|
|
14
|
+
<CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
|
|
10
15
|
<ReviewWorkflowsListView />
|
|
11
|
-
</
|
|
16
|
+
</CheckPagePermissions>
|
|
12
17
|
);
|
|
13
18
|
}
|
|
@@ -22,13 +22,7 @@ export const initialState = {
|
|
|
22
22
|
data: {
|
|
23
23
|
name: '',
|
|
24
24
|
contentTypes: [],
|
|
25
|
-
stages: [
|
|
26
|
-
{
|
|
27
|
-
color: STAGE_COLOR_DEFAULT,
|
|
28
|
-
name: '',
|
|
29
|
-
__temp_key__: 1,
|
|
30
|
-
},
|
|
31
|
-
],
|
|
25
|
+
stages: [],
|
|
32
26
|
},
|
|
33
27
|
isDirty: false,
|
|
34
28
|
hasDeletedServerStages: false,
|
|
@@ -79,7 +73,7 @@ export function reducer(state = initialState, action) {
|
|
|
79
73
|
|
|
80
74
|
if (!currentWorkflow.hasDeletedServerStages) {
|
|
81
75
|
draft.clientState.currentWorkflow.hasDeletedServerStages = !!(
|
|
82
|
-
state.serverState.
|
|
76
|
+
state.serverState.workflow?.stages ?? []
|
|
83
77
|
).find((stage) => stage.id === stageId);
|
|
84
78
|
}
|
|
85
79
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
@@ -62,13 +62,7 @@ export const SingleSignOn = () => {
|
|
|
62
62
|
|
|
63
63
|
const showLoader = isLoadingForPermissions || isLoading;
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
if (formErrors.defaultRole) {
|
|
67
|
-
const selector = `[name="defaultRole"]`;
|
|
68
|
-
|
|
69
|
-
document.querySelector(selector).focus();
|
|
70
|
-
}
|
|
71
|
-
}, [formErrors]);
|
|
65
|
+
// TODO: focus() first error field, but it looks like that requires refactoring from useSettingsForm to Formik
|
|
72
66
|
|
|
73
67
|
const isHeaderButtonDisabled = isEqual(initialData, modifiedData);
|
|
74
68
|
|
|
@@ -6,11 +6,14 @@ const schema = yup.object().shape({
|
|
|
6
6
|
defaultRole: yup.mixed().when('autoRegister', (value, initSchema) => {
|
|
7
7
|
return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
|
|
8
8
|
}),
|
|
9
|
-
ssoLockedRoles: yup
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
ssoLockedRoles: yup
|
|
10
|
+
.array()
|
|
11
|
+
.nullable()
|
|
12
|
+
.of(
|
|
13
|
+
yup.mixed().when('ssoLockedRoles', (value, initSchema) => {
|
|
14
|
+
return value ? initSchema.required(translatedErrors.required) : initSchema.nullable();
|
|
15
|
+
})
|
|
16
|
+
),
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
export default schema;
|
|
@@ -22,7 +22,7 @@ const MAX_JOIN_TABLE_NAME_SUFFIX =
|
|
|
22
22
|
const MAX_CONTENT_TYPE_NAME_LEN = MAX_DB_TABLE_NAME_LEN - MAX_JOIN_TABLE_NAME_SUFFIX;
|
|
23
23
|
|
|
24
24
|
const DEFAULT_OPTIONS = {
|
|
25
|
-
|
|
25
|
+
numberOfWorkflows: MAX_WORKFLOWS,
|
|
26
26
|
stagesPerWorkflow: MAX_STAGES_PER_WORKFLOW,
|
|
27
27
|
};
|
|
28
28
|
|