From b6b02ac97c4dce9fed8fd1f51bbd68ef418f5a4d Mon Sep 17 00:00:00 2001 From: Tobias Odendahl Date: Fri, 2 May 2025 15:21:02 +0200 Subject: [PATCH] fix(ui): fix version list status for unpublished documents (#11983) ### What? Fixes the label for documents which were the current published document but got unpublished in the version view. ### Why? If the most recent published document was unpublished, it remained displayed as "Currently published version" in the version list. ### How? Checks whether the document has a currently published version instead of only looking at the latest published version when determining the label in the versions view. Fixes https://github.com/payloadcms/payload/issues/10838 --------- Co-authored-by: Alessio Gravili Co-authored-by: Dan Ribbens --- .../views/Version/SelectComparison/index.tsx | 16 ++- packages/next/src/views/Versions/index.tsx | 33 +++++- test/versions/e2e.spec.ts | 110 ++++++++++++++++++ 3 files changed, 151 insertions(+), 8 deletions(-) diff --git a/packages/next/src/views/Version/SelectComparison/index.tsx b/packages/next/src/views/Version/SelectComparison/index.tsx index 82f8ef2958..37c50742e1 100644 --- a/packages/next/src/views/Version/SelectComparison/index.tsx +++ b/packages/next/src/views/Version/SelectComparison/index.tsx @@ -2,7 +2,14 @@ import type { PaginatedDocs, Where } from 'payload' -import { fieldBaseClass, Pill, ReactSelect, useConfig, useTranslation } from '@payloadcms/ui' +import { + fieldBaseClass, + Pill, + ReactSelect, + useConfig, + useDocumentInfo, + useTranslation, +} from '@payloadcms/ui' import { formatDate } from '@payloadcms/ui/shared' import { stringify } from 'qs-esm' import React, { useCallback, useEffect, useState } from 'react' @@ -37,6 +44,8 @@ export const SelectComparison: React.FC = (props) => { }, } = useConfig() + const { hasPublishedDoc } = useDocumentInfo() + const [options, setOptions] = useState< { label: React.ReactNode | string @@ -109,7 +118,10 @@ export const SelectComparison: React.FC = (props) => { }, published: { currentLabel: t('version:currentPublishedVersion'), - latestVersion: latestPublishedVersion, + // The latest published version does not necessarily equal the current published version, + // because the latest published version might have been unpublished in the meantime. + // Hence, we should only use the latest published version if there is a published document. + latestVersion: hasPublishedDoc ? latestPublishedVersion : undefined, pillStyle: 'success', previousLabel: t('version:previouslyPublished'), }, diff --git a/packages/next/src/views/Versions/index.tsx b/packages/next/src/views/Versions/index.tsx index 54e05e0088..744ddfd08b 100644 --- a/packages/next/src/views/Versions/index.tsx +++ b/packages/next/src/views/Versions/index.tsx @@ -85,13 +85,34 @@ export async function VersionsView(props: DocumentViewServerProps) { payload, status: 'draft', }) - latestPublishedVersion = await getLatestVersion({ - slug: collectionSlug, - type: 'collection', - parentID: id, - payload, - status: 'published', + const publishedDoc = await payload.count({ + collection: collectionSlug, + depth: 0, + overrideAccess: true, + req, + where: { + id: { + equals: id, + }, + _status: { + equals: 'published', + }, + }, }) + + // If we pass a latestPublishedVersion to buildVersionColumns, + // this will be used to display it as the "current published version". + // However, the latest published version might have been unpublished in the meantime. + // Hence, we should only pass the latest published version if there is a published document. + latestPublishedVersion = + publishedDoc.totalDocs > 0 && + (await getLatestVersion({ + slug: collectionSlug, + type: 'collection', + parentID: id, + payload, + status: 'published', + })) } } catch (err) { logError({ err, payload }) diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 2798fb52cd..f22e7fd5e1 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -205,6 +205,116 @@ describe('Versions', () => { await expect(page.locator('#field-title')).toHaveValue('v1') }) + test('should show currently published version status in versions view', async () => { + const publishedDoc = await payload.create({ + collection: draftCollectionSlug, + data: { + _status: 'published', + title: 'title', + description: 'description', + }, + overrideAccess: true, + }) + + await page.goto(`${url.edit(publishedDoc.id)}/versions`) + await expect(page.locator('main.versions')).toContainText('Current Published Version') + }) + + test('should show unpublished version status in versions view', async () => { + const publishedDoc = await payload.create({ + collection: draftCollectionSlug, + data: { + _status: 'published', + title: 'title', + description: 'description', + }, + overrideAccess: true, + }) + + // Unpublish the document + await payload.update({ + collection: draftCollectionSlug, + id: publishedDoc.id, + data: { + _status: 'draft', + }, + draft: false, + }) + + await page.goto(`${url.edit(publishedDoc.id)}/versions`) + await expect(page.locator('main.versions')).toContainText('Previously Published') + }) + + test('should show global versions view level action in globals versions view', async () => { + const global = new AdminUrlUtil(serverURL, draftGlobalSlug) + await page.goto(`${global.global(draftGlobalSlug)}/versions`) + await expect(page.locator('.app-header .global-versions-button')).toHaveCount(1) + }) + + test('global — has versions tab', async () => { + const global = new AdminUrlUtil(serverURL, draftGlobalSlug) + await page.goto(global.global(draftGlobalSlug)) + + const docURL = page.url() + const pathname = new URL(docURL).pathname + + const versionsTab = page.locator('.doc-tab', { + hasText: 'Versions', + }) + await versionsTab.waitFor({ state: 'visible' }) + + expect(versionsTab).toBeTruthy() + const href = versionsTab.locator('a').first() + await expect(href).toHaveAttribute('href', `${pathname}/versions`) + }) + + test('global — respects max number of versions', async () => { + await payload.updateGlobal({ + slug: draftWithMaxGlobalSlug, + data: { + title: 'initial title', + }, + }) + + const global = new AdminUrlUtil(serverURL, draftWithMaxGlobalSlug) + await page.goto(global.global(draftWithMaxGlobalSlug)) + + const titleFieldInitial = page.locator('#field-title') + await titleFieldInitial.fill('updated title') + await saveDocAndAssert(page, '#action-save-draft') + await expect(titleFieldInitial).toHaveValue('updated title') + + const versionsTab = page.locator('.doc-tab', { + hasText: '1', + }) + + await versionsTab.waitFor({ state: 'visible' }) + + expect(versionsTab).toBeTruthy() + + const titleFieldUpdated = page.locator('#field-title') + await titleFieldUpdated.fill('latest title') + await saveDocAndAssert(page, '#action-save-draft') + await expect(titleFieldUpdated).toHaveValue('latest title') + + const versionsTabUpdated = page.locator('.doc-tab', { + hasText: '1', + }) + + await versionsTabUpdated.waitFor({ state: 'visible' }) + + expect(versionsTabUpdated).toBeTruthy() + }) + + test('global — has versions route', async () => { + const global = new AdminUrlUtil(serverURL, autoSaveGlobalSlug) + const versionsURL = `${global.global(autoSaveGlobalSlug)}/versions` + await page.goto(versionsURL) + await expect(() => { + expect(page.url()).toMatch(/\/versions/) + }).toPass({ timeout: 10000, intervals: [100] }) + }) + test('collection - should autosave', async () => { await page.goto(autosaveURL.create) await page.locator('#field-title').fill('autosave title')