Skip to content

Commit

Permalink
feat: adds ability to define base filter for list view (#9177)
Browse files Browse the repository at this point in the history
Adds the ability to define base list view filters, which is super
helpful when you're doing multi-tenant things in Payload.
  • Loading branch information
jmikrut authored Nov 13, 2024
1 parent f4d526d commit 9da8543
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/admin/collections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion packages/next/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion packages/payload/src/collections/config/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ServerOnlyCollectionProperties = keyof Pick<

export type ServerOnlyCollectionAdminProperties = keyof Pick<
SanitizedCollectionConfig['admin'],
'hidden'
'baseListFilter' | 'hidden'
>

export type ServerOnlyUploadProperties = keyof Pick<
Expand Down Expand Up @@ -75,6 +75,7 @@ const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [

const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
'hidden',
'baseListFilter',
// 'preview' is handled separately
// `livePreview` is handled separately
]
Expand Down
11 changes: 11 additions & 0 deletions packages/payload/src/collections/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
*/
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ export type {
AfterRefreshHook as CollectionAfterRefreshHook,
AuthCollection,
AuthOperationsFromCollectionSlug,
BaseListFilter,
BeforeChangeHook as CollectionBeforeChangeHook,
BeforeDeleteHook as CollectionBeforeDeleteHook,
BeforeLoginHook as CollectionBeforeLoginHook,
Expand Down
19 changes: 19 additions & 0 deletions test/admin/collections/BaseListFilter.ts
Original file line number Diff line number Diff line change
@@ -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',
},
],
}
2 changes: 2 additions & 0 deletions test/admin/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -154,6 +155,7 @@ export default buildConfigWithDefaults({
CustomIdTab,
CustomIdRow,
DisableDuplicate,
BaseListFilter,
],
globals: [
GlobalHidden,
Expand Down
10 changes: 10 additions & 0 deletions test/admin/e2e/2/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('admin2', () => {
let page: Page
let geoUrl: AdminUrlUtil
let postsUrl: AdminUrlUtil
let baseListFiltersUrl: AdminUrlUtil
let customViewsUrl: AdminUrlUtil

let serverURL: string
Expand All @@ -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()
Expand Down Expand Up @@ -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)
})
})
})
})

Expand Down
25 changes: 25 additions & 0 deletions test/admin/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>;
Expand Down Expand Up @@ -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".
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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".
Expand Down
18 changes: 18 additions & 0 deletions test/admin/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 9da8543

Please sign in to comment.