mirror of
https://github.com/strapi/strapi.git
synced 2025-12-06 03:52:38 +00:00
feat(i18n): add bulk unpublish locales edit view action (#21030)
* feat(i18n): add bulk unpublish locales edit view action * fix(e2e): i18n bulk actions * chore(e2e): skip for webkit * chore: clean up * fix(i18n): fixes and e2e test coverage for bulk locale unpublish * fix(e2e): content manager edit view * fix(content-manager): incorporate edit view notification fix * chore(i18n): clean up
This commit is contained in:
parent
aac137c6a2
commit
69590e70d5
@ -553,6 +553,7 @@
|
|||||||
"app.utils.published": "Published",
|
"app.utils.published": "Published",
|
||||||
"app.utils.ready-to-publish": "Ready to publish",
|
"app.utils.ready-to-publish": "Ready to publish",
|
||||||
"app.utils.ready-to-publish-changes": "Ready to publish changes",
|
"app.utils.ready-to-publish-changes": "Ready to publish changes",
|
||||||
|
"app.utils.ready-to-unpublish-changes": "Ready to unpublish",
|
||||||
"app.confirm.body": "Are you sure?",
|
"app.confirm.body": "Are you sure?",
|
||||||
"clearLabel": "Clear",
|
"clearLabel": "Clear",
|
||||||
"coming.soon": "This content is currently under construction and will be back in a few weeks!",
|
"coming.soon": "This content is currently under construction and will be back in a few weeks!",
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { CrossCircle, More, WarningCircle } from '@strapi/icons';
|
import { Cross, More, WarningCircle } from '@strapi/icons';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useMatch, useNavigate } from 'react-router-dom';
|
import { useMatch, useNavigate } from 'react-router-dom';
|
||||||
import { styled, DefaultTheme } from 'styled-components';
|
import { styled, DefaultTheme } from 'styled-components';
|
||||||
@ -582,29 +582,29 @@ const PublishAction: DocumentActionComponent = ({
|
|||||||
}, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
|
}, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (documentId && !isListView) {
|
if (!document || !document.documentId || isListView) {
|
||||||
// We don't want to call count draft relations if in the list view. There is no
|
return;
|
||||||
// use for the response.
|
|
||||||
const fetchDraftRelationsCount = async () => {
|
|
||||||
const { data, error } = await countDraftRelations({
|
|
||||||
collectionType,
|
|
||||||
model,
|
|
||||||
documentId,
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
setServerCountOfDraftRelations(data.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchDraftRelationsCount();
|
|
||||||
}
|
}
|
||||||
}, [isListView, documentId, countDraftRelations, collectionType, model, params]);
|
|
||||||
|
const fetchDraftRelationsCount = async () => {
|
||||||
|
const { data, error } = await countDraftRelations({
|
||||||
|
collectionType,
|
||||||
|
model,
|
||||||
|
documentId,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
setServerCountOfDraftRelations(data.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDraftRelationsCount();
|
||||||
|
}, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
|
||||||
|
|
||||||
const isDocumentPublished =
|
const isDocumentPublished =
|
||||||
(document?.[PUBLISHED_AT_ATTRIBUTE_NAME] ||
|
(document?.[PUBLISHED_AT_ATTRIBUTE_NAME] ||
|
||||||
@ -909,7 +909,7 @@ const UnpublishAction: DocumentActionComponent = ({
|
|||||||
id: 'app.utils.unpublish',
|
id: 'app.utils.unpublish',
|
||||||
defaultMessage: 'Unpublish',
|
defaultMessage: 'Unpublish',
|
||||||
}),
|
}),
|
||||||
icon: <StyledCrossCircle />,
|
icon: <Cross />,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
/**
|
/**
|
||||||
* return if there's no id & we're in a collection type, or the status modified
|
* return if there's no id & we're in a collection type, or the status modified
|
||||||
@ -1043,7 +1043,7 @@ const DiscardAction: DocumentActionComponent = ({
|
|||||||
id: 'content-manager.actions.discard.label',
|
id: 'content-manager.actions.discard.label',
|
||||||
defaultMessage: 'Discard changes',
|
defaultMessage: 'Discard changes',
|
||||||
}),
|
}),
|
||||||
icon: <StyledCrossCircle />,
|
icon: <Cross />,
|
||||||
position: ['panel', 'table-row'],
|
position: ['panel', 'table-row'],
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
dialog: {
|
dialog: {
|
||||||
@ -1077,16 +1077,6 @@ const DiscardAction: DocumentActionComponent = ({
|
|||||||
|
|
||||||
DiscardAction.type = 'discard';
|
DiscardAction.type = 'discard';
|
||||||
|
|
||||||
/**
|
|
||||||
* Because the icon system is completely broken, we have to do
|
|
||||||
* this to remove the fill from the cog.
|
|
||||||
*/
|
|
||||||
const StyledCrossCircle = styled(CrossCircle)`
|
|
||||||
path {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DEFAULT_ACTIONS = [PublishAction, UpdateAction, UnpublishAction, DiscardAction];
|
const DEFAULT_ACTIONS = [PublishAction, UpdateAction, UnpublishAction, DiscardAction];
|
||||||
|
|
||||||
export { DocumentActions, DocumentActionsMenu, DocumentActionButton, DEFAULT_ACTIONS };
|
export { DocumentActions, DocumentActionsMenu, DocumentActionButton, DEFAULT_ACTIONS };
|
||||||
|
|||||||
@ -63,6 +63,7 @@
|
|||||||
"components.SettingsViewWrapper.pluginHeader.description.list-settings": "Define the settings of the list view.",
|
"components.SettingsViewWrapper.pluginHeader.description.list-settings": "Define the settings of the list view.",
|
||||||
"components.SettingsViewWrapper.pluginHeader.title": "Configure the view — {name}",
|
"components.SettingsViewWrapper.pluginHeader.title": "Configure the view — {name}",
|
||||||
"bulk-publish.already-published": "Already Published",
|
"bulk-publish.already-published": "Already Published",
|
||||||
|
"bulk-unpublish.already-unpublished": "Already Unpublished",
|
||||||
"bulk-publish.modified": "Ready to publish changes",
|
"bulk-publish.modified": "Ready to publish changes",
|
||||||
"components.TableDelete.delete": "Delete all",
|
"components.TableDelete.delete": "Delete all",
|
||||||
"components.TableDelete.deleteSelected": "Delete selected",
|
"components.TableDelete.deleteSelected": "Delete selected",
|
||||||
@ -104,8 +105,8 @@
|
|||||||
"containers.list.items": "{number} {number, plural, =0 {items} one {item} other {items}}",
|
"containers.list.items": "{number} {number, plural, =0 {items} one {item} other {items}}",
|
||||||
"containers.list.table.row-actions": "Row actions",
|
"containers.list.table.row-actions": "Row actions",
|
||||||
"containers.list.selectedEntriesModal.title": "Publish entries",
|
"containers.list.selectedEntriesModal.title": "Publish entries",
|
||||||
"containers.list.selectedEntriesModal.selectedCount": "<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
"containers.list.selectedEntriesModal.selectedCount.publish": "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
||||||
"containers.list.selectedEntriesModal.publishedCount": "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} published. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
"containers.list.selectedEntriesModal.selectedCount.unpublish": "<b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. <b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.",
|
||||||
"containers.list.autoCloneModal.header": "Duplicate",
|
"containers.list.autoCloneModal.header": "Duplicate",
|
||||||
"containers.list.autoCloneModal.title": "This entry can't be duplicated directly.",
|
"containers.list.autoCloneModal.title": "This entry can't be duplicated directly.",
|
||||||
"containers.list.autoCloneModal.description": "A new entry will be created with the same content, but you'll have to change the following fields to save it.",
|
"containers.list.autoCloneModal.description": "A new entry will be created with the same content, but you'll have to change the following fields to save it.",
|
||||||
|
|||||||
@ -510,7 +510,9 @@ export default {
|
|||||||
return ctx.forbidden();
|
return ctx.forbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { locale } = await getDocumentLocaleAndStatus(body, model);
|
const { locale } = await getDocumentLocaleAndStatus(body, model, {
|
||||||
|
allowMultipleLocales: true,
|
||||||
|
});
|
||||||
|
|
||||||
const entityPromises = documentIds.map((documentId: any) =>
|
const entityPromises = documentIds.map((documentId: any) =>
|
||||||
documentManager.findLocales(documentId, model, { locale, isPublished: true })
|
documentManager.findLocales(documentId, model, { locale, isPublished: true })
|
||||||
|
|||||||
@ -315,7 +315,6 @@ export declare namespace BulkPublish {
|
|||||||
};
|
};
|
||||||
query: {
|
query: {
|
||||||
// If not provided, the default locale will be used
|
// If not provided, the default locale will be used
|
||||||
// Otherwise specify the locales to publish of the documents
|
|
||||||
locale?: string | string[] | null;
|
locale?: string | string[] | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -340,7 +339,10 @@ export declare namespace BulkUnpublish {
|
|||||||
body: {
|
body: {
|
||||||
documentIds: Modules.Documents.ID[];
|
documentIds: Modules.Documents.ID[];
|
||||||
};
|
};
|
||||||
query: {};
|
query: {
|
||||||
|
// If not provided, the default locale will be used
|
||||||
|
locale?: string | string[] | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type Status = Modules.Documents.Params.PublicationStatus.Kind | 'modified';
|
|||||||
interface EntryValidationTextProps {
|
interface EntryValidationTextProps {
|
||||||
status: Status;
|
status: Status;
|
||||||
validationErrors: FormErrors[string] | null;
|
validationErrors: FormErrors[string] | null;
|
||||||
|
action: 'bulk-publish' | 'bulk-unpublish';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TranslationMessage extends MessageDescriptor {
|
interface TranslationMessage extends MessageDescriptor {
|
||||||
@ -35,7 +36,11 @@ const isErrorMessageDescriptor = (object?: string | object): object is Translati
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValidationTextProps) => {
|
const EntryValidationText = ({
|
||||||
|
status = 'draft',
|
||||||
|
validationErrors,
|
||||||
|
action,
|
||||||
|
}: EntryValidationTextProps) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,42 +91,67 @@ const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValida
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'published') {
|
const getStatusMessage = () => {
|
||||||
return (
|
if (action === 'bulk-publish') {
|
||||||
<Flex gap={2}>
|
if (status === 'published') {
|
||||||
<CheckCircle fill="success600" />
|
return {
|
||||||
<Typography textColor="success600" fontWeight="bold">
|
icon: <CheckCircle fill="success600" />,
|
||||||
{formatMessage({
|
text: formatMessage({
|
||||||
id: 'content-manager.bulk-publish.already-published',
|
id: 'content-manager.bulk-publish.already-published',
|
||||||
defaultMessage: 'Already Published',
|
defaultMessage: 'Already Published',
|
||||||
})}
|
}),
|
||||||
</Typography>
|
textColor: 'success600',
|
||||||
</Flex>
|
fontWeight: 'bold',
|
||||||
);
|
};
|
||||||
}
|
} else if (status === 'modified') {
|
||||||
|
return {
|
||||||
if (status === 'modified') {
|
icon: <ArrowsCounterClockwise fill="alternative600" />,
|
||||||
return (
|
text: formatMessage({
|
||||||
<Flex gap={2}>
|
|
||||||
<ArrowsCounterClockwise fill="alternative600" />
|
|
||||||
<Typography>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'app.utils.ready-to-publish-changes',
|
id: 'app.utils.ready-to-publish-changes',
|
||||||
defaultMessage: 'Ready to publish changes',
|
defaultMessage: 'Ready to publish changes',
|
||||||
})}
|
}),
|
||||||
</Typography>
|
};
|
||||||
</Flex>
|
} else {
|
||||||
);
|
return {
|
||||||
}
|
icon: <CheckCircle fill="success600" />,
|
||||||
|
text: formatMessage({
|
||||||
|
id: 'app.utils.ready-to-publish',
|
||||||
|
defaultMessage: 'Ready to publish',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (status === 'draft') {
|
||||||
|
return {
|
||||||
|
icon: <CheckCircle fill="success600" />,
|
||||||
|
text: formatMessage({
|
||||||
|
id: 'content-manager.bulk-unpublish.already-unpublished',
|
||||||
|
defaultMessage: 'Already Unpublished',
|
||||||
|
}),
|
||||||
|
textColor: 'success600',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
icon: <CheckCircle fill="success600" />,
|
||||||
|
text: formatMessage({
|
||||||
|
id: 'app.utils.ready-to-unpublish-changes',
|
||||||
|
defaultMessage: 'Ready to unpublish',
|
||||||
|
}),
|
||||||
|
textColor: 'success600',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { icon, text, textColor = 'success600', fontWeight = 'normal' } = getStatusMessage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
<CheckCircle fill="success600" />
|
{icon}
|
||||||
<Typography>
|
<Typography textColor={textColor} fontWeight={fontWeight}>
|
||||||
{formatMessage({
|
{text}
|
||||||
id: 'app.utils.ready-to-publish',
|
|
||||||
defaultMessage: 'Ready to publish',
|
|
||||||
})}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@ -145,14 +175,15 @@ interface BulkLocaleActionModalProps {
|
|||||||
}[];
|
}[];
|
||||||
localesMetadata: Locale[];
|
localesMetadata: Locale[];
|
||||||
validationErrors?: FormErrors;
|
validationErrors?: FormErrors;
|
||||||
|
action: 'bulk-publish' | 'bulk-unpublish';
|
||||||
}
|
}
|
||||||
|
|
||||||
const BulkLocaleActionModal = ({
|
const BulkLocaleActionModal = ({
|
||||||
headers,
|
headers,
|
||||||
rows,
|
rows,
|
||||||
localesMetadata,
|
localesMetadata,
|
||||||
|
|
||||||
validationErrors = {},
|
validationErrors = {},
|
||||||
|
action,
|
||||||
}: BulkLocaleActionModalProps) => {
|
}: BulkLocaleActionModalProps) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
@ -168,11 +199,11 @@ const BulkLocaleActionModal = ({
|
|||||||
}, {});
|
}, {});
|
||||||
const localesWithErrors = Object.keys(validationErrors);
|
const localesWithErrors = Object.keys(validationErrors);
|
||||||
|
|
||||||
const alreadyPublishedCount = selectedRows.filter(
|
const publishedCount = selectedRows.filter(
|
||||||
({ locale }) => currentStatusByLocale[locale] === 'published'
|
({ locale }) => currentStatusByLocale[locale] === 'published'
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const readyToPublishCount = selectedRows.filter(
|
const draftCount = selectedRows.filter(
|
||||||
({ locale }) =>
|
({ locale }) =>
|
||||||
(currentStatusByLocale[locale] === 'draft' ||
|
(currentStatusByLocale[locale] === 'draft' ||
|
||||||
currentStatusByLocale[locale] === 'modified') &&
|
currentStatusByLocale[locale] === 'modified') &&
|
||||||
@ -180,17 +211,25 @@ const BulkLocaleActionModal = ({
|
|||||||
).length;
|
).length;
|
||||||
|
|
||||||
const withErrorsCount = localesWithErrors.length;
|
const withErrorsCount = localesWithErrors.length;
|
||||||
|
const messageId =
|
||||||
|
action === 'bulk-publish'
|
||||||
|
? 'content-manager.containers.list.selectedEntriesModal.selectedCount.publish'
|
||||||
|
: 'content-manager.containers.list.selectedEntriesModal.selectedCount.unpublish';
|
||||||
|
|
||||||
|
const defaultMessage =
|
||||||
|
action === 'bulk-publish'
|
||||||
|
? '<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.'
|
||||||
|
: '<b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. <b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.';
|
||||||
|
|
||||||
return formatMessage(
|
return formatMessage(
|
||||||
{
|
{
|
||||||
id: 'content-manager.containers.list.selectedEntriesModal.selectedCount',
|
id: messageId,
|
||||||
defaultMessage:
|
defaultMessage,
|
||||||
'<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
withErrorsCount,
|
withErrorsCount,
|
||||||
readyToPublishCount,
|
draftCount,
|
||||||
alreadyPublishedCount,
|
publishedCount,
|
||||||
b: BoldChunk,
|
b: BoldChunk,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -243,7 +282,7 @@ const BulkLocaleActionModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<EntryValidationText validationErrors={error} status={status} />
|
<EntryValidationText validationErrors={error} status={status} action={action} />
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@ -11,12 +11,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
type HeaderActionComponent,
|
type HeaderActionComponent,
|
||||||
type DocumentActionComponent,
|
type DocumentActionComponent,
|
||||||
|
type DocumentActionProps,
|
||||||
unstable_useDocument as useDocument,
|
unstable_useDocument as useDocument,
|
||||||
unstable_useDocumentActions as useDocumentActions,
|
unstable_useDocumentActions as useDocumentActions,
|
||||||
buildValidParams,
|
buildValidParams,
|
||||||
} from '@strapi/content-manager/strapi-admin';
|
} from '@strapi/content-manager/strapi-admin';
|
||||||
import { Flex, Status, Typography, Button, Modal } from '@strapi/design-system';
|
import { Flex, Status, Typography, Button, Modal } from '@strapi/design-system';
|
||||||
import { WarningCircle, ListPlus, Trash } from '@strapi/icons';
|
import { WarningCircle, ListPlus, Trash, Cross } from '@strapi/icons';
|
||||||
import { Modules } from '@strapi/types';
|
import { Modules } from '@strapi/types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@ -239,27 +240,34 @@ const DeleteLocaleAction: DocumentActionComponent = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
|
||||||
* BulkPublishAction
|
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
export type LocaleStatus = {
|
export type LocaleStatus = {
|
||||||
locale: string;
|
locale: string;
|
||||||
status: Modules.Documents.Params.PublicationStatus.Kind | 'modified';
|
status: Modules.Documents.Params.PublicationStatus.Kind | 'modified';
|
||||||
};
|
};
|
||||||
|
|
||||||
const BulkLocalePublishAction: DocumentActionComponent = ({
|
interface ExtendedDocumentActionProps extends DocumentActionProps {
|
||||||
|
action?: 'bulk-publish' | 'bulk-unpublish';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* BulkLocaleAction
|
||||||
|
*
|
||||||
|
* This component is used to handle bulk publish and unpublish actions on locales.
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const BulkLocaleAction: DocumentActionComponent = ({
|
||||||
document: baseDocument,
|
document: baseDocument,
|
||||||
documentId,
|
documentId,
|
||||||
model,
|
model,
|
||||||
collectionType,
|
collectionType,
|
||||||
}) => {
|
action,
|
||||||
|
}: ExtendedDocumentActionProps) => {
|
||||||
const baseLocale = baseDocument?.locale ?? null;
|
const baseLocale = baseDocument?.locale ?? null;
|
||||||
|
|
||||||
const [{ query }] = useQueryParams<{ status: 'draft' | 'published' }>();
|
const [{ query }] = useQueryParams<{ status: 'draft' | 'published' }>();
|
||||||
|
|
||||||
const params = React.useMemo(() => buildValidParams(query), [query]);
|
const params = React.useMemo(() => buildValidParams(query), [query]);
|
||||||
const isPublishedTab = query.status === 'published';
|
const isOnPublishedTab = query.status === 'published';
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { hasI18n, canPublish } = useI18n();
|
const { hasI18n, canPublish } = useI18n();
|
||||||
@ -270,7 +278,9 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] =
|
const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] =
|
||||||
React.useState<boolean>(false);
|
React.useState<boolean>(false);
|
||||||
|
|
||||||
const { publishMany: publishManyAction } = useDocumentActions();
|
const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } =
|
||||||
|
useDocumentActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
document,
|
document,
|
||||||
meta: documentMeta,
|
meta: documentMeta,
|
||||||
@ -332,6 +342,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
|
|
||||||
return { locale, status };
|
return { locale, status };
|
||||||
});
|
});
|
||||||
|
|
||||||
rowsFromMeta.unshift({
|
rowsFromMeta.unshift({
|
||||||
locale: document.locale,
|
locale: document.locale,
|
||||||
status: document.status,
|
status: document.status,
|
||||||
@ -355,16 +366,26 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
return [rowsFromMeta, errors];
|
return [rowsFromMeta, errors];
|
||||||
}, [document, documentMeta?.availableLocales, validate]);
|
}, [document, documentMeta?.availableLocales, validate]);
|
||||||
|
|
||||||
const localesToPublish = selectedRows.reduce((acc, selectedRow) => {
|
const isBulkPublish = action === 'bulk-publish';
|
||||||
if (
|
const localesForAction = selectedRows.reduce((acc: string[], selectedRow: LocaleStatus) => {
|
||||||
selectedRow.status !== 'published' &&
|
const isValidLocale =
|
||||||
!Object.keys(validationErrors).includes(selectedRow.locale)
|
// Validation errors are irrelevant if we are trying to unpublish
|
||||||
) {
|
!isBulkPublish || !Object.keys(validationErrors).includes(selectedRow.locale);
|
||||||
|
|
||||||
|
const shouldAddLocale = isBulkPublish
|
||||||
|
? selectedRow.status !== 'published' && isValidLocale
|
||||||
|
: selectedRow.status !== 'draft' && isValidLocale;
|
||||||
|
|
||||||
|
if (shouldAddLocale) {
|
||||||
acc.push(selectedRow.locale);
|
acc.push(selectedRow.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// TODO skipping this for now as there is a bug with the draft relation count that will be worked on separately
|
||||||
|
// see https://www.notion.so/strapi/Count-draft-relations-56901b492efb45ab90d42fe975b32bd8?pvs=4
|
||||||
|
const enableDraftRelationsCount = false;
|
||||||
const {
|
const {
|
||||||
data: draftRelationsCount = 0,
|
data: draftRelationsCount = 0,
|
||||||
isLoading: isDraftRelationsLoading,
|
isLoading: isDraftRelationsLoading,
|
||||||
@ -373,10 +394,10 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
{
|
{
|
||||||
model,
|
model,
|
||||||
documentIds: [documentId!],
|
documentIds: [documentId!],
|
||||||
locale: localesToPublish,
|
locale: localesForAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
skip: !documentId || localesToPublish.length === 0,
|
skip: !enableDraftRelationsCount || !documentId || localesForAction.length === 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -410,7 +431,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
documentIds: [documentId],
|
documentIds: [documentId],
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
locale: localesToPublish,
|
locale: localesForAction,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedRows([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unpublish = async () => {
|
||||||
|
await unpublishManyAction({
|
||||||
|
model,
|
||||||
|
documentIds: [documentId],
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
locale: localesForAction,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -420,17 +454,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
const handleAction = async () => {
|
const handleAction = async () => {
|
||||||
if (draftRelationsCount > 0) {
|
if (draftRelationsCount > 0) {
|
||||||
setIsDraftRelationConfirmationOpen(true);
|
setIsDraftRelationConfirmationOpen(true);
|
||||||
} else {
|
} else if (isBulkPublish) {
|
||||||
await publish();
|
await publish();
|
||||||
|
} else {
|
||||||
|
await unpublish();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isUnpublish = document?.status === 'published';
|
|
||||||
if (isUnpublish) {
|
|
||||||
// TODO: For now we still proceed so we have the bulk locale publish action in all cases
|
|
||||||
console.warn(['I18N'], 'Bulk locale unpublish modal not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDraftRelationConfirmationOpen) {
|
if (isDraftRelationConfirmationOpen) {
|
||||||
return {
|
return {
|
||||||
label: formatMessage({
|
label: formatMessage({
|
||||||
@ -480,18 +510,18 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
label: formatMessage({
|
label: formatMessage({
|
||||||
id: getTranslation('CMEditViewBulkLocale.publish-title'),
|
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
|
||||||
defaultMessage: 'Publish Multiple Locales',
|
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
|
||||||
}),
|
}),
|
||||||
icon: <ListPlus />,
|
variant: isBulkPublish ? 'secondary' : 'danger',
|
||||||
disabled: isPublishedTab || canPublish.length === 0,
|
icon: isBulkPublish ? <ListPlus /> : <Cross />,
|
||||||
|
disabled: isOnPublishedTab || canPublish.length === 0,
|
||||||
position: ['panel'],
|
position: ['panel'],
|
||||||
variant: 'secondary',
|
|
||||||
dialog: {
|
dialog: {
|
||||||
type: 'modal',
|
type: 'modal',
|
||||||
title: formatMessage({
|
title: formatMessage({
|
||||||
id: getTranslation('CMEditViewBulkLocale.publish-title'),
|
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
|
||||||
defaultMessage: 'Publish Multiple Locales',
|
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
|
||||||
}),
|
}),
|
||||||
content: () => {
|
content: () => {
|
||||||
return (
|
return (
|
||||||
@ -509,6 +539,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
headers={headers}
|
headers={headers}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
localesMetadata={localesMetadata as Locale[]}
|
localesMetadata={localesMetadata as Locale[]}
|
||||||
|
action={action ?? 'bulk-publish'}
|
||||||
/>
|
/>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
);
|
);
|
||||||
@ -517,13 +548,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
<Modal.Footer justifyContent="flex-end">
|
<Modal.Footer justifyContent="flex-end">
|
||||||
<Button
|
<Button
|
||||||
loading={isDraftRelationsLoading}
|
loading={isDraftRelationsLoading}
|
||||||
disabled={!hasPermission || localesToPublish.length === 0}
|
disabled={!hasPermission || localesForAction.length === 0}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleAction}
|
onClick={handleAction}
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'app.utils.publish',
|
id: isBulkPublish ? 'app.utils.publish' : 'app.utils.unpublish',
|
||||||
defaultMessage: 'Publish',
|
defaultMessage: isBulkPublish ? 'Publish' : 'Unpublish',
|
||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
@ -532,6 +563,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* BulkLocalePublishAction
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
const BulkLocalePublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => {
|
||||||
|
return BulkLocaleAction({ action: 'bulk-publish', ...props });
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* BulkLocaleUnpublishAction
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
const BulkLocaleUnpublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => {
|
||||||
|
return BulkLocaleAction({ action: 'bulk-unpublish', ...props });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because the icon system is completely broken, we have to do
|
* Because the icon system is completely broken, we have to do
|
||||||
* this to remove the fill from the cog.
|
* this to remove the fill from the cog.
|
||||||
@ -542,4 +587,9 @@ const StyledTrash = styled(Trash)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { BulkLocalePublishAction, DeleteLocaleAction, LocalePickerAction };
|
export {
|
||||||
|
BulkLocalePublishAction,
|
||||||
|
BulkLocaleUnpublishAction,
|
||||||
|
DeleteLocaleAction,
|
||||||
|
LocalePickerAction,
|
||||||
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import * as yup from 'yup';
|
|||||||
import { CheckboxConfirmation } from './components/CheckboxConfirmation';
|
import { CheckboxConfirmation } from './components/CheckboxConfirmation';
|
||||||
import {
|
import {
|
||||||
BulkLocalePublishAction,
|
BulkLocalePublishAction,
|
||||||
|
BulkLocaleUnpublishAction,
|
||||||
DeleteLocaleAction,
|
DeleteLocaleAction,
|
||||||
LocalePickerAction,
|
LocalePickerAction,
|
||||||
} from './components/CMHeaderActions';
|
} from './components/CMHeaderActions';
|
||||||
@ -79,6 +80,7 @@ export default {
|
|||||||
// When enabled the bulk locale publish action should be the first action
|
// When enabled the bulk locale publish action should be the first action
|
||||||
// in 'More Document Actions' and therefore the third action in the array
|
// in 'More Document Actions' and therefore the third action in the array
|
||||||
actions.splice(2, 0, BulkLocalePublishAction);
|
actions.splice(2, 0, BulkLocalePublishAction);
|
||||||
|
actions.splice(5, 0, BulkLocaleUnpublishAction);
|
||||||
return actions;
|
return actions;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"CMEditViewCopyLocale.copy-text": "Fill in from another locale",
|
"CMEditViewCopyLocale.copy-text": "Fill in from another locale",
|
||||||
"CMEditViewCopyLocale.submit-text": "Yes, fill in",
|
"CMEditViewCopyLocale.submit-text": "Yes, fill in",
|
||||||
"CMEditViewBulkLocale.publish-title": "Publish Multiple Locales",
|
"CMEditViewBulkLocale.publish-title": "Publish Multiple Locales",
|
||||||
|
"CMEditViewBulkLocale.unpublish-title": "Unpublish Multiple Locales",
|
||||||
"CMEditViewBulkLocale.status": "Status",
|
"CMEditViewBulkLocale.status": "Status",
|
||||||
"CMEditViewBulkLocale.publication-status": "Publication Status",
|
"CMEditViewBulkLocale.publication-status": "Publication Status",
|
||||||
"CMEditViewBulkLocale.draft-relation-warning": "Some locales are related to draft entries. Publishing them could leave broken links in your app.",
|
"CMEditViewBulkLocale.draft-relation-warning": "Some locales are related to draft entries. Publishing them could leave broken links in your app.",
|
||||||
|
|||||||
@ -137,7 +137,9 @@ test.describe('Edit View', () => {
|
|||||||
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||||
|
).not.toBeDisabled();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
||||||
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
||||||
|
|
||||||
@ -330,8 +332,10 @@ test.describe('Edit View', () => {
|
|||||||
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
await expect(
|
||||||
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
|
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||||
|
).not.toBeDisabled();
|
||||||
|
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
|
||||||
|
|
||||||
await findAndClose(page, 'Unpublished Document');
|
await findAndClose(page, 'Unpublished Document');
|
||||||
|
|
||||||
@ -483,7 +487,9 @@ test.describe('Edit View', () => {
|
|||||||
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||||
|
).not.toBeDisabled();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
||||||
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
||||||
|
|
||||||
@ -656,8 +662,10 @@ test.describe('Edit View', () => {
|
|||||||
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
await expect(
|
||||||
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
|
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||||
|
).not.toBeDisabled();
|
||||||
|
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
|
||||||
|
|
||||||
await findAndClose(page, 'Unpublished Document');
|
await findAndClose(page, 'Unpublished Document');
|
||||||
|
|
||||||
|
|||||||
@ -327,6 +327,113 @@ test.describe('Edit view', () => {
|
|||||||
).toBeDisabled();
|
).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('As a user I want to unpublish multiple locales of my document', async ({
|
||||||
|
page,
|
||||||
|
browser,
|
||||||
|
}) => {
|
||||||
|
if (browser.browserType().name() === 'webkit') {
|
||||||
|
// See DX-1550
|
||||||
|
return test.fixme();
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||||
|
const EDIT_URL =
|
||||||
|
/\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to our articles list-view where there will be one document already made in the `en` locale
|
||||||
|
*/
|
||||||
|
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Article' }).click();
|
||||||
|
await page.waitForURL(LIST_URL);
|
||||||
|
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert we're on the english locale and our document exists
|
||||||
|
*/
|
||||||
|
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||||
|
'English (en)'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('row', { name: 'Why I prefer football over soccer' })
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole('row', { name: 'Why I prefer football over soccer' }).click();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new spanish draft article
|
||||||
|
*/
|
||||||
|
await page.waitForURL(EDIT_URL);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Why I prefer football over soccer' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish the english article
|
||||||
|
*/
|
||||||
|
await page.getByRole('button', { name: 'Publish' }).click();
|
||||||
|
await findAndClose(page, 'Success:Published');
|
||||||
|
|
||||||
|
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||||
|
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now we should be on a new document in the `es` locale
|
||||||
|
*/
|
||||||
|
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is here because the `fill` method below doesn't immediately update the value
|
||||||
|
* in webkit.
|
||||||
|
*/
|
||||||
|
if (browser.browserType().name() === 'webkit') {
|
||||||
|
await page.getByRole('textbox', { name: 'title' }).press('s');
|
||||||
|
await page.getByRole('textbox', { name: 'title' }).press('Delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('textbox', { name: 'title' }).fill('Por qué prefiero el fútbol al fútbol');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the spanish draft
|
||||||
|
*/
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await findAndClose(page, 'Success:Saved');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish the spanish article
|
||||||
|
*/
|
||||||
|
await page.getByRole('button', { name: 'Publish' }).click();
|
||||||
|
await findAndClose(page, 'Success:Published');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the bulk locale unpublish modal
|
||||||
|
*/
|
||||||
|
await page.getByText('More document actions').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Unpublish Multiple Locales', exact: true }).click();
|
||||||
|
|
||||||
|
// Select all locales, assert there are 2 entries ready to unpublish and unpublish them
|
||||||
|
await page
|
||||||
|
.getByRole('row', { name: 'Select all entries Name' })
|
||||||
|
.getByLabel('Select all entries')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpublish the articles
|
||||||
|
*/
|
||||||
|
await expect(page.getByText('2 entries ready to unpublish')).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByLabel('Unpublish Multiple Locales')
|
||||||
|
.getByRole('button', { name: 'Unpublish' })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Assert that all locales are now unpublished
|
||||||
|
await expect(page.getByRole('gridcell', { name: 'Draft' })).toHaveCount(2);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByLabel('Unpublish Multiple Locales').getByRole('button', { name: 'Unpublish' })
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
interface ValidationType {
|
interface ValidationType {
|
||||||
field: string;
|
field: string;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user