Skip to content

Commit 062c1d7

Browse files
authored
fix: pagination returning duplicate results when timestamps: false (#13920)
### What Fixes a bug where collection docs paginate incorrectly when `timestamps: false` is set — the same docs were appearing across multiple pages. ### Why The `find` query sanitizes the `sort` parameter. - With `timestamps: true`, it defaults to `createdAt` - With `timestamps: false`, it falls back to `_id` That logic is correct, but in `find.ts` we always passed `timestamps: true`, ignoring the collection config. With the right sort applied, pagination works as expected. ### How `find.ts` now passes `collectionConfig.timestamps` to `buildSortParam()`, ensuring the correct sort field is chosen. --- Fixes #13888
1 parent 96c6612 commit 062c1d7

File tree

5 files changed

+70
-2
lines changed

5 files changed

+70
-2
lines changed

packages/db-mongodb/src/find.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const find: Find = async function find(
5454
locale,
5555
sort: sortArg || collectionConfig.defaultSort,
5656
sortAggregation,
57-
timestamps: true,
57+
timestamps: collectionConfig.timestamps || false,
5858
})
5959
}
6060

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
export const noTimestampsSlug = 'no-timestamps'
4+
5+
export const NoTimestampsCollection: CollectionConfig = {
6+
slug: noTimestampsSlug,
7+
timestamps: false,
8+
fields: [
9+
{
10+
name: 'title',
11+
type: 'text',
12+
},
13+
],
14+
}

test/admin/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CollectionHidden } from './collections/Hidden.js'
2121
import { ListDrawer } from './collections/ListDrawer.js'
2222
import { ListViewSelectAPI } from './collections/ListViewSelectAPI/index.js'
2323
import { CollectionNoApiView } from './collections/NoApiView.js'
24+
import { NoTimestampsCollection } from './collections/NoTimestamps.js'
2425
import { CollectionNotInView } from './collections/NotInView.js'
2526
import { Placeholder } from './collections/Placeholder.js'
2627
import { Posts } from './collections/Posts.js'
@@ -191,6 +192,7 @@ export default buildConfigWithDefaults({
191192
CustomListDrawer,
192193
ListViewSelectAPI,
193194
Virtuals,
195+
NoTimestampsCollection,
194196
],
195197
globals: [
196198
GlobalHidden,

test/admin/e2e/list-view/e2e.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const description = 'Description'
3535
let payload: PayloadTestSDK<Config>
3636

3737
import { listViewSelectAPISlug } from 'admin/collections/ListViewSelectAPI/index.js'
38+
import { noTimestampsSlug } from 'admin/collections/NoTimestamps.js'
3839
import { devUser } from 'credentials.js'
39-
import { getRowByCellValueAndAssert } from 'helpers/e2e/getRowByCellValueAndAssert.js'
4040
import {
4141
openListColumns,
4242
reorderColumns,
@@ -45,6 +45,7 @@ import {
4545
waitForColumnInURL,
4646
} from 'helpers/e2e/columns/index.js'
4747
import { addListFilter, openListFilters } from 'helpers/e2e/filters/index.js'
48+
import { getRowByCellValueAndAssert } from 'helpers/e2e/getRowByCellValueAndAssert.js'
4849
import { goToNextPage, goToPreviousPage } from 'helpers/e2e/goToNextPage.js'
4950
import { goToFirstCell } from 'helpers/e2e/navigateToDoc.js'
5051
import { deletePreferences } from 'helpers/e2e/preferences.js'
@@ -76,6 +77,7 @@ describe('List View', () => {
7677
let disableBulkEditUrl: AdminUrlUtil
7778
let user: any
7879
let virtualsUrl: AdminUrlUtil
80+
let noTimestampsUrl: AdminUrlUtil
7981

8082
let serverURL: string
8183
let adminRoutes: ReturnType<typeof getRoutes>
@@ -101,6 +103,7 @@ describe('List View', () => {
101103
placeholderUrl = new AdminUrlUtil(serverURL, placeholderCollectionSlug)
102104
disableBulkEditUrl = new AdminUrlUtil(serverURL, 'disable-bulk-edit')
103105
virtualsUrl = new AdminUrlUtil(serverURL, virtualsSlug)
106+
noTimestampsUrl = new AdminUrlUtil(serverURL, noTimestampsSlug)
104107
const context = await browser.newContext()
105108
page = await context.newPage()
106109
initPageConsoleErrorCatch(page)
@@ -1568,6 +1571,24 @@ describe('List View', () => {
15681571
await expect(page.locator('.per-page')).toContainText('Per Page: 15') // ensure this hasn't changed
15691572
await expect(page.locator('.page-controls__page-info')).toHaveText('16-16 of 16')
15701573
})
1574+
1575+
test('should paginate when timestamps are disabled', async () => {
1576+
await mapAsync([...Array(6)], async () => {
1577+
await createNoTimestampPost()
1578+
})
1579+
1580+
await page.goto(noTimestampsUrl.list)
1581+
1582+
await page.locator('.per-page .popup-button').click()
1583+
await page.getByRole('button', { name: '5', exact: true }).click()
1584+
await page.waitForURL(/limit=5/)
1585+
1586+
const firstPageIds = await page.locator('.cell-id').allInnerTexts()
1587+
await goToNextPage(page)
1588+
const secondPageIds = await page.locator('.cell-id').allInnerTexts()
1589+
1590+
expect(firstPageIds).not.toContain(secondPageIds[0])
1591+
})
15711592
})
15721593

15731594
// TODO: Troubleshoot flaky suite
@@ -1918,6 +1939,16 @@ async function createGeo(overrides?: Partial<Geo>): Promise<Geo> {
19181939
}) as unknown as Promise<Geo>
19191940
}
19201941

1942+
async function createNoTimestampPost(overrides?: Partial<Post>): Promise<Post> {
1943+
return payload.create({
1944+
collection: noTimestampsSlug,
1945+
data: {
1946+
title,
1947+
...overrides,
1948+
},
1949+
}) as unknown as Promise<Post>
1950+
}
1951+
19211952
async function createArray() {
19221953
return payload.create({
19231954
collection: arrayCollectionSlug,

test/admin/payload-types.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export interface Config {
9696
'custom-list-drawer': CustomListDrawer;
9797
'list-view-select-api': ListViewSelectApi;
9898
virtuals: Virtual;
99+
'no-timestamps': NoTimestamp;
99100
'payload-locked-documents': PayloadLockedDocument;
100101
'payload-preferences': PayloadPreference;
101102
'payload-migrations': PayloadMigration;
@@ -131,6 +132,7 @@ export interface Config {
131132
'custom-list-drawer': CustomListDrawerSelect<false> | CustomListDrawerSelect<true>;
132133
'list-view-select-api': ListViewSelectApiSelect<false> | ListViewSelectApiSelect<true>;
133134
virtuals: VirtualsSelect<false> | VirtualsSelect<true>;
135+
'no-timestamps': NoTimestampsSelect<false> | NoTimestampsSelect<true>;
134136
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
135137
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
136138
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
@@ -612,6 +614,14 @@ export interface Virtual {
612614
updatedAt: string;
613615
createdAt: string;
614616
}
617+
/**
618+
* This interface was referenced by `Config`'s JSON-Schema
619+
* via the `definition` "no-timestamps".
620+
*/
621+
export interface NoTimestamp {
622+
id: string;
623+
title?: string | null;
624+
}
615625
/**
616626
* This interface was referenced by `Config`'s JSON-Schema
617627
* via the `definition` "payload-locked-documents".
@@ -734,6 +744,10 @@ export interface PayloadLockedDocument {
734744
| ({
735745
relationTo: 'virtuals';
736746
value: string | Virtual;
747+
} | null)
748+
| ({
749+
relationTo: 'no-timestamps';
750+
value: string | NoTimestamp;
737751
} | null);
738752
globalSlug?: string | null;
739753
user: {
@@ -1175,6 +1189,13 @@ export interface VirtualsSelect<T extends boolean = true> {
11751189
updatedAt?: T;
11761190
createdAt?: T;
11771191
}
1192+
/**
1193+
* This interface was referenced by `Config`'s JSON-Schema
1194+
* via the `definition` "no-timestamps_select".
1195+
*/
1196+
export interface NoTimestampsSelect<T extends boolean = true> {
1197+
title?: T;
1198+
}
11781199
/**
11791200
* This interface was referenced by `Config`'s JSON-Schema
11801201
* via the `definition` "payload-locked-documents_select".

0 commit comments

Comments
 (0)