feat: adds ability to define base filter for list view (#9177)

Adds the ability to define base list view filters, which is super
helpful when you're doing multi-tenant things in Payload.
This commit is contained in:
James Mikrut
2024-11-13 13:34:01 -05:00
committed by GitHub
parent f4d526d6e5
commit 9da85430a5
10 changed files with 105 additions and 2 deletions

View File

@@ -42,6 +42,7 @@ The following options are available:
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| **`listSearchableFields`** | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| **`pagination`** | Set pagination-specific options for this Collection. [More details](#pagination). |
| **`baseListFilter`** | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
### Custom Components

View File

@@ -117,7 +117,7 @@ export const renderListView = async (
const page = isNumber(query?.page) ? Number(query.page) : 0
const whereQuery = mergeListSearchAndWhere({
let whereQuery = mergeListSearchAndWhere({
collectionConfig,
search: typeof query?.search === 'string' ? query.search : undefined,
where: (query?.where as Where) || undefined,
@@ -135,6 +135,21 @@ export const renderListView = async (
? collectionConfig.defaultSort
: undefined)
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
const baseListFilter = await collectionConfig.admin.baseListFilter({
limit,
page,
req,
sort,
})
if (baseListFilter) {
whereQuery = {
and: [whereQuery, baseListFilter].filter(Boolean),
}
}
}
const data = await payload.find({
collection: collectionSlug,
depth: 0,

View File

@@ -20,7 +20,7 @@ export type ServerOnlyCollectionProperties = keyof Pick<
export type ServerOnlyCollectionAdminProperties = keyof Pick<
SanitizedCollectionConfig['admin'],
'hidden'
'baseListFilter' | 'hidden'
>
export type ServerOnlyUploadProperties = keyof Pick<
@@ -75,6 +75,7 @@ const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
'hidden',
'baseListFilter',
// 'preview' is handled separately
// `livePreview` is handled separately
]

View File

@@ -39,12 +39,14 @@ import type {
TypedAuthOperations,
TypedCollection,
TypedCollectionSelect,
TypedLocale,
} from '../../index.js'
import type {
PayloadRequest,
SelectType,
Sort,
TransformCollectionWithSelect,
Where,
} from '../../types/index.js'
import type { SanitizedUploadConfig, UploadConfig } from '../../uploads/types.js'
import type {
@@ -253,7 +255,16 @@ export type AfterForgotPasswordHook = (args: {
context: RequestContext
}) => any
export type BaseListFilter = (args: {
limit: number
locale?: TypedLocale
page: number
req: PayloadRequest
sort: string
}) => null | Promise<null | Where> | Where
export type CollectionAdminOptions = {
baseListFilter?: BaseListFilter
/**
* Custom admin components
*/

View File

@@ -804,6 +804,7 @@ export type {
AfterRefreshHook as CollectionAfterRefreshHook,
AuthCollection,
AuthOperationsFromCollectionSlug,
BaseListFilter,
BeforeChangeHook as CollectionBeforeChangeHook,
BeforeDeleteHook as CollectionBeforeDeleteHook,
BeforeLoginHook as CollectionBeforeLoginHook,

View File

@@ -0,0 +1,19 @@
import type { CollectionConfig } from 'payload'
export const BaseListFilter: CollectionConfig = {
slug: 'base-list-filters',
admin: {
baseListFilter: () => ({
title: {
not_equals: 'hide me',
},
}),
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
},
],
}

View File

@@ -3,6 +3,7 @@ import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { BaseListFilter } from './collections/BaseListFilter.js'
import { CustomFields } from './collections/CustomFields/index.js'
import { CustomIdRow } from './collections/CustomIdRow.js'
import { CustomIdTab } from './collections/CustomIdTab.js'
@@ -154,6 +155,7 @@ export default buildConfigWithDefaults({
CustomIdTab,
CustomIdRow,
DisableDuplicate,
BaseListFilter,
],
globals: [
GlobalHidden,

View File

@@ -43,6 +43,7 @@ describe('admin2', () => {
let page: Page
let geoUrl: AdminUrlUtil
let postsUrl: AdminUrlUtil
let baseListFiltersUrl: AdminUrlUtil
let customViewsUrl: AdminUrlUtil
let serverURL: string
@@ -61,6 +62,7 @@ describe('admin2', () => {
geoUrl = new AdminUrlUtil(serverURL, geoCollectionSlug)
postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug)
baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters')
customViewsUrl = new AdminUrlUtil(serverURL, customViews1CollectionSlug)
const context = await browser.newContext()
@@ -777,6 +779,14 @@ describe('admin2', () => {
).toHaveText('Title')
})
})
describe('base list filters', () => {
test('should respect base list filters', async () => {
await page.goto(baseListFiltersUrl.list)
await page.waitForURL((url) => url.toString().startsWith(baseListFiltersUrl.list))
await expect(page.locator(tableRowLocator)).toHaveCount(1)
})
})
})
})

View File

@@ -27,6 +27,7 @@ export interface Config {
customIdTab: CustomIdTab;
customIdRow: CustomIdRow;
'disable-duplicate': DisableDuplicate;
'base-list-filters': BaseListFilter;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
@@ -49,6 +50,7 @@ export interface Config {
customIdTab: CustomIdTabSelect<false> | CustomIdTabSelect<true>;
customIdRow: CustomIdRowSelect<false> | CustomIdRowSelect<true>;
'disable-duplicate': DisableDuplicateSelect<false> | DisableDuplicateSelect<true>;
'base-list-filters': BaseListFiltersSelect<false> | BaseListFiltersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
@@ -307,6 +309,16 @@ export interface DisableDuplicate {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "base-list-filters".
*/
export interface BaseListFilter {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
@@ -377,6 +389,10 @@ export interface PayloadLockedDocument {
| ({
relationTo: 'disable-duplicate';
value: string | DisableDuplicate;
} | null)
| ({
relationTo: 'base-list-filters';
value: string | BaseListFilter;
} | null);
globalSlug?: string | null;
user: {
@@ -604,6 +620,15 @@ export interface DisableDuplicateSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "base-list-filters_select".
*/
export interface BaseListFiltersSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".

View File

@@ -27,6 +27,24 @@ export const seed = async (_payload) => {
depth: 0,
overrideAccess: true,
}),
() =>
_payload.create({
collection: 'base-list-filters',
data: {
title: 'show me',
},
depth: 0,
overrideAccess: true,
}),
() =>
_payload.create({
collection: 'base-list-filters',
data: {
title: 'hide me',
},
depth: 0,
overrideAccess: true,
}),
...[...Array(11)].map((_, i) => async () => {
const postDoc = await _payload.create({
collection: postsCollectionSlug,