Skip to content

Commit

Permalink
fix(next): autosave document rendering (#9364)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jacobsfletch authored Nov 20, 2024
1 parent 4030e21 commit 9e85be0
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 36 deletions.
46 changes: 27 additions & 19 deletions packages/next/src/views/Document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const renderDocument = async ({
}> => {
const {
collectionConfig,
docID: id,
docID: idFromArgs,
globalConfig,
locale,
permissions,
Expand All @@ -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>
Expand All @@ -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,
Expand All @@ -104,7 +104,7 @@ export const renderDocument = async ({
] = await Promise.all([
// Get document preferences
getDocPreferences({
id,
id: idFromArgs,
collectionSlug,
globalSlug,
payload,
Expand All @@ -113,7 +113,7 @@ export const renderDocument = async ({

// Get permissions
getDocumentPermissions({
id,
id: idFromArgs,
collectionConfig,
data: doc,
globalConfig,
Expand All @@ -122,7 +122,7 @@ export const renderDocument = async ({

// Fetch document lock state
getIsLocked({
id,
id: idFromArgs,
collectionConfig,
globalConfig,
isEditing,
Expand All @@ -135,7 +135,7 @@ export const renderDocument = async ({
{ state: formState },
] = await Promise.all([
getVersions({
id,
id: idFromArgs,
collectionConfig,
docPermissions,
globalConfig,
Expand All @@ -144,15 +144,15 @@ export const renderDocument = async ({
user,
}),
buildFormState({
id,
id: idFromArgs,
collectionSlug,
data: doc,
docPermissions,
docPreferences,
fallbackLocale: false,
globalSlug,
locale: locale?.code,
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
operation: (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create',
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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,
Expand All @@ -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')
}
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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])

Expand Down
16 changes: 6 additions & 10 deletions packages/ui/src/providers/ServerFunctions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
7 changes: 3 additions & 4 deletions test/fields-relationship/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})

Expand Down
3 changes: 3 additions & 0 deletions test/helpers/e2e/trackNetworkRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +27,8 @@ export const trackNetworkRequests = async (
}
})

await action()

if (typeof beforePoll === 'function') {
await beforePoll()
}
Expand Down
38 changes: 38 additions & 0 deletions test/versions/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/_community/config.ts"
"./test/versions/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"
Expand Down

0 comments on commit 9e85be0

Please sign in to comment.