fix(next): prevent errors in globals version view (#12920)
### What? This PR fixes a runtime error that occurs when opening the "More versions..." drawer while browsing the versions for a global. It also fixes a minor runtime error when navigating to a global version view where an optional chaining operator was missing as the collection variable would be undefined as we are viewing a global. This PR also adds an e2e test to ensure the versions drawer is accessible and renders the appropriate number of versions for globals. ### Why? To properly render global version views without errors. ### How? By threading the global slug to the versions drawer and adjusting some properties of the `renderDocument` server function call there. This PR also adds an optional chaining operator the `versionUseAsTitle` in the original view to prevent an error in globals. Notes: - This was brought to my attention in Discord by a handful of users Before: (Missing optional chaining error) [error1-verions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/3dc4dbe4-ee5a-43df-8d25-05128b05e063) Before: (Versions drawer error) [error2-versions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/98c3e1da-cb0b-4a36-bafd-240f641e8814) After: [versions-globals-Dashboard---Payload.webm](https://github.com/user-attachments/assets/c778d3f0-a8fe-4e31-92cb-62da8e6d8cb4)
This commit is contained in:
@@ -238,6 +238,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
|||||||
<SelectComparison
|
<SelectComparison
|
||||||
collectionSlug={collectionSlug}
|
collectionSlug={collectionSlug}
|
||||||
docID={originalDocID}
|
docID={originalDocID}
|
||||||
|
globalSlug={globalSlug}
|
||||||
onChange={onChangeVersionFrom}
|
onChange={onChangeVersionFrom}
|
||||||
versionFromID={versionFromID}
|
versionFromID={versionFromID}
|
||||||
versionFromOptions={versionFromOptions}
|
versionFromOptions={versionFromOptions}
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ export const formatVersionDrawerSlug = ({
|
|||||||
}) => `version-drawer_${depth}_${uuid}`
|
}) => `version-drawer_${depth}_${uuid}`
|
||||||
|
|
||||||
export const VersionDrawerContent: React.FC<{
|
export const VersionDrawerContent: React.FC<{
|
||||||
collectionSlug: string
|
collectionSlug?: string
|
||||||
docID: number | string
|
docID?: number | string
|
||||||
drawerSlug: string
|
drawerSlug: string
|
||||||
|
globalSlug?: string
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { collectionSlug, docID, drawerSlug } = props
|
const { collectionSlug, docID, drawerSlug, globalSlug } = props
|
||||||
const { closeModal } = useModal()
|
const { closeModal } = useModal()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const prevSearchParams = useRef(searchParams)
|
const prevSearchParams = useRef(searchParams)
|
||||||
@@ -46,12 +47,20 @@ export const VersionDrawerContent: React.FC<{
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const isGlobal = Boolean(globalSlug)
|
||||||
|
const entitySlug = collectionSlug ?? globalSlug
|
||||||
|
|
||||||
const result = await renderDocument({
|
const result = await renderDocument({
|
||||||
collectionSlug,
|
collectionSlug: entitySlug,
|
||||||
docID,
|
docID,
|
||||||
drawerSlug,
|
drawerSlug,
|
||||||
paramsOverride: {
|
paramsOverride: {
|
||||||
segments: ['collections', collectionSlug, String(docID), 'versions'],
|
segments: [
|
||||||
|
isGlobal ? 'globals' : 'collections',
|
||||||
|
entitySlug,
|
||||||
|
isGlobal ? undefined : String(docID),
|
||||||
|
'versions',
|
||||||
|
].filter(Boolean),
|
||||||
},
|
},
|
||||||
redirectAfterDelete: false,
|
redirectAfterDelete: false,
|
||||||
redirectAfterDuplicate: false,
|
redirectAfterDuplicate: false,
|
||||||
@@ -75,7 +84,7 @@ export const VersionDrawerContent: React.FC<{
|
|||||||
|
|
||||||
void fetchDocumentView()
|
void fetchDocumentView()
|
||||||
},
|
},
|
||||||
[closeModal, collectionSlug, drawerSlug, renderDocument, searchParams, t],
|
[closeModal, collectionSlug, globalSlug, drawerSlug, renderDocument, searchParams, t],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -93,11 +102,12 @@ export const VersionDrawerContent: React.FC<{
|
|||||||
return DocumentView
|
return DocumentView
|
||||||
}
|
}
|
||||||
export const VersionDrawer: React.FC<{
|
export const VersionDrawer: React.FC<{
|
||||||
collectionSlug: string
|
collectionSlug?: string
|
||||||
docID: number | string
|
docID?: number | string
|
||||||
drawerSlug: string
|
drawerSlug: string
|
||||||
|
globalSlug?: string
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { collectionSlug, docID, drawerSlug } = props
|
const { collectionSlug, docID, drawerSlug, globalSlug } = props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +117,12 @@ export const VersionDrawer: React.FC<{
|
|||||||
slug={drawerSlug}
|
slug={drawerSlug}
|
||||||
title={t('version:selectVersionToCompare')}
|
title={t('version:selectVersionToCompare')}
|
||||||
>
|
>
|
||||||
<VersionDrawerContent collectionSlug={collectionSlug} docID={docID} drawerSlug={drawerSlug} />
|
<VersionDrawerContent
|
||||||
|
collectionSlug={collectionSlug}
|
||||||
|
docID={docID}
|
||||||
|
drawerSlug={drawerSlug}
|
||||||
|
globalSlug={globalSlug}
|
||||||
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -115,9 +130,11 @@ export const VersionDrawer: React.FC<{
|
|||||||
export const useVersionDrawer = ({
|
export const useVersionDrawer = ({
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
docID,
|
docID,
|
||||||
|
globalSlug,
|
||||||
}: {
|
}: {
|
||||||
collectionSlug: string
|
collectionSlug?: string
|
||||||
docID: number | string
|
docID?: number | string
|
||||||
|
globalSlug?: string
|
||||||
}) => {
|
}) => {
|
||||||
const drawerDepth = useEditDepth()
|
const drawerDepth = useEditDepth()
|
||||||
const uuid = useId()
|
const uuid = useId()
|
||||||
@@ -147,9 +164,14 @@ export const useVersionDrawer = ({
|
|||||||
|
|
||||||
const MemoizedDrawer = useMemo(() => {
|
const MemoizedDrawer = useMemo(() => {
|
||||||
return () => (
|
return () => (
|
||||||
<VersionDrawer collectionSlug={collectionSlug} docID={docID} drawerSlug={drawerSlug} />
|
<VersionDrawer
|
||||||
|
collectionSlug={collectionSlug}
|
||||||
|
docID={docID}
|
||||||
|
drawerSlug={drawerSlug}
|
||||||
|
globalSlug={globalSlug}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}, [collectionSlug, docID, drawerSlug])
|
}, [collectionSlug, docID, drawerSlug, globalSlug])
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ export const SelectComparison: React.FC<Props> = memo((props) => {
|
|||||||
const {
|
const {
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
docID,
|
docID,
|
||||||
|
globalSlug,
|
||||||
onChange: onChangeFromProps,
|
onChange: onChangeFromProps,
|
||||||
versionFromID,
|
versionFromID,
|
||||||
versionFromOptions,
|
versionFromOptions,
|
||||||
} = props
|
} = props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { Drawer, openDrawer } = useVersionDrawer({ collectionSlug, docID })
|
const { Drawer, openDrawer } = useVersionDrawer({ collectionSlug, docID, globalSlug })
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import type { PaginatedDocs, SanitizedCollectionConfig } from 'payload'
|
|||||||
import type { CompareOption } from '../Default/types.js'
|
import type { CompareOption } from '../Default/types.js'
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
collectionSlug: string
|
collectionSlug?: string
|
||||||
docID: number | string
|
docID?: number | string
|
||||||
|
globalSlug?: string
|
||||||
onChange: (val: CompareOption) => void
|
onChange: (val: CompareOption) => void
|
||||||
versionFromID?: string
|
versionFromID?: string
|
||||||
versionFromOptions: CompareOption[]
|
versionFromOptions: CompareOption[]
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ export async function VersionView(props: DocumentViewServerProps) {
|
|||||||
VersionToCreatedAtLabel={formatPill({ doc: versionTo, labelStyle: 'pill' })}
|
VersionToCreatedAtLabel={formatPill({ doc: versionTo, labelStyle: 'pill' })}
|
||||||
versionToID={versionTo.id}
|
versionToID={versionTo.id}
|
||||||
versionToStatus={versionTo.version?._status}
|
versionToStatus={versionTo.version?._status}
|
||||||
versionToUseAsTitle={versionTo[collectionConfig.admin?.useAsTitle || 'id']}
|
versionToUseAsTitle={versionTo[collectionConfig?.admin?.useAsTitle || 'id']}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ describe('Versions', () => {
|
|||||||
const fieldValue = autosaveRelationField.locator('.value-container')
|
const fieldValue = autosaveRelationField.locator('.value-container')
|
||||||
await expect(fieldValue).toContainText('test')
|
await expect(fieldValue).toContainText('test')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should show collection versions view level action in collection versions view', async () => {
|
test('should show collection versions view level action in collection versions view', async () => {
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
await page.locator('tbody tr .cell-title a').first().click()
|
await page.locator('tbody tr .cell-title a').first().click()
|
||||||
@@ -839,6 +840,51 @@ describe('Versions', () => {
|
|||||||
await page.goto(url.global(disablePublishGlobalSlug))
|
await page.goto(url.global(disablePublishGlobalSlug))
|
||||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('global — should show versions drawer when SelectComparison more option is clicked', async () => {
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: draftGlobalSlug,
|
||||||
|
data: {
|
||||||
|
title: 'initial title',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: draftGlobalSlug,
|
||||||
|
data: {
|
||||||
|
title: 'initial title 2',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = new AdminUrlUtil(serverURL, draftGlobalSlug)
|
||||||
|
await page.goto(`${url.global(draftGlobalSlug)}/versions`)
|
||||||
|
|
||||||
|
const versionsTable = page.locator('.table table')
|
||||||
|
await expect(versionsTable).toBeVisible()
|
||||||
|
|
||||||
|
const versionAnchor = versionsTable.locator('tbody tr.row-1 td.cell-updatedAt a')
|
||||||
|
await expect(versionAnchor).toBeVisible()
|
||||||
|
await versionAnchor.click()
|
||||||
|
|
||||||
|
const compareFromContainer = page.locator(
|
||||||
|
'.view-version__version-from .field-type.compare-version',
|
||||||
|
)
|
||||||
|
await expect(compareFromContainer).toBeVisible()
|
||||||
|
|
||||||
|
const fromSelect = compareFromContainer.locator('.react-select .rs__control')
|
||||||
|
await expect(fromSelect).toBeVisible()
|
||||||
|
await fromSelect.click()
|
||||||
|
|
||||||
|
const moreVersions = compareFromContainer.locator('.rs__option:has-text("More versions...")')
|
||||||
|
await expect(moreVersions).toBeVisible()
|
||||||
|
await moreVersions.click()
|
||||||
|
|
||||||
|
const versionDrawer = page.locator('dialog.version-drawer')
|
||||||
|
await expect(versionDrawer).toBeVisible()
|
||||||
|
|
||||||
|
const versionsDrawerTableBody = versionDrawer.locator('main.versions table tbody')
|
||||||
|
await expect(versionsDrawerTableBody).toBeVisible()
|
||||||
|
await expect(versionsDrawerTableBody.locator('tr')).toHaveCount(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Scheduled publish', () => {
|
describe('Scheduled publish', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user