fix(next): autosave document rendering (#9364)

Closes #9242 and #9365. Autosave-enabled documents rendered within a
drawer were not being properly handled. This was causing multiple draft
documents to be created upon opening the drawer, as well as an empty
document returned from the server function, etc.
This commit is contained in:
Jacob Fletcher
2024-11-19 19:01:54 -05:00
committed by GitHub
parent 4030e212f5
commit 9e85be0006
7 changed files with 82 additions and 36 deletions

View File

@@ -49,7 +49,7 @@ export const renderDocument = async ({
}> => {
const {
collectionConfig,
docID: id,
docID: idFromArgs,
globalConfig,
locale,
permissions,
@@ -72,7 +72,7 @@ export const renderDocument = async ({
const segments = Array.isArray(params?.segments) ? params.segments : []
const collectionSlug = collectionConfig?.slug || undefined
const globalSlug = globalConfig?.slug || undefined
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
let isEditing = getIsEditing({ id: idFromArgs, collectionSlug, globalSlug })
let RootViewOverride: PayloadComponent
let CustomView: ViewFromConfig<ServerSideEditViewProps>
@@ -82,10 +82,10 @@ export const renderDocument = async ({
let apiURL: string
// Fetch the doc required for the view
const doc =
let doc =
initialData ||
(await getDocumentData({
id,
id: idFromArgs,
collectionSlug,
globalSlug,
locale,
@@ -104,7 +104,7 @@ export const renderDocument = async ({
] = await Promise.all([
// Get document preferences
getDocPreferences({
id,
id: idFromArgs,
collectionSlug,
globalSlug,
payload,
@@ -113,7 +113,7 @@ export const renderDocument = async ({
// Get permissions
getDocumentPermissions({
id,
id: idFromArgs,
collectionConfig,
data: doc,
globalConfig,
@@ -122,7 +122,7 @@ export const renderDocument = async ({
// Fetch document lock state
getIsLocked({
id,
id: idFromArgs,
collectionConfig,
globalConfig,
isEditing,
@@ -135,7 +135,7 @@ export const renderDocument = async ({
{ state: formState },
] = await Promise.all([
getVersions({
id,
id: idFromArgs,
collectionConfig,
docPermissions,
globalConfig,
@@ -144,7 +144,7 @@ export const renderDocument = async ({
user,
}),
buildFormState({
id,
id: idFromArgs,
collectionSlug,
data: doc,
docPermissions,
@@ -152,7 +152,7 @@ export const renderDocument = async ({
fallbackLocale: false,
globalSlug,
locale: locale?.code,
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
operation: (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create',
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
@@ -187,7 +187,7 @@ export const renderDocument = async ({
const apiQueryParams = `?${params.toString()}`
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${idFromArgs}${apiQueryParams}`
RootViewOverride =
collectionConfig?.admin?.components?.views?.edit?.root &&
@@ -274,8 +274,10 @@ export const renderDocument = async ({
const validateDraftData =
collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.validate
if (shouldAutosave && !validateDraftData && !id && collectionSlug) {
const doc = await payload.create({
let id = idFromArgs
if (shouldAutosave && !validateDraftData && !idFromArgs && collectionSlug) {
doc = await payload.create({
collection: collectionSlug,
data: initialData || {},
depth: 0,
@@ -287,12 +289,18 @@ export const renderDocument = async ({
})
if (doc?.id) {
const redirectURL = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${doc.id}`,
serverURL,
})
redirect(redirectURL)
id = doc.id
isEditing = getIsEditing({ id: doc.id, collectionSlug, globalSlug })
if (!drawerSlug) {
const redirectURL = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${doc.id}`,
serverURL,
})
redirect(redirectURL)
}
} else {
throw new Error('not-found')
}

View File

@@ -1,7 +1,7 @@
'use client'
import { useModal } from '@faceless-ui/modal'
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import type { DocumentDrawerProps } from './types.js'
@@ -45,6 +45,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
const [DocumentView, setDocumentView] = useState<React.ReactNode>(undefined)
const [isLoading, setIsLoading] = useState(true)
const hasRenderedDocument = useRef(false)
const getDocumentView = useCallback(
(docID?: number | string) => {
@@ -142,8 +143,9 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
}, [getDocumentView])
useEffect(() => {
if (!DocumentView) {
if (!DocumentView && !hasRenderedDocument.current) {
getDocumentView(existingDocID)
hasRenderedDocument.current = true
}
}, [DocumentView, getDocumentView, existingDocID])

View File

@@ -32,7 +32,7 @@ type RenderDocument = (args: {
redirectAfterDelete?: boolean
redirectAfterDuplicate?: boolean
signal?: AbortSignal
}) => Promise<{ docID: string; Document: React.ReactNode }>
}) => Promise<{ data: Data; Document: React.ReactNode }>
type GetDocumentSlots = (args: {
collectionSlug: string
@@ -129,16 +129,12 @@ export const ServerFunctionsProvider: React.FC<{
const { signal: remoteSignal, ...rest } = args || {}
try {
if (!remoteSignal?.aborted) {
const result = (await serverFunction({
name: 'render-document',
args: { fallbackLocale: false, ...rest },
})) as { docID: string; Document: React.ReactNode }
const result = (await serverFunction({
name: 'render-document',
args: { fallbackLocale: false, ...rest },
})) as { data: Data; Document: React.ReactNode }
if (!remoteSignal?.aborted) {
return result
}
}
return result
} catch (_err) {
console.error(_err) // eslint-disable-line no-console
}

View File

@@ -179,10 +179,9 @@ describe('fields - relationship', () => {
await expect(options).toHaveCount(2) // two docs
await options.nth(0).click()
await expect(field).toContainText(relationOneDoc.id)
await saveDocAndAssert(page)
await wait(200)
await trackNetworkRequests(page, `/api/${relationOneSlug}`, {
beforePoll: async () => await page.reload(),
await trackNetworkRequests(page, `/api/${relationOneSlug}`, async () => {
await saveDocAndAssert(page)
await wait(200)
})
})

View File

@@ -8,6 +8,7 @@ import { expect } from '@playwright/test'
export const trackNetworkRequests = async (
page: Page,
url: string,
action: () => Promise<any>,
options?: {
allowedNumberOfRequests?: number
beforePoll?: () => Promise<any> | void
@@ -26,6 +27,8 @@ export const trackNetworkRequests = async (
}
})
await action()
if (typeof beforePoll === 'function') {
await beforePoll()
}

View File

@@ -25,6 +25,7 @@
import type { BrowserContext, Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
@@ -43,6 +44,7 @@ import {
throttleTest,
} from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { trackNetworkRequests } from '../helpers/e2e/trackNetworkRequests.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../helpers/reInitializeDB.js'
import { waitForAutoSaveToRunAndComplete } from '../helpers/waitForAutoSaveToRunAndComplete.js'
@@ -394,6 +396,42 @@ describe('versions', () => {
expect(page.url()).toMatch(/\/versions$/)
})
test('collection - should autosave', async () => {
await page.goto(autosaveURL.create)
await page.locator('#field-title').fill('autosave title')
await waitForAutoSaveToRunAndComplete(page)
await expect(page.locator('#field-title')).toHaveValue('autosave title')
const { id: postID } = await payload.create({
collection: postCollectionSlug,
data: {
title: 'post title',
description: 'post description',
},
})
await page.goto(postURL.edit(postID))
await trackNetworkRequests(
page,
`${serverURL}/admin/collections/${postCollectionSlug}/${postID}`,
async () => {
await page
.locator(
'#field-relationToAutosaves.field-type.relationship .relationship-add-new__add-button.doc-drawer__toggler',
)
.click()
},
{
allowedNumberOfRequests: 1,
},
)
const drawer = page.locator('[id^=doc-drawer_autosave-posts_1_]')
await expect(drawer).toBeVisible()
await expect(drawer.locator('.id-label')).toBeVisible()
})
test('global - should autosave', async () => {
const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug)
await page.goto(url.global(autoSaveGlobalSlug))

View File

@@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/_community/config.ts"
"./test/versions/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"