fix(ui): query presets are available for unrelated collections (#11872)

When selecting query presets from the list drawer, all query presets are
available for selection, even if unrelated to the underlying collection.
When selecting one of these presets, the list view will crash with
client-side exceptions because the columns and filters that are applied
are incompatible.

The fix is to the thread `filterOptions` through the query presets
drawer. This will ensure that only related collections are shown.
This commit is contained in:
Jacob Fletcher
2025-03-25 23:45:03 -04:00
committed by GitHub
parent 10ac9893ad
commit 4fc2eec301
8 changed files with 105 additions and 20 deletions

View File

@@ -65,9 +65,22 @@ export const useQueryPresets = ({
collectionSlug: queryPresetsSlug,
})
const filterOptions = useMemo(
() => ({
'payload-query-presets': {
relatedCollection: {
equals: collectionSlug,
},
},
}),
[collectionSlug],
)
const [ListDrawer, , { closeDrawer: closeListDrawer, openDrawer: openListDrawer }] =
useListDrawer({
collectionSlugs: [queryPresetsSlug],
filterOptions,
selectedCollection: queryPresetsSlug,
})
const handlePresetChange = useCallback(

View File

@@ -0,0 +1,21 @@
import type { CollectionConfig } from 'payload'
import { postsSlug } from '../../slugs.js'
export const Posts: CollectionConfig = {
slug: postsSlug,
admin: {
useAsTitle: 'text',
},
enableQueryPresets: true,
lockDocuments: false,
fields: [
{
name: 'text',
type: 'text',
},
],
versions: {
drafts: true,
},
}

View File

@@ -3,6 +3,7 @@ import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { Pages } from './collections/Pages/index.js'
import { Posts } from './collections/Posts/index.js'
import { Users } from './collections/Users/index.js'
import { roles } from './fields/roles.js'
import { seed } from './seed.js'
@@ -54,7 +55,7 @@ export default buildConfigWithDefaults({
],
},
},
collections: [Pages, Users],
collections: [Pages, Users, Posts],
onInit: async (payload) => {
if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') {
await seed(payload)

View File

@@ -389,4 +389,19 @@ describe('Query Presets', () => {
}),
).toBeVisible()
})
test('only shows query presets related to the underlying collection', async () => {
// no results on `users` collection
const postsUrl = new AdminUrlUtil(serverURL, 'posts')
await page.goto(postsUrl.list)
const drawer = await openQueryPresetDrawer({ page })
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(0)
await expect(drawer.locator('.collection-list__no-results')).toBeVisible()
// results on `pages` collection
await page.goto(pagesUrl.list)
await openQueryPresetDrawer({ page })
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(3)
await drawer.locator('.collection-list__no-results').isHidden()
})
})

View File

@@ -1,5 +1,11 @@
import type { Page } from '@playwright/test'
import type { Locator, Page } from '@playwright/test'
export async function openQueryPresetDrawer({ page }: { page: Page }) {
import { expect } from '@playwright/test'
export async function openQueryPresetDrawer({ page }: { page: Page }): Promise<Locator> {
await page.click('button#select-preset')
const drawer = page.locator('dialog[id^="list-drawer_0_"]')
await expect(drawer).toBeVisible()
await expect(drawer.locator('.collection-list--payload-query-presets')).toBeVisible()
return drawer
}

View File

@@ -69,6 +69,7 @@ export interface Config {
collections: {
pages: Page;
users: User;
posts: Post;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
@@ -78,13 +79,14 @@ export interface Config {
collectionsSelect: {
pages: PagesSelect<false> | PagesSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
posts: PostsSelect<false> | PostsSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
'payload-query-presets': PayloadQueryPresetsSelect<false> | PayloadQueryPresetsSelect<true>;
};
db: {
defaultIDType: number;
defaultIDType: string;
};
globals: {};
globalsSelect: {};
@@ -120,7 +122,7 @@ export interface UserAuthOperations {
* via the `definition` "pages".
*/
export interface Page {
id: number;
id: string;
text?: string | null;
updatedAt: string;
createdAt: string;
@@ -131,7 +133,7 @@ export interface Page {
* via the `definition` "users".
*/
export interface User {
id: number;
id: string;
name?: string | null;
roles?: ('admin' | 'user' | 'anonymous')[] | null;
updatedAt: string;
@@ -145,25 +147,40 @@ export interface User {
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
*/
export interface Post {
id: string;
text?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: number;
id: string;
document?:
| ({
relationTo: 'pages';
value: number | Page;
value: string | Page;
} | null)
| ({
relationTo: 'users';
value: number | User;
value: string | User;
} | null)
| ({
relationTo: 'posts';
value: string | Post;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: number | User;
value: string | User;
};
updatedAt: string;
createdAt: string;
@@ -173,10 +190,10 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: number;
id: string;
user: {
relationTo: 'users';
value: number | User;
value: string | User;
};
key?: string | null;
value?:
@@ -196,7 +213,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: number;
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
@@ -207,23 +224,23 @@ export interface PayloadMigration {
* via the `definition` "payload-query-presets".
*/
export interface PayloadQueryPreset {
id: number;
id: string;
title: string;
isShared?: boolean | null;
access?: {
read?: {
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
users?: (number | User)[] | null;
users?: (string | User)[] | null;
roles?: ('admin' | 'user' | 'anonymous')[] | null;
};
update?: {
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
users?: (number | User)[] | null;
users?: (string | User)[] | null;
roles?: ('admin' | 'user' | 'anonymous')[] | null;
};
delete?: {
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
users?: (number | User)[] | null;
users?: (string | User)[] | null;
};
};
where?:
@@ -244,7 +261,7 @@ export interface PayloadQueryPreset {
| number
| boolean
| null;
relatedCollection: 'pages';
relatedCollection: 'pages' | 'posts';
updatedAt: string;
createdAt: string;
}
@@ -275,6 +292,16 @@ export interface UsersSelect<T extends boolean = true> {
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts_select".
*/
export interface PostsSelect<T extends boolean = true> {
text?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".

View File

@@ -2,4 +2,6 @@ export const usersSlug = 'users'
export const pagesSlug = 'pages'
export const collectionSlugs = [usersSlug, pagesSlug]
export const postsSlug = 'posts'
export const collectionSlugs = [usersSlug, pagesSlug, postsSlug]

View File

@@ -31,7 +31,7 @@
}
],
"paths": {
"@payload-config": ["./test/_community/config.ts"],
"@payload-config": ["./test/query-presets/config.ts"],
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
"@payloadcms/live-preview": ["./packages/live-preview/src"],
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],