mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 07:03:38 +00:00
chore(admin): convert review workflows page to TS (fix feedback)
This commit is contained in:
parent
31c6013e13
commit
59a6b3828b
@ -19,11 +19,7 @@ import {
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import {
|
||||
LimitsModal,
|
||||
Body,
|
||||
Title,
|
||||
} from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
|
||||
import { LimitsModal } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
|
||||
import { useLicenseLimits } from '../../../../../hooks/useLicenseLimits';
|
||||
import {
|
||||
CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
|
||||
@ -217,37 +213,43 @@ export function StageSelect() {
|
||||
</Flex>
|
||||
</Field>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'workflow'} onClose={() => setShowLimitModal(false)}>
|
||||
<Title>
|
||||
<LimitsModal.Root
|
||||
isOpen={showLimitModal === 'workflow'}
|
||||
onClose={() => setShowLimitModal(false)}
|
||||
>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'content-manager.reviewWorkflows.workflows.limit.title',
|
||||
defaultMessage: 'You’ve reached the limit of workflows in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'content-manager.reviewWorkflows.workflows.limit.body',
|
||||
defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'stage'} onClose={() => setShowLimitModal(false)}>
|
||||
<Title>
|
||||
<LimitsModal.Root
|
||||
isOpen={showLimitModal === 'stage'}
|
||||
onClose={() => setShowLimitModal(false)}
|
||||
>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'content-manager.reviewWorkflows.stages.limit.title',
|
||||
defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'content-manager.reviewWorkflows.stages.limit.body',
|
||||
defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ function useLicenseLimits({ enabled }: UseLicenseLimitsArgs = { enabled: true })
|
||||
|
||||
type FeatureNames = GetLicenseLimitInformation.Response['data']['features'][number]['name'];
|
||||
|
||||
type GetFeatureType = (name: FeatureNames) => Record<string, unknown> | undefined;
|
||||
type GetFeatureType = <T>(name: FeatureNames) => Record<string, T> | undefined;
|
||||
|
||||
const getFeature = React.useCallback<GetFeatureType>(
|
||||
(name) => {
|
||||
|
||||
@ -32,7 +32,7 @@ import {
|
||||
setWorkflows,
|
||||
} from './actions';
|
||||
import * as Layout from './components/Layout';
|
||||
import { LimitsModal, Body, Title } from './components/LimitsModal';
|
||||
import { LimitsModal } from './components/LimitsModal';
|
||||
import { Stages } from './components/Stages';
|
||||
import { WorkflowAttributes } from './components/WorkflowAttributes';
|
||||
import {
|
||||
@ -71,9 +71,9 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
const [initialErrors, setInitialErrors] = React.useState<FormikErrors<CurrentWorkflow>>();
|
||||
const [savePrompts, setSavePrompts] = React.useState<{ hasReassignedContentTypes?: boolean }>({});
|
||||
|
||||
const limits = getFeature('review-workflows');
|
||||
const numberOfWorkflows = limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] as string;
|
||||
const stagesPerWorkflow = limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] as string;
|
||||
const limits = getFeature<string>('review-workflows');
|
||||
const numberOfWorkflows = limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME];
|
||||
const stagesPerWorkflow = limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME];
|
||||
const contentTypesFromOtherWorkflows = workflows?.flatMap((workflow) => workflow.contentTypes);
|
||||
|
||||
const { mutateAsync } = useMutation<
|
||||
@ -165,7 +165,7 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
* update, because it would throw an API error.
|
||||
*/
|
||||
|
||||
if (meta && meta?.workflowCount >= parseInt(numberOfWorkflows, 10)) {
|
||||
if (meta && numberOfWorkflows && meta?.workflowCount >= parseInt(numberOfWorkflows, 10)) {
|
||||
setShowLimitModal('workflow');
|
||||
|
||||
/**
|
||||
@ -175,6 +175,7 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
*/
|
||||
} else if (
|
||||
currentWorkflow.stages &&
|
||||
stagesPerWorkflow &&
|
||||
currentWorkflow.stages.length >= parseInt(stagesPerWorkflow, 10)
|
||||
) {
|
||||
setShowLimitModal('stage');
|
||||
@ -242,6 +243,7 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
if (
|
||||
currentWorkflow.stages &&
|
||||
limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
|
||||
stagesPerWorkflow &&
|
||||
currentWorkflow.stages.length >= parseInt(stagesPerWorkflow, 10)
|
||||
) {
|
||||
setShowLimitModal('stage');
|
||||
@ -250,7 +252,7 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
}, [isLicenseLoading, isLoadingWorkflow, limits, currentWorkflow.stages, stagesPerWorkflow]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLoading && roles.length === 0) {
|
||||
if (!isLoading && roles?.length === 0) {
|
||||
toggleNotification({
|
||||
blockTransition: true,
|
||||
type: 'warning',
|
||||
@ -349,37 +351,40 @@ export const ReviewWorkflowsCreatePage = () => {
|
||||
</ConfirmDialog.Body>
|
||||
</ConfirmDialog.Root>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'workflow'} onClose={() => setShowLimitModal(null)}>
|
||||
<Title>
|
||||
<LimitsModal.Root
|
||||
isOpen={showLimitModal === 'workflow'}
|
||||
onClose={() => setShowLimitModal(null)}
|
||||
>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.create.page.workflows.limit.title',
|
||||
defaultMessage: 'You’ve reached the limit of workflows in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.create.page.workflows.limit.body',
|
||||
defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'stage'} onClose={() => setShowLimitModal(null)}>
|
||||
<Title>
|
||||
<LimitsModal.Root isOpen={showLimitModal === 'stage'} onClose={() => setShowLimitModal(null)}>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.create.page.stages.limit.title',
|
||||
defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.create.page.stages.limit.body',
|
||||
defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -22,7 +22,7 @@ import { useAdminRoles } from '../../../../../../../admin/src/hooks/useAdminRole
|
||||
import { useContentTypes } from '../../../../../../../admin/src/hooks/useContentTypes';
|
||||
import { useInjectReducer } from '../../../../../../../admin/src/hooks/useInjectReducer';
|
||||
import { selectAdminPermissions } from '../../../../../../../admin/src/selectors';
|
||||
import { Stage, Update } from '../../../../../../../shared/contracts/review-workflows';
|
||||
import { Stage, Update, Workflow } from '../../../../../../../shared/contracts/review-workflows';
|
||||
import { useLicenseLimits } from '../../../../hooks/useLicenseLimits';
|
||||
|
||||
import {
|
||||
@ -34,7 +34,7 @@ import {
|
||||
setWorkflows,
|
||||
} from './actions';
|
||||
import * as Layout from './components/Layout';
|
||||
import { LimitsModal, Body, Title } from './components/LimitsModal';
|
||||
import { LimitsModal } from './components/LimitsModal';
|
||||
import { Stages } from './components/Stages';
|
||||
import { WorkflowAttributes } from './components/WorkflowAttributes';
|
||||
import {
|
||||
@ -89,9 +89,9 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
?.filter((workflow) => workflow.id !== parseInt(workflowId, 10))
|
||||
.flatMap((workflow) => workflow.contentTypes);
|
||||
|
||||
const limits = getFeature('review-workflows');
|
||||
const numberOfWorkflows = limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] as string;
|
||||
const stagesPerWorkflow = limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] as string;
|
||||
const limits = getFeature<string>('review-workflows');
|
||||
const numberOfWorkflows = limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME];
|
||||
const stagesPerWorkflow = limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME];
|
||||
|
||||
const { mutateAsync, isLoading: isLoadingMutation } = useMutation<
|
||||
Update.Response['data'],
|
||||
@ -117,7 +117,7 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const updateWorkflow = async (workflow: CurrentWorkflow) => {
|
||||
const updateWorkflow = async (workflow: Partial<Workflow>) => {
|
||||
// reset the error messages
|
||||
setInitialErrors(undefined);
|
||||
|
||||
@ -131,16 +131,16 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
// permissions to see roles
|
||||
stages: workflow.stages?.map((stage) => {
|
||||
let hasUpdatedPermissions = true;
|
||||
const serverStage = serverState.workflow?.stages.find(
|
||||
const serverStage = serverState.workflow?.stages?.find(
|
||||
(serverStage) => serverStage.id === stage?.id
|
||||
);
|
||||
|
||||
if (serverStage) {
|
||||
hasUpdatedPermissions =
|
||||
serverStage.permissions?.length !== stage.permissions?.length ||
|
||||
!serverStage.permissions.every(
|
||||
!serverStage.permissions?.every(
|
||||
(serverPermission) =>
|
||||
!!stage.permissions.find(
|
||||
!!stage.permissions?.find(
|
||||
(permission) => permission.role === serverPermission.role
|
||||
)
|
||||
);
|
||||
@ -149,7 +149,7 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
return {
|
||||
...stage,
|
||||
permissions: hasUpdatedPermissions ? stage.permissions : undefined,
|
||||
} as Stage;
|
||||
} satisfies Stage;
|
||||
}),
|
||||
},
|
||||
});
|
||||
@ -296,10 +296,11 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLoadingWorkflow && !isLicenseLoading) {
|
||||
if (meta && meta?.workflowCount > parseInt(numberOfWorkflows, 10)) {
|
||||
if (meta && numberOfWorkflows && meta?.workflowCount > parseInt(numberOfWorkflows, 10)) {
|
||||
setShowLimitModal('workflow');
|
||||
} else if (
|
||||
currentWorkflow.stages &&
|
||||
stagesPerWorkflow &&
|
||||
currentWorkflow.stages.length > parseInt(stagesPerWorkflow, 10)
|
||||
) {
|
||||
setShowLimitModal('stage');
|
||||
@ -316,7 +317,7 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLoading && roles.length === 0) {
|
||||
if (!isLoading && roles?.length === 0) {
|
||||
toggleNotification({
|
||||
blockTransition: true,
|
||||
type: 'warning',
|
||||
@ -438,37 +439,40 @@ export const ReviewWorkflowsEditPage = () => {
|
||||
</ConfirmDialog.Body>
|
||||
</ConfirmDialog.Root>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'workflow'} onClose={() => setShowLimitModal(null)}>
|
||||
<Title>
|
||||
<LimitsModal.Root
|
||||
isOpen={showLimitModal === 'workflow'}
|
||||
onClose={() => setShowLimitModal(null)}
|
||||
>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.edit.page.workflows.limit.title',
|
||||
defaultMessage: 'You’ve reached the limit of workflows in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.edit.page.workflows.limit.body',
|
||||
defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal === 'stage'} onClose={() => setShowLimitModal(null)}>
|
||||
<Title>
|
||||
<LimitsModal.Root isOpen={showLimitModal === 'stage'} onClose={() => setShowLimitModal(null)}>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.edit.page.stages.limit.title',
|
||||
defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.edit.page.stages.limit.body',
|
||||
defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -41,7 +41,7 @@ import { Update } from '../../../../../../../shared/contracts/review-workflows';
|
||||
import { useLicenseLimits } from '../../../../hooks/useLicenseLimits';
|
||||
|
||||
import * as Layout from './components/Layout';
|
||||
import { LimitsModal, Title, Body } from './components/LimitsModal';
|
||||
import { LimitsModal } from './components/LimitsModal';
|
||||
import { CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME } from './constants';
|
||||
import { useReviewWorkflows } from './hooks/useReviewWorkflows';
|
||||
|
||||
@ -390,21 +390,21 @@ export const ReviewWorkflowsListView = () => {
|
||||
onConfirm={handleConfirmDeleteDialog}
|
||||
/>
|
||||
|
||||
<LimitsModal isOpen={showLimitModal} onClose={() => setShowLimitModal(false)}>
|
||||
<Title>
|
||||
<LimitsModal.Root isOpen={showLimitModal} onClose={() => setShowLimitModal(false)}>
|
||||
<LimitsModal.Title>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.list.page.workflows.limit.title',
|
||||
defaultMessage: 'You’ve reached the limit of workflows in your plan',
|
||||
})}
|
||||
</Title>
|
||||
</LimitsModal.Title>
|
||||
|
||||
<Body>
|
||||
<LimitsModal.Body>
|
||||
{formatMessage({
|
||||
id: 'Settings.review-workflows.list.page.workflows.limit.body',
|
||||
defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
|
||||
})}
|
||||
</Body>
|
||||
</LimitsModal>
|
||||
</LimitsModal.Body>
|
||||
</LimitsModal.Root>
|
||||
</Layout.Root>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -13,7 +13,7 @@ const TITLE_ID = 'limits-title';
|
||||
const CTA_LEARN_MORE_HREF = 'https://strapi.io/pricing-cloud';
|
||||
const CTA_SALES_HREF = 'https://strapi.io/contact-sales';
|
||||
|
||||
export const Title: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const Title: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<Typography variant="alpha" id={TITLE_ID}>
|
||||
{children}
|
||||
@ -21,7 +21,7 @@ export const Title: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Body: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const Body: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return <Typography variant="omega">{children}</Typography>;
|
||||
};
|
||||
|
||||
@ -59,7 +59,7 @@ export type LimitsModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const LimitsModal: React.FC<React.PropsWithChildren<LimitsModalProps>> = ({
|
||||
const Root: React.FC<React.PropsWithChildren<LimitsModalProps>> = ({
|
||||
children,
|
||||
isOpen = false,
|
||||
onClose,
|
||||
@ -99,3 +99,11 @@ export const LimitsModal: React.FC<React.PropsWithChildren<LimitsModalProps>> =
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
const LimitsModal = {
|
||||
Title,
|
||||
Body,
|
||||
Root,
|
||||
};
|
||||
|
||||
export { LimitsModal };
|
||||
|
||||
@ -214,6 +214,7 @@ export const Stage = ({
|
||||
useDragAndDrop(canReorder, {
|
||||
index,
|
||||
item: {
|
||||
index,
|
||||
name: nameField.value,
|
||||
},
|
||||
onGrabItem: handleGrabStage,
|
||||
@ -225,7 +226,7 @@ export const Stage = ({
|
||||
|
||||
const composedRef = composeRefs(stageRef, dropRef);
|
||||
|
||||
const colorOptions = AVAILABLE_COLORS.map(({ hex, name }: { hex: string; name: string }) => ({
|
||||
const colorOptions = AVAILABLE_COLORS.map(({ hex, name }) => ({
|
||||
value: hex,
|
||||
label: formatMessage(
|
||||
{
|
||||
@ -242,14 +243,14 @@ export const Stage = ({
|
||||
const filteredRoles = roles
|
||||
// Super admins always have permissions to do everything and therefore
|
||||
// there is no point for this role to show up in the role combobox
|
||||
.filter((role) => role.code !== 'strapi-super-admin');
|
||||
?.filter((role) => role.code !== 'strapi-super-admin');
|
||||
|
||||
React.useEffect(() => {
|
||||
dragPreviewRef(getEmptyImage(), { captureDraggingState: false });
|
||||
}, [dragPreviewRef, index]);
|
||||
|
||||
return (
|
||||
<Box ref={composedRef}>
|
||||
<Box ref={(ref) => composedRef(ref!)}>
|
||||
{liveText && <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>}
|
||||
|
||||
{isDragging ? (
|
||||
@ -315,6 +316,7 @@ export const Stage = ({
|
||||
{canUpdate && (
|
||||
<DragIconButton
|
||||
background="transparent"
|
||||
// @ts-expect-error - forwardedAs needs to be defined as string in the IconButton props
|
||||
forwardedAs="div"
|
||||
hasRadius
|
||||
role="button"
|
||||
@ -384,39 +386,37 @@ export const Stage = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{colorOptions.map(
|
||||
({ value, label, color }: { value: string; label: string; color: string }) => {
|
||||
const { themeColorName } = getStageColorByHex(color) || {};
|
||||
{colorOptions.map(({ value, label, color }) => {
|
||||
const { themeColorName } = getStageColorByHex(color) || {};
|
||||
|
||||
return (
|
||||
<SingleSelectOption
|
||||
value={value}
|
||||
key={value}
|
||||
startIcon={
|
||||
<Flex
|
||||
as="span"
|
||||
height={2}
|
||||
background={color}
|
||||
// @ts-expect-error - transparent doesn't exist in theme.colors
|
||||
borderColor={
|
||||
themeColorName === 'neutral0' ? 'neutral150' : 'transparent'
|
||||
}
|
||||
hasRadius
|
||||
shrink={0}
|
||||
width={2}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</SingleSelectOption>
|
||||
);
|
||||
}
|
||||
)}
|
||||
return (
|
||||
<SingleSelectOption
|
||||
value={value}
|
||||
key={value}
|
||||
startIcon={
|
||||
<Flex
|
||||
as="span"
|
||||
height={2}
|
||||
background={color}
|
||||
// @ts-expect-error - transparent doesn't exist in theme.colors
|
||||
borderColor={
|
||||
themeColorName === 'neutral0' ? 'neutral150' : 'transparent'
|
||||
}
|
||||
hasRadius
|
||||
shrink={0}
|
||||
width={2}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</SingleSelectOption>
|
||||
);
|
||||
})}
|
||||
</SingleSelect>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6}>
|
||||
{filteredRoles.length === 0 ? (
|
||||
{filteredRoles?.length === 0 ? (
|
||||
<NotAllowedInput
|
||||
description={{
|
||||
id: 'Settings.review-workflows.stage.permissions.noPermissions.description',
|
||||
@ -468,9 +468,9 @@ export const Stage = ({
|
||||
id: 'Settings.review-workflows.stage.permissions.allRoles.label',
|
||||
defaultMessage: 'All roles',
|
||||
})}
|
||||
values={filteredRoles.map((r) => `${r.id}`)}
|
||||
values={filteredRoles?.map((r) => `${r.id}`)}
|
||||
>
|
||||
{filteredRoles.map((role) => {
|
||||
{filteredRoles?.map((role) => {
|
||||
return (
|
||||
<NestedOption key={role.id} value={`${role.id}`}>
|
||||
{role.name}
|
||||
|
||||
@ -33,7 +33,7 @@ export type WorkflowAttributesProps = {
|
||||
export const WorkflowAttributes = ({ canUpdate = true }: WorkflowAttributesProps) => {
|
||||
const { formatMessage, locale } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const { collectionTypes, singleTypes } = useSelector(selectContentTypes);
|
||||
const contentTypes = useSelector(selectContentTypes);
|
||||
const currentWorkflow = useSelector(selectCurrentWorkflow);
|
||||
const workflows = useSelector(selectWorkflows);
|
||||
const [nameField, nameMeta, nameHelper] = useField('name');
|
||||
@ -62,119 +62,121 @@ export const WorkflowAttributes = ({ canUpdate = true }: WorkflowAttributesProps
|
||||
/>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6}>
|
||||
<MultiSelect
|
||||
{...contentTypesField}
|
||||
customizeContent={(value) =>
|
||||
formatMessage(
|
||||
{
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.displayValue',
|
||||
defaultMessage:
|
||||
'{count} {count, plural, one {content type} other {content types}} selected',
|
||||
},
|
||||
{ count: value?.length }
|
||||
)
|
||||
}
|
||||
disabled={!canUpdate}
|
||||
error={contentTypesMeta.error ?? false}
|
||||
id={contentTypesField.name}
|
||||
label={formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.label',
|
||||
defaultMessage: 'Associated to',
|
||||
})}
|
||||
onChange={(values) => {
|
||||
dispatch(updateWorkflow({ contentTypes: values }));
|
||||
contentTypesHelper.setValue(values);
|
||||
}}
|
||||
placeholder={formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
|
||||
defaultMessage: 'Select',
|
||||
})}
|
||||
>
|
||||
{[
|
||||
...(collectionTypes.length > 0
|
||||
? [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
|
||||
defaultMessage: 'Collection Types',
|
||||
}),
|
||||
children: [...collectionTypes]
|
||||
.sort((a, b) => formatter.compare(a.info.displayName, b.info.displayName))
|
||||
.map((contentType) => ({
|
||||
{contentTypes && (
|
||||
<GridItem col={6}>
|
||||
<MultiSelect
|
||||
{...contentTypesField}
|
||||
customizeContent={(value) =>
|
||||
formatMessage(
|
||||
{
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.displayValue',
|
||||
defaultMessage:
|
||||
'{count} {count, plural, one {content type} other {content types}} selected',
|
||||
},
|
||||
{ count: value?.length }
|
||||
)
|
||||
}
|
||||
disabled={!canUpdate}
|
||||
error={contentTypesMeta.error ?? false}
|
||||
id={contentTypesField.name}
|
||||
label={formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.label',
|
||||
defaultMessage: 'Associated to',
|
||||
})}
|
||||
onChange={(values) => {
|
||||
dispatch(updateWorkflow({ contentTypes: values }));
|
||||
contentTypesHelper.setValue(values);
|
||||
}}
|
||||
placeholder={formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
|
||||
defaultMessage: 'Select',
|
||||
})}
|
||||
>
|
||||
{[
|
||||
...(contentTypes.collectionTypes.length > 0
|
||||
? [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
|
||||
defaultMessage: 'Collection Types',
|
||||
}),
|
||||
children: [...contentTypes.collectionTypes]
|
||||
.sort((a, b) => formatter.compare(a.info.displayName, b.info.displayName))
|
||||
.map((contentType) => ({
|
||||
label: contentType.info.displayName,
|
||||
value: contentType.uid,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(contentTypes.singleTypes.length > 0
|
||||
? [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
|
||||
defaultMessage: 'Single Types',
|
||||
}),
|
||||
children: [...contentTypes.singleTypes].map((contentType) => ({
|
||||
label: contentType.info.displayName,
|
||||
value: contentType.uid,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((opt) => {
|
||||
if ('children' in opt) {
|
||||
return (
|
||||
<MultiSelectGroup
|
||||
key={opt.label}
|
||||
label={opt.label}
|
||||
values={opt.children.map((child) => child.value.toString())}
|
||||
>
|
||||
{opt.children.map((child) => {
|
||||
const { name: assignedWorkflowName } =
|
||||
workflows?.find(
|
||||
(workflow) =>
|
||||
((currentWorkflow && workflow.id !== currentWorkflow.id) ||
|
||||
!currentWorkflow) &&
|
||||
workflow.contentTypes.includes(child.value)
|
||||
) ?? {};
|
||||
|
||||
...(singleTypes.length > 0
|
||||
? [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
|
||||
defaultMessage: 'Single Types',
|
||||
}),
|
||||
children: [...singleTypes].map((contentType) => ({
|
||||
label: contentType.info.displayName,
|
||||
value: contentType.uid,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((opt) => {
|
||||
if ('children' in opt) {
|
||||
return (
|
||||
<MultiSelectGroup
|
||||
key={opt.label}
|
||||
label={opt.label}
|
||||
values={opt.children.map((child) => child.value.toString())}
|
||||
>
|
||||
{opt.children.map((child) => {
|
||||
const { name: assignedWorkflowName } =
|
||||
workflows.find(
|
||||
(workflow) =>
|
||||
((currentWorkflow && workflow.id !== currentWorkflow.id) ||
|
||||
!currentWorkflow) &&
|
||||
workflow.contentTypes.includes(child.value)
|
||||
) ?? {};
|
||||
|
||||
return (
|
||||
<NestedOption key={child.value} value={child.value}>
|
||||
<Typography>
|
||||
{
|
||||
// @ts-expect-error - formatMessage options doesn't expect to be a React component but that's what we need actually for the <i> and <em> components
|
||||
formatMessage(
|
||||
{
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.assigned.notice',
|
||||
defaultMessage:
|
||||
'{label} {name, select, undefined {} other {<i>(assigned to <em>{name}</em> workflow)</i>}}',
|
||||
},
|
||||
{
|
||||
label: child.label,
|
||||
name: assignedWorkflowName,
|
||||
em: (...children) => (
|
||||
<Typography as="em" fontWeight="bold">
|
||||
{children}
|
||||
</Typography>
|
||||
),
|
||||
i: (...children) => (
|
||||
<ContentTypeTakeNotice>{children}</ContentTypeTakeNotice>
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
</Typography>
|
||||
</NestedOption>
|
||||
);
|
||||
})}
|
||||
</MultiSelectGroup>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</MultiSelect>
|
||||
</GridItem>
|
||||
return (
|
||||
<NestedOption key={child.value} value={child.value}>
|
||||
<Typography>
|
||||
{
|
||||
// @ts-expect-error - formatMessage options doesn't expect to be a React component but that's what we need actually for the <i> and <em> components
|
||||
formatMessage(
|
||||
{
|
||||
id: 'Settings.review-workflows.workflow.contentTypes.assigned.notice',
|
||||
defaultMessage:
|
||||
'{label} {name, select, undefined {} other {<i>(assigned to <em>{name}</em> workflow)</i>}}',
|
||||
},
|
||||
{
|
||||
label: child.label,
|
||||
name: assignedWorkflowName,
|
||||
em: (...children) => (
|
||||
<Typography as="em" fontWeight="bold">
|
||||
{children}
|
||||
</Typography>
|
||||
),
|
||||
i: (...children) => (
|
||||
<ContentTypeTakeNotice>{children}</ContentTypeTakeNotice>
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
</Typography>
|
||||
</NestedOption>
|
||||
);
|
||||
})}
|
||||
</MultiSelectGroup>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</MultiSelect>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,14 +5,14 @@ import { render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { Body, LimitsModal, LimitsModalProps, Title } from '../LimitsModal';
|
||||
import { LimitsModal, LimitsModalProps } from '../LimitsModal';
|
||||
|
||||
const setup = (props?: Partial<LimitsModalProps>) => ({
|
||||
...render(
|
||||
<LimitsModal isOpen onClose={() => {}} {...props}>
|
||||
<Title>Title</Title>
|
||||
<Body>Body</Body>
|
||||
</LimitsModal>,
|
||||
<LimitsModal.Root isOpen onClose={() => {}} {...props}>
|
||||
<LimitsModal.Title>Title</LimitsModal.Title>
|
||||
<LimitsModal.Body>Body</LimitsModal.Body>
|
||||
</LimitsModal.Root>,
|
||||
{
|
||||
wrapper({ children }) {
|
||||
return (
|
||||
|
||||
@ -156,6 +156,7 @@ const setup = ({ roles, ...props }: Setup = {}) => {
|
||||
const store = configureStore({
|
||||
reducer,
|
||||
preloadedState: {
|
||||
// @ts-expect-error - Since we are passing the local ReviewWorkflow reducer, REDUX_NAMESPACE can't be set as part of the preloadedState
|
||||
[REDUX_NAMESPACE]: {
|
||||
serverState: {
|
||||
contentTypes: CONTENT_TYPES_FIXTURE,
|
||||
|
||||
@ -112,6 +112,7 @@ const setup = ({
|
||||
const store = configureStore({
|
||||
reducer,
|
||||
preloadedState: {
|
||||
// @ts-expect-error - Since we are passing the local ReviewWorkflow reducer, REDUX_NAMESPACE can't be set as part of the preloadedState
|
||||
[REDUX_NAMESPACE]: {
|
||||
serverState: {
|
||||
contentTypes: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { lightTheme } from '@strapi/design-system';
|
||||
|
||||
export const REDUX_NAMESPACE: string = 'settings_review-workflows';
|
||||
export const REDUX_NAMESPACE = 'settings_review-workflows';
|
||||
|
||||
export const ACTION_RESET_WORKFLOW = `Settings/Review_Workflows/RESET_WORKFLOW`;
|
||||
export const ACTION_SET_CONTENT_TYPES = `Settings/Review_Workflows/SET_CONTENT_TYPES`;
|
||||
|
||||
@ -1,51 +1,69 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useFetchClient } from '@strapi/helper-plugin';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GetAll } from '../../../../../../../../shared/contracts/review-workflows';
|
||||
import { GetAll, Get } from '../../../../../../../../shared/contracts/review-workflows';
|
||||
|
||||
type Params = { id?: number };
|
||||
export type APIReviewWorkflowsQueryParams = Get.Params | (GetAll.Request['query'] & { id?: never });
|
||||
|
||||
export function useReviewWorkflows(params: Params = {}) {
|
||||
export function useReviewWorkflows(params: APIReviewWorkflowsQueryParams = {}) {
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const { id = '', ...queryParams } = params;
|
||||
const { ...queryParams } = params;
|
||||
const defaultQueryParams = {
|
||||
populate: 'stages',
|
||||
};
|
||||
|
||||
const { data, isLoading, status, refetch } = useQuery<
|
||||
GetAll.Response,
|
||||
AxiosError<GetAll.Response['error']>
|
||||
>(['review-workflows', 'workflows', id], async () => {
|
||||
const res = await get<GetAll.Response>(`/admin/review-workflows/workflows/${id}`, {
|
||||
params: { ...defaultQueryParams, ...queryParams },
|
||||
});
|
||||
const { data, isLoading, status, refetch } = useQuery(
|
||||
['review-workflows', 'workflows', params.id],
|
||||
async () => {
|
||||
const res = await get<GetAll.Response | Get.Response>(
|
||||
`/admin/review-workflows/workflows/${params.id}`,
|
||||
{
|
||||
params: { ...defaultQueryParams, ...queryParams },
|
||||
}
|
||||
);
|
||||
|
||||
return res.data;
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
);
|
||||
|
||||
// the return value needs to be memoized, because intantiating
|
||||
// an empty array as default value would lead to an unstable return
|
||||
// value, which later on triggers infinite loops if used in the
|
||||
// dependency arrays of other hooks
|
||||
const workflows = React.useMemo(() => {
|
||||
if (id && data?.data) {
|
||||
return [data.data];
|
||||
}
|
||||
if (Array.isArray(data?.data)) {
|
||||
return data.data;
|
||||
let workflows: GetAll.Response['data'] = [];
|
||||
|
||||
if (data) {
|
||||
if (Array.isArray(data.data)) {
|
||||
workflows = data.data;
|
||||
} else {
|
||||
workflows = [data.data];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [data?.data, id]);
|
||||
return workflows;
|
||||
}, [data]);
|
||||
|
||||
const meta = React.useMemo(() => {
|
||||
let meta: GetAll.Response['meta'];
|
||||
|
||||
if (data) {
|
||||
if (Array.isArray(data.data)) {
|
||||
// @ts-expect-error - data.meta doesn't exist in type Get.Response
|
||||
meta = data?.meta;
|
||||
}
|
||||
}
|
||||
|
||||
return meta;
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
// meta contains e.g. the total of all workflows. we can not use
|
||||
// the pagination object here, because the list is not paginated.
|
||||
meta: React.useMemo(() => data?.meta ?? {}, [data?.meta]) as GetAll.Response['meta'],
|
||||
meta,
|
||||
workflows,
|
||||
isLoading,
|
||||
status,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Entity, Schema } from '@strapi/types';
|
||||
import { produce } from 'immer';
|
||||
import { Schema } from '@strapi/types';
|
||||
import { createDraft, produce } from 'immer';
|
||||
|
||||
import {
|
||||
Stage,
|
||||
@ -44,11 +44,11 @@ interface ServerState {
|
||||
}
|
||||
|
||||
// This isn't something we should do.
|
||||
// TODO: Revamp the way we are
|
||||
type StageWithTempKey = Partial<Stage & { __temp_key__?: number }>;
|
||||
// TODO: Revamp the way we are handling this temp key for delete or create
|
||||
export type StageWithTempKey = Stage & { __temp_key__?: number };
|
||||
interface ClientState {
|
||||
currentWorkflow: {
|
||||
data: Partial<Omit<CurrentWorkflow, 'stages'> & { stages?: StageWithTempKey[] }>;
|
||||
data: Partial<Omit<CurrentWorkflow, 'stages'> & { stages: StageWithTempKey[] }>;
|
||||
};
|
||||
isLoading?: boolean;
|
||||
}
|
||||
@ -127,7 +127,7 @@ export function reducer(state: State = initialState, action: { type: string; pay
|
||||
|
||||
case ACTION_RESET_WORKFLOW: {
|
||||
draft.clientState = initialState.clientState;
|
||||
draft.serverState = initialState.serverState;
|
||||
draft.serverState = createDraft(initialState.serverState);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -175,6 +175,7 @@ export function reducer(state: State = initialState, action: { type: string; pay
|
||||
|
||||
draft.clientState.currentWorkflow.data.stages?.splice(sourceStageIndex + 1, 0, {
|
||||
...sourceStage,
|
||||
// @ts-expect-error - We are handling temporary (unsaved) duplicated stages with temporary keys and undefined ids. It should be revamp imo
|
||||
id: undefined,
|
||||
__temp_key__: getMaxTempKey(draft.clientState.currentWorkflow.data.stages),
|
||||
});
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { RootState } from '../../../../../../../admin/src/core/store/configure';
|
||||
import { Stage } from '../../../../../../../shared/contracts/review-workflows';
|
||||
|
||||
import { REDUX_NAMESPACE } from './constants';
|
||||
import { State, initialState } from './reducer';
|
||||
|
||||
type Store = {
|
||||
interface Store extends RootState {
|
||||
[REDUX_NAMESPACE]: State;
|
||||
};
|
||||
}
|
||||
|
||||
export const selectNamespace = (state: Store) => state[REDUX_NAMESPACE] ?? initialState;
|
||||
|
||||
@ -39,7 +40,7 @@ export const selectHasDeletedServerStages = createSelector(
|
||||
selectNamespace,
|
||||
({ serverState, clientState: { currentWorkflow } }) =>
|
||||
!(serverState.workflow?.stages ?? []).every(
|
||||
(stage: Stage) => !!currentWorkflow.data.stages?.find(({ id }) => id === stage.id)
|
||||
(stage: Partial<Stage>) => !!currentWorkflow.data.stages?.find(({ id }) => id === stage.id)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@ -12,9 +12,9 @@ import {
|
||||
ACTION_UPDATE_STAGE_POSITION,
|
||||
ACTION_UPDATE_WORKFLOW,
|
||||
} from '../constants';
|
||||
import { PartialWorkflow, State, initialState, reducer } from '../reducer';
|
||||
import { State, initialState, reducer } from '../reducer';
|
||||
|
||||
const WORKFLOW_FIXTURE: PartialWorkflow = {
|
||||
const WORKFLOW_FIXTURE = {
|
||||
id: 1,
|
||||
name: 'Workflow fixture',
|
||||
stages: [
|
||||
@ -22,11 +22,15 @@ const WORKFLOW_FIXTURE: PartialWorkflow = {
|
||||
id: 1,
|
||||
color: '#4945FF',
|
||||
name: 'stage-1',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
color: '#4945FF',
|
||||
name: 'stage-2',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -237,9 +241,12 @@ describe('Admin | Settings | Review Workflows | reducer', () => {
|
||||
stages: [
|
||||
...(WORKFLOW_FIXTURE.stages || []),
|
||||
{
|
||||
id: 4,
|
||||
__temp_key__: 4,
|
||||
color: '#4945FF',
|
||||
name: 'stage-temp',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -313,11 +320,17 @@ describe('Admin | Settings | Review Workflows | reducer', () => {
|
||||
{
|
||||
id: 1,
|
||||
name: 'stage-1',
|
||||
color: '#4945FF',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
|
||||
{
|
||||
id: 3,
|
||||
name: 'stage-2',
|
||||
color: '#4945FF',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -378,6 +391,8 @@ describe('Admin | Settings | Review Workflows | reducer', () => {
|
||||
id: 1,
|
||||
color: '#4945FF',
|
||||
name: 'stage-1-modified',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { lightTheme } from '@strapi/design-system';
|
||||
import { DefaultTheme } from 'styled-components';
|
||||
|
||||
import { STAGE_COLORS } from '../constants';
|
||||
|
||||
@ -34,7 +35,7 @@ export function getStageColorByHex(hex: string) {
|
||||
|
||||
export function getAvailableStageColors() {
|
||||
return Object.entries(STAGE_COLORS).map(([themeColorName, name]) => ({
|
||||
hex: lightTheme.colors[themeColorName as keyof typeof lightTheme.colors].toUpperCase(),
|
||||
hex: lightTheme.colors[themeColorName as keyof DefaultTheme['colors']].toUpperCase(),
|
||||
name,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -28,6 +28,5 @@ describe('Settings | Review Workflows | colors', () => {
|
||||
});
|
||||
|
||||
expect(getStageColorByHex('random')).toStrictEqual(null);
|
||||
expect(getStageColorByHex()).toStrictEqual(null);
|
||||
});
|
||||
});
|
||||
@ -1,10 +1,12 @@
|
||||
import { PartialWorkflow } from '../../reducer';
|
||||
import { validateWorkflow } from '../validateWorkflow';
|
||||
|
||||
const generateStringWithLength = (length) => new Array(length + 1).join('_');
|
||||
const generateStringWithLength = (length: number) => new Array(length + 1).join('_');
|
||||
|
||||
const formatMessage = (message) => message.defaultMessage;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const formatMessage = (message: any) => message.defaultMessage;
|
||||
|
||||
const setup = (values) => validateWorkflow({ values, formatMessage });
|
||||
const setup = (values: PartialWorkflow) => validateWorkflow({ values, formatMessage });
|
||||
|
||||
describe('Settings | Review Workflows | validateWorkflow()', () => {
|
||||
test('name: valid input', async () => {
|
||||
@ -154,35 +156,18 @@ describe('Settings | Review Workflows | validateWorkflow()', () => {
|
||||
{
|
||||
name: 'stage-1',
|
||||
color: '#ffffff',
|
||||
permissions: [{ role: 1, action: 'admin::review-workflow.stage.transition' }],
|
||||
permissions: [
|
||||
{
|
||||
id: 1,
|
||||
actionParameters: {},
|
||||
role: 1,
|
||||
action: 'admin::review-workflow.stage.transition',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual(true);
|
||||
|
||||
expect(
|
||||
await setup({
|
||||
name: 'name',
|
||||
stages: [
|
||||
{
|
||||
name: 'stage-1',
|
||||
color: '#ffffff',
|
||||
permissions: { role: '1', action: 'admin::review-workflow.stage.transition' },
|
||||
},
|
||||
],
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"stages": [
|
||||
{
|
||||
"permissions": "stages[0].permissions must be a \`array\` type, but the final value was: \`{
|
||||
"role": "\\"1\\"",
|
||||
"action": "\\"admin::review-workflow.stage.transition\\""
|
||||
}\`.",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('stages.permissions: undefined', async () => {
|
||||
@ -1,15 +1,14 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import set from 'lodash/set';
|
||||
import { IntlFormatters } from 'react-intl';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import { Stage } from '../../../../../../../../shared/contracts/review-workflows';
|
||||
import { CurrentWorkflow } from '../reducer';
|
||||
import { PartialWorkflow } from '../reducer';
|
||||
|
||||
export async function validateWorkflow({
|
||||
values,
|
||||
formatMessage,
|
||||
}: { values: CurrentWorkflow } & Pick<IntlFormatters, 'formatMessage'>) {
|
||||
}: { values: PartialWorkflow } & Pick<IntlFormatters, 'formatMessage'>) {
|
||||
const schema = yup.object({
|
||||
contentTypes: yup.array().of(yup.string()),
|
||||
name: yup
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"extends": "tsconfig/client.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "../",
|
||||
"rootDir": "../../",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@tests/*": ["../../admin/tests/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "tests", "custom.d.ts"]
|
||||
}
|
||||
"include": ["./src", "../../shared", "./tests", "./custom.d.ts", "./module.d.ts"]
|
||||
}
|
||||
@ -9,7 +9,7 @@ export interface StagePermission
|
||||
interface Stage extends Entity {
|
||||
color: string;
|
||||
name: string;
|
||||
permissions: StagePermission[];
|
||||
permissions?: StagePermission[];
|
||||
}
|
||||
|
||||
interface Workflow extends Entity {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user