feat(ui): display the actual error message on unpublish if available (#11898)
### What? If an error occurs while unpublishing a document in the edit view UI, the toast which shows the error message now displays the actual message which is sent from the server, if available. ### Why? Only a generic error message was shown if an unpublish operation failed. Some errors might be solvable by the user, so that there is value in showing the actual, actionable error message instead of a generic one. ### How? The server response is parsed for error message if an unpublish operation fails and displayed in the toast, instead of the generic error message. 
This commit is contained in:
@@ -117,7 +117,19 @@ export const Status: React.FC = () => {
|
|||||||
setUnpublishedVersionCount(0)
|
setUnpublishedVersionCount(0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('error:unPublishingDocument'))
|
try {
|
||||||
|
const json = await res.json()
|
||||||
|
if (json.errors?.[0]?.message) {
|
||||||
|
toast.error(json.errors[0].message)
|
||||||
|
} else if (json.error) {
|
||||||
|
toast.error(json.error)
|
||||||
|
} else {
|
||||||
|
toast.error(t('error:unPublishingDocument'))
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(t('error:unPublishingDocument'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -154,6 +166,7 @@ export const Status: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
buttonStyle="none"
|
buttonStyle="none"
|
||||||
className={`${baseClass}__action`}
|
className={`${baseClass}__action`}
|
||||||
|
id={`action-unpublish`}
|
||||||
onClick={() => toggleModal(unPublishModalSlug)}
|
onClick={() => toggleModal(unPublishModalSlug)}
|
||||||
>
|
>
|
||||||
{t('version:unpublish')}
|
{t('version:unpublish')}
|
||||||
|
|||||||
33
test/versions/collections/ErrorOnUnpublish.ts
Normal file
33
test/versions/collections/ErrorOnUnpublish.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { APIError } from 'payload'
|
||||||
|
|
||||||
|
import { errorOnUnpublishSlug } from '../slugs.js'
|
||||||
|
|
||||||
|
const ErrorOnUnpublish: CollectionConfig = {
|
||||||
|
slug: errorOnUnpublishSlug,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'title',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
versions: {
|
||||||
|
drafts: true,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [
|
||||||
|
({ data, originalDoc }) => {
|
||||||
|
if (data?._status === 'draft' && originalDoc?._status === 'published') {
|
||||||
|
throw new APIError('Custom error on unpublish', 400, {}, true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorOnUnpublish
|
||||||
@@ -12,6 +12,7 @@ import DisablePublish from './collections/DisablePublish.js'
|
|||||||
import DraftPosts from './collections/Drafts.js'
|
import DraftPosts from './collections/Drafts.js'
|
||||||
import DraftWithMax from './collections/DraftsWithMax.js'
|
import DraftWithMax from './collections/DraftsWithMax.js'
|
||||||
import DraftsWithValidate from './collections/DraftsWithValidate.js'
|
import DraftsWithValidate from './collections/DraftsWithValidate.js'
|
||||||
|
import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js'
|
||||||
import LocalizedPosts from './collections/Localized.js'
|
import LocalizedPosts from './collections/Localized.js'
|
||||||
import { Media } from './collections/Media.js'
|
import { Media } from './collections/Media.js'
|
||||||
import Posts from './collections/Posts.js'
|
import Posts from './collections/Posts.js'
|
||||||
@@ -42,6 +43,7 @@ export default buildConfigWithDefaults({
|
|||||||
DraftPosts,
|
DraftPosts,
|
||||||
DraftWithMax,
|
DraftWithMax,
|
||||||
DraftsWithValidate,
|
DraftsWithValidate,
|
||||||
|
ErrorOnUnpublish,
|
||||||
LocalizedPosts,
|
LocalizedPosts,
|
||||||
VersionPosts,
|
VersionPosts,
|
||||||
CustomIDs,
|
CustomIDs,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import {
|
|||||||
draftWithMaxCollectionSlug,
|
draftWithMaxCollectionSlug,
|
||||||
draftWithMaxGlobalSlug,
|
draftWithMaxGlobalSlug,
|
||||||
draftWithValidateCollectionSlug,
|
draftWithValidateCollectionSlug,
|
||||||
|
errorOnUnpublishSlug,
|
||||||
localizedCollectionSlug,
|
localizedCollectionSlug,
|
||||||
localizedGlobalSlug,
|
localizedGlobalSlug,
|
||||||
postCollectionSlug,
|
postCollectionSlug,
|
||||||
@@ -86,6 +87,7 @@ describe('Versions', () => {
|
|||||||
let disablePublishURL: AdminUrlUtil
|
let disablePublishURL: AdminUrlUtil
|
||||||
let customIDURL: AdminUrlUtil
|
let customIDURL: AdminUrlUtil
|
||||||
let postURL: AdminUrlUtil
|
let postURL: AdminUrlUtil
|
||||||
|
let errorOnUnpublishURL: AdminUrlUtil
|
||||||
let id: string
|
let id: string
|
||||||
|
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
@@ -124,6 +126,7 @@ describe('Versions', () => {
|
|||||||
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
|
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
|
||||||
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
|
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
|
||||||
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
|
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
|
||||||
|
errorOnUnpublishURL = new AdminUrlUtil(serverURL, errorOnUnpublishSlug)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection — has versions tab', async () => {
|
test('collection — has versions tab', async () => {
|
||||||
@@ -579,6 +582,22 @@ describe('Versions', () => {
|
|||||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('collections — should show custom error message when unpublishing fails', async () => {
|
||||||
|
const publishedDoc = await payload.create({
|
||||||
|
collection: errorOnUnpublishSlug,
|
||||||
|
data: {
|
||||||
|
_status: 'published',
|
||||||
|
title: 'title',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.goto(errorOnUnpublishURL.edit(String(publishedDoc.id)))
|
||||||
|
await page.locator('#action-unpublish').click()
|
||||||
|
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
|
||||||
|
await expect(
|
||||||
|
page.locator('.payload-toast-item:has-text("Custom error on unpublish")'),
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('should show documents title in relationship even if draft document', async () => {
|
test('should show documents title in relationship even if draft document', async () => {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: autosaveCollectionSlug,
|
collection: autosaveCollectionSlug,
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export interface Config {
|
|||||||
'draft-posts': DraftPost;
|
'draft-posts': DraftPost;
|
||||||
'draft-with-max-posts': DraftWithMaxPost;
|
'draft-with-max-posts': DraftWithMaxPost;
|
||||||
'draft-with-validate-posts': DraftWithValidatePost;
|
'draft-with-validate-posts': DraftWithValidatePost;
|
||||||
|
'error-on-unpublish': ErrorOnUnpublish;
|
||||||
'localized-posts': LocalizedPost;
|
'localized-posts': LocalizedPost;
|
||||||
'version-posts': VersionPost;
|
'version-posts': VersionPost;
|
||||||
'custom-ids': CustomId;
|
'custom-ids': CustomId;
|
||||||
@@ -97,6 +98,7 @@ export interface Config {
|
|||||||
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
|
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
|
||||||
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
|
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
|
||||||
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
|
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
|
||||||
|
'error-on-unpublish': ErrorOnUnpublishSelect<false> | ErrorOnUnpublishSelect<true>;
|
||||||
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
||||||
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
|
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
|
||||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||||
@@ -289,6 +291,17 @@ export interface DraftWithValidatePost {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "error-on-unpublish".
|
||||||
|
*/
|
||||||
|
export interface ErrorOnUnpublish {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: ('draft' | 'published') | null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "localized-posts".
|
* via the `definition` "localized-posts".
|
||||||
@@ -589,6 +602,10 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'draft-with-validate-posts';
|
relationTo: 'draft-with-validate-posts';
|
||||||
value: string | DraftWithValidatePost;
|
value: string | DraftWithValidatePost;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'error-on-unpublish';
|
||||||
|
value: string | ErrorOnUnpublish;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'localized-posts';
|
relationTo: 'localized-posts';
|
||||||
value: string | LocalizedPost;
|
value: string | LocalizedPost;
|
||||||
@@ -778,6 +795,16 @@ export interface DraftWithValidatePostsSelect<T extends boolean = true> {
|
|||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
_status?: T;
|
_status?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "error-on-unpublish_select".
|
||||||
|
*/
|
||||||
|
export interface ErrorOnUnpublishSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
_status?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "localized-posts_select".
|
* via the `definition` "localized-posts_select".
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const mediaCollectionSlug = 'media'
|
|||||||
export const versionCollectionSlug = 'version-posts'
|
export const versionCollectionSlug = 'version-posts'
|
||||||
|
|
||||||
export const disablePublishSlug = 'disable-publish'
|
export const disablePublishSlug = 'disable-publish'
|
||||||
|
export const errorOnUnpublishSlug = 'error-on-unpublish'
|
||||||
|
|
||||||
export const disablePublishGlobalSlug = 'disable-publish-global'
|
export const disablePublishGlobalSlug = 'disable-publish-global'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user