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:
Jamie Howard 2024-08-27 12:04:59 +01:00 committed by GitHub
parent aac137c6a2
commit 69590e70d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 323 additions and 120 deletions

View File

@ -553,6 +553,7 @@
"app.utils.published": "Published",
"app.utils.ready-to-publish": "Ready to publish",
"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?",
"clearLabel": "Clear",
"coming.soon": "This content is currently under construction and will be back in a few weeks!",

View File

@ -18,7 +18,7 @@ import {
Menu,
ButtonProps,
} from '@strapi/design-system';
import { CrossCircle, More, WarningCircle } from '@strapi/icons';
import { Cross, More, WarningCircle } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useMatch, useNavigate } from 'react-router-dom';
import { styled, DefaultTheme } from 'styled-components';
@ -582,29 +582,29 @@ const PublishAction: DocumentActionComponent = ({
}, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
React.useEffect(() => {
if (documentId && !isListView) {
// We don't want to call count draft relations if in the list view. There is no
// 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();
if (!document || !document.documentId || isListView) {
return;
}
}, [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 =
(document?.[PUBLISHED_AT_ATTRIBUTE_NAME] ||
@ -909,7 +909,7 @@ const UnpublishAction: DocumentActionComponent = ({
id: 'app.utils.unpublish',
defaultMessage: 'Unpublish',
}),
icon: <StyledCrossCircle />,
icon: <Cross />,
onClick: async () => {
/**
* 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',
defaultMessage: 'Discard changes',
}),
icon: <StyledCrossCircle />,
icon: <Cross />,
position: ['panel', 'table-row'],
variant: 'danger',
dialog: {
@ -1077,16 +1077,6 @@ const DiscardAction: DocumentActionComponent = ({
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];
export { DocumentActions, DocumentActionsMenu, DocumentActionButton, DEFAULT_ACTIONS };

View File

@ -63,6 +63,7 @@
"components.SettingsViewWrapper.pluginHeader.description.list-settings": "Define the settings of the list view.",
"components.SettingsViewWrapper.pluginHeader.title": "Configure the view — {name}",
"bulk-publish.already-published": "Already Published",
"bulk-unpublish.already-unpublished": "Already Unpublished",
"bulk-publish.modified": "Ready to publish changes",
"components.TableDelete.delete": "Delete all",
"components.TableDelete.deleteSelected": "Delete selected",
@ -104,8 +105,8 @@
"containers.list.items": "{number} {number, plural, =0 {items} one {item} other {items}}",
"containers.list.table.row-actions": "Row actions",
"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.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.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.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.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.",

View File

@ -510,7 +510,9 @@ export default {
return ctx.forbidden();
}
const { locale } = await getDocumentLocaleAndStatus(body, model);
const { locale } = await getDocumentLocaleAndStatus(body, model, {
allowMultipleLocales: true,
});
const entityPromises = documentIds.map((documentId: any) =>
documentManager.findLocales(documentId, model, { locale, isPublished: true })

View File

@ -315,7 +315,6 @@ export declare namespace BulkPublish {
};
query: {
// If not provided, the default locale will be used
// Otherwise specify the locales to publish of the documents
locale?: string | string[] | null;
};
}
@ -340,7 +339,10 @@ export declare namespace BulkUnpublish {
body: {
documentIds: Modules.Documents.ID[];
};
query: {};
query: {
// If not provided, the default locale will be used
locale?: string | string[] | null;
};
}
export interface Params {

View File

@ -23,6 +23,7 @@ type Status = Modules.Documents.Params.PublicationStatus.Kind | 'modified';
interface EntryValidationTextProps {
status: Status;
validationErrors: FormErrors[string] | null;
action: 'bulk-publish' | 'bulk-unpublish';
}
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();
/**
@ -86,42 +91,67 @@ const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValida
);
}
if (status === 'published') {
return (
<Flex gap={2}>
<CheckCircle fill="success600" />
<Typography textColor="success600" fontWeight="bold">
{formatMessage({
const getStatusMessage = () => {
if (action === 'bulk-publish') {
if (status === 'published') {
return {
icon: <CheckCircle fill="success600" />,
text: formatMessage({
id: 'content-manager.bulk-publish.already-published',
defaultMessage: 'Already Published',
})}
</Typography>
</Flex>
);
}
if (status === 'modified') {
return (
<Flex gap={2}>
<ArrowsCounterClockwise fill="alternative600" />
<Typography>
{formatMessage({
}),
textColor: 'success600',
fontWeight: 'bold',
};
} else if (status === 'modified') {
return {
icon: <ArrowsCounterClockwise fill="alternative600" />,
text: formatMessage({
id: 'app.utils.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 (
<Flex gap={2}>
<CheckCircle fill="success600" />
<Typography>
{formatMessage({
id: 'app.utils.ready-to-publish',
defaultMessage: 'Ready to publish',
})}
{icon}
<Typography textColor={textColor} fontWeight={fontWeight}>
{text}
</Typography>
</Flex>
);
@ -145,14 +175,15 @@ interface BulkLocaleActionModalProps {
}[];
localesMetadata: Locale[];
validationErrors?: FormErrors;
action: 'bulk-publish' | 'bulk-unpublish';
}
const BulkLocaleActionModal = ({
headers,
rows,
localesMetadata,
validationErrors = {},
action,
}: BulkLocaleActionModalProps) => {
const { formatMessage } = useIntl();
@ -168,11 +199,11 @@ const BulkLocaleActionModal = ({
}, {});
const localesWithErrors = Object.keys(validationErrors);
const alreadyPublishedCount = selectedRows.filter(
const publishedCount = selectedRows.filter(
({ locale }) => currentStatusByLocale[locale] === 'published'
).length;
const readyToPublishCount = selectedRows.filter(
const draftCount = selectedRows.filter(
({ locale }) =>
(currentStatusByLocale[locale] === 'draft' ||
currentStatusByLocale[locale] === 'modified') &&
@ -180,17 +211,25 @@ const BulkLocaleActionModal = ({
).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(
{
id: 'content-manager.containers.list.selectedEntriesModal.selectedCount',
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.',
id: messageId,
defaultMessage,
},
{
withErrorsCount,
readyToPublishCount,
alreadyPublishedCount,
draftCount,
publishedCount,
b: BoldChunk,
}
);
@ -243,7 +282,7 @@ const BulkLocaleActionModal = ({
</Box>
</Table.Cell>
<Table.Cell>
<EntryValidationText validationErrors={error} status={status} />
<EntryValidationText validationErrors={error} status={status} action={action} />
</Table.Cell>
<Table.Cell>
<IconButton

View File

@ -11,12 +11,13 @@ import {
import {
type HeaderActionComponent,
type DocumentActionComponent,
type DocumentActionProps,
unstable_useDocument as useDocument,
unstable_useDocumentActions as useDocumentActions,
buildValidParams,
} from '@strapi/content-manager/strapi-admin';
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 { useIntl } from 'react-intl';
import { useNavigate } from 'react-router-dom';
@ -239,27 +240,34 @@ const DeleteLocaleAction: DocumentActionComponent = ({
};
};
/* -------------------------------------------------------------------------------------------------
* BulkPublishAction
* -----------------------------------------------------------------------------------------------*/
export type LocaleStatus = {
locale: string;
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,
documentId,
model,
collectionType,
}) => {
action,
}: ExtendedDocumentActionProps) => {
const baseLocale = baseDocument?.locale ?? null;
const [{ query }] = useQueryParams<{ status: 'draft' | 'published' }>();
const params = React.useMemo(() => buildValidParams(query), [query]);
const isPublishedTab = query.status === 'published';
const isOnPublishedTab = query.status === 'published';
const { formatMessage } = useIntl();
const { hasI18n, canPublish } = useI18n();
@ -270,7 +278,9 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] =
React.useState<boolean>(false);
const { publishMany: publishManyAction } = useDocumentActions();
const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } =
useDocumentActions();
const {
document,
meta: documentMeta,
@ -332,6 +342,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
return { locale, status };
});
rowsFromMeta.unshift({
locale: document.locale,
status: document.status,
@ -355,16 +366,26 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
return [rowsFromMeta, errors];
}, [document, documentMeta?.availableLocales, validate]);
const localesToPublish = selectedRows.reduce((acc, selectedRow) => {
if (
selectedRow.status !== 'published' &&
!Object.keys(validationErrors).includes(selectedRow.locale)
) {
const isBulkPublish = action === 'bulk-publish';
const localesForAction = selectedRows.reduce((acc: string[], selectedRow: LocaleStatus) => {
const isValidLocale =
// 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);
}
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 {
data: draftRelationsCount = 0,
isLoading: isDraftRelationsLoading,
@ -373,10 +394,10 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
{
model,
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],
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 () => {
if (draftRelationsCount > 0) {
setIsDraftRelationConfirmationOpen(true);
} else {
} else if (isBulkPublish) {
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) {
return {
label: formatMessage({
@ -480,18 +510,18 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
return {
label: formatMessage({
id: getTranslation('CMEditViewBulkLocale.publish-title'),
defaultMessage: 'Publish Multiple Locales',
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
}),
icon: <ListPlus />,
disabled: isPublishedTab || canPublish.length === 0,
variant: isBulkPublish ? 'secondary' : 'danger',
icon: isBulkPublish ? <ListPlus /> : <Cross />,
disabled: isOnPublishedTab || canPublish.length === 0,
position: ['panel'],
variant: 'secondary',
dialog: {
type: 'modal',
title: formatMessage({
id: getTranslation('CMEditViewBulkLocale.publish-title'),
defaultMessage: 'Publish Multiple Locales',
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
}),
content: () => {
return (
@ -509,6 +539,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
headers={headers}
rows={rows}
localesMetadata={localesMetadata as Locale[]}
action={action ?? 'bulk-publish'}
/>
</Table.Root>
);
@ -517,13 +548,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
<Modal.Footer justifyContent="flex-end">
<Button
loading={isDraftRelationsLoading}
disabled={!hasPermission || localesToPublish.length === 0}
disabled={!hasPermission || localesForAction.length === 0}
variant="default"
onClick={handleAction}
>
{formatMessage({
id: 'app.utils.publish',
defaultMessage: 'Publish',
id: isBulkPublish ? 'app.utils.publish' : 'app.utils.unpublish',
defaultMessage: isBulkPublish ? 'Publish' : 'Unpublish',
})}
</Button>
</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
* 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,
};

View File

@ -4,6 +4,7 @@ import * as yup from 'yup';
import { CheckboxConfirmation } from './components/CheckboxConfirmation';
import {
BulkLocalePublishAction,
BulkLocaleUnpublishAction,
DeleteLocaleAction,
LocalePickerAction,
} from './components/CMHeaderActions';
@ -79,6 +80,7 @@ export default {
// When enabled the bulk locale publish action should be the first action
// in 'More Document Actions' and therefore the third action in the array
actions.splice(2, 0, BulkLocalePublishAction);
actions.splice(5, 0, BulkLocaleUnpublishAction);
return actions;
});

View File

@ -8,6 +8,7 @@
"CMEditViewCopyLocale.copy-text": "Fill in from another locale",
"CMEditViewCopyLocale.submit-text": "Yes, fill in",
"CMEditViewBulkLocale.publish-title": "Publish Multiple Locales",
"CMEditViewBulkLocale.unpublish-title": "Unpublish Multiple Locales",
"CMEditViewBulkLocale.status": "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.",

View File

@ -137,7 +137,9 @@ test.describe('Edit View', () => {
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
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 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 page.getByRole('button', { name: 'More document actions' }).click();
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
await expect(
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
).not.toBeDisabled();
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
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 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 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 page.getByRole('button', { name: 'More document actions' }).click();
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
await expect(
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
).not.toBeDisabled();
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
await findAndClose(page, 'Unpublished Document');

View File

@ -327,6 +327,113 @@ test.describe('Edit view', () => {
).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 {
field: string;
initialValue: string;