diff --git a/packages/payload/src/versions/saveVersion.ts b/packages/payload/src/versions/saveVersion.ts index 6e9273ad284..77d44b513ad 100644 --- a/packages/payload/src/versions/saveVersion.ts +++ b/packages/payload/src/versions/saveVersion.ts @@ -42,6 +42,11 @@ export const saveVersion = async ({ if (draft) { versionData._status = 'draft' } + + if (collection?.timestamps && draft) { + versionData.updatedAt = now + } + if (versionData._id) { delete versionData._id } diff --git a/packages/ui/src/elements/Autosave/index.tsx b/packages/ui/src/elements/Autosave/index.tsx index 3460047e438..b8c7fbd0c83 100644 --- a/packages/ui/src/elements/Autosave/index.tsx +++ b/packages/ui/src/elements/Autosave/index.tsx @@ -49,6 +49,7 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) setLastUpdateTime, setMostRecentVersionIsAutosaved, setUnpublishedVersionCount, + updateSavedDocumentData, } = useDocumentInfo() const queueRef = useRef([]) const isProcessingRef = useRef(false) @@ -180,9 +181,9 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) setMostRecentVersionIsAutosaved(true) setUnpublishedVersionCount((prev) => prev + 1) } - } else { - return res.json() } + + return res.json() }) .then((json) => { if ( @@ -231,6 +232,14 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) setSaving(false) return } + } else { + // If it's not an error then we can update the document data inside the context + const document = json?.doc || json?.result + + // Manually update the data since this function doesn't fire the `submit` function from useForm + if (document) { + updateSavedDocumentData(document) + } } }) .then(() => { diff --git a/packages/ui/src/views/Edit/index.tsx b/packages/ui/src/views/Edit/index.tsx index 41c0295ef2f..10042f0b025 100644 --- a/packages/ui/src/views/Edit/index.tsx +++ b/packages/ui/src/views/Edit/index.tsx @@ -225,10 +225,12 @@ export const DefaultEditView: React.FC = ({ async (json): Promise => { const controller = handleAbortRef(abortOnSaveRef) + const document = json?.doc || json?.result + reportUpdate({ id, entitySlug, - updatedAt: json?.result?.updatedAt || new Date().toISOString(), + updatedAt: document?.updatedAt || new Date().toISOString(), }) // If we're editing the doc of the logged-in user, @@ -240,13 +242,19 @@ export const DefaultEditView: React.FC = ({ incrementVersionCount() if (typeof updateSavedDocumentData === 'function') { - void updateSavedDocumentData(json?.doc || {}) + void updateSavedDocumentData(document || {}) } if (typeof onSaveFromContext === 'function') { + const operation = id ? 'update' : 'create' + void onSaveFromContext({ ...json, - operation: id ? 'update' : 'create', + operation, + updatedAt: + operation === 'update' + ? new Date().toISOString() + : document?.updatedAt || new Date().toISOString(), }) } @@ -254,7 +262,7 @@ export const DefaultEditView: React.FC = ({ // Redirect to the same locale if it's been set const redirectRoute = formatAdminURL({ adminRoute, - path: `/collections/${collectionSlug}/${json?.doc?.id}${locale ? `?locale=${locale}` : ''}`, + path: `/collections/${collectionSlug}/${document?.id}${locale ? `?locale=${locale}` : ''}`, }) router.push(redirectRoute) } else { @@ -269,7 +277,7 @@ export const DefaultEditView: React.FC = ({ const { state } = await getFormState({ id, collectionSlug, - data: json?.doc || json?.result, + data: document, docPermissions, docPreferences, globalSlug, diff --git a/test/versions/config.ts b/test/versions/config.ts index 1bf443f9a1c..e89131e9fde 100644 --- a/test/versions/config.ts +++ b/test/versions/config.ts @@ -23,6 +23,8 @@ export default buildConfigWithDefaults({ importMap: { baseDir: path.resolve(dirname), }, + // The autosave test uses this format in order to compare timestamps in the UI + dateFormat: 'MMMM do yyyy, h:mm:ss a', }, collections: [ DisablePublish, diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index b9a32c37a8b..bc055e487ae 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -426,6 +426,59 @@ describe('Versions', () => { await expect(drawer.locator('.id-label')).toBeVisible() }) + test('collection - should update updatedAt', async () => { + await page.goto(url.create) + await page.waitForURL(`**/${url.create}`) + + // fill out doc in english + await page.locator('#field-title').fill('title') + await page.locator('#field-description').fill('initial description') + await saveDocAndAssert(page) + + const updatedAtWrapper = await page.locator( + '.doc-controls .doc-controls__content .doc-controls__list-item', + { + hasText: 'Last Modified', + }, + ) + const initialUpdatedAt = await updatedAtWrapper.locator('.doc-controls__value').textContent() + + // wait for 1 second so that the timestamp can be different + await wait(1000) + + await page.locator('#field-description').fill('changed description') + await saveDocAndAssert(page) + + const newUpdatedAt = await updatedAtWrapper.locator('.doc-controls__value').textContent() + + expect(newUpdatedAt).not.toEqual(initialUpdatedAt) + }) + + test('collection - should update updatedAt on 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 updatedAtWrapper = await page.locator( + '.doc-controls .doc-controls__content .doc-controls__list-item', + { + hasText: 'Last Modified', + }, + ) + const initialUpdatedAt = await updatedAtWrapper.locator('.doc-controls__value').textContent() + + // wait for 1 second so that the timestamp can be different + await wait(1000) + + await page.locator('#field-title').fill('autosave title updated') + await waitForAutoSaveToRunAndComplete(page) + + const newUpdatedAt = await updatedAtWrapper.locator('.doc-controls__value').textContent() + + expect(newUpdatedAt).not.toEqual(initialUpdatedAt) + }) + test('global - should autosave', async () => { const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug) await page.goto(url.global(autoSaveGlobalSlug)) diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index 2464ab892ea..15243c02445 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -593,6 +593,61 @@ describe('Versions', () => { expect(draftPost.title.es).toBe(spanishTitle) }) + it('should have correct updatedAt timestamps when saving drafts', async () => { + const created = await payload.create({ + collection: draftCollectionSlug, + data: { + description: 'desc', + title: 'title', + }, + draft: true, + }) + + await wait(10) + + const updated = await payload.update({ + id: created.id, + collection: draftCollectionSlug, + data: { + title: 'updated title', + }, + draft: true, + }) + + const createdUpdatedAt = new Date(created.updatedAt) + const updatedUpdatedAt = new Date(updated.updatedAt) + + expect(Number(updatedUpdatedAt)).toBeGreaterThan(Number(createdUpdatedAt)) + }) + + it('should have correct updatedAt timestamps when saving drafts with autosave', async () => { + const created = await payload.create({ + collection: draftCollectionSlug, + data: { + description: 'desc', + title: 'title', + }, + draft: true, + }) + + await wait(10) + + const updated = await payload.update({ + id: created.id, + collection: draftCollectionSlug, + data: { + title: 'updated title', + }, + draft: true, + autosave: true, + }) + + const createdUpdatedAt = new Date(created.updatedAt) + const updatedUpdatedAt = new Date(updated.updatedAt) + + expect(Number(updatedUpdatedAt)).toBeGreaterThan(Number(createdUpdatedAt)) + }) + it('should validate when publishing with the draft arg', async () => { // no title (not valid for publishing) const doc = await payload.create({