feat(ui): allow array fields to be filtered in list view (#11925)

### What?
Allows array fields to be filtered in the list view.

### Why?
Array fields were not filterable in the list view although all other
field types were filterable already.

### How?
Adds handling for array fields as filter option.


![image](https://github.com/user-attachments/assets/6df1a113-1d9f-4d50-92f7-d1fceed294d0)
This commit is contained in:
Tobias Odendahl
2025-05-01 20:19:43 +02:00
committed by GitHub
parent c08c7071ee
commit 78d3af7dc9
6 changed files with 97 additions and 1 deletions

View File

@@ -99,7 +99,7 @@ export const reduceFields = ({
return reduced return reduced
} }
if (field.type === 'group' && 'fields' in field) { if ((field.type === 'group' || field.type === 'array') && 'fields' in field) {
const translatedLabel = getTranslation(field.label || '', i18n) const translatedLabel = getTranslation(field.label || '', i18n)
const labelWithPrefix = labelPrefix const labelWithPrefix = labelPrefix

View File

@@ -0,0 +1,19 @@
import type { CollectionConfig } from 'payload'
import { arrayCollectionSlug } from '../slugs.js'
export const Array: CollectionConfig = {
slug: arrayCollectionSlug,
fields: [
{
name: 'array',
type: 'array',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
}

View File

@@ -3,6 +3,7 @@ import path from 'path'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { Array } from './collections/Array.js'
import { BaseListFilter } from './collections/BaseListFilter.js' import { BaseListFilter } from './collections/BaseListFilter.js'
import { CustomFields } from './collections/CustomFields/index.js' import { CustomFields } from './collections/CustomFields/index.js'
import { CustomViews1 } from './collections/CustomViews1.js' import { CustomViews1 } from './collections/CustomViews1.js'
@@ -158,6 +159,7 @@ export default buildConfigWithDefaults({
CollectionGroup2A, CollectionGroup2A,
CollectionGroup2B, CollectionGroup2B,
Geo, Geo,
Array,
DisableDuplicate, DisableDuplicate,
DisableCopyToLocale, DisableCopyToLocale,
BaseListFilter, BaseListFilter,

View File

@@ -17,6 +17,7 @@ import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { customAdminRoutes } from '../../shared.js' import { customAdminRoutes } from '../../shared.js'
import { import {
arrayCollectionSlug,
customViews1CollectionSlug, customViews1CollectionSlug,
geoCollectionSlug, geoCollectionSlug,
listDrawerSlug, listDrawerSlug,
@@ -57,6 +58,7 @@ const dirname = path.resolve(currentFolder, '../../')
describe('List View', () => { describe('List View', () => {
let page: Page let page: Page
let geoUrl: AdminUrlUtil let geoUrl: AdminUrlUtil
let arrayUrl: AdminUrlUtil
let postsUrl: AdminUrlUtil let postsUrl: AdminUrlUtil
let baseListFiltersUrl: AdminUrlUtil let baseListFiltersUrl: AdminUrlUtil
let customViewsUrl: AdminUrlUtil let customViewsUrl: AdminUrlUtil
@@ -79,6 +81,7 @@ describe('List View', () => {
})) }))
geoUrl = new AdminUrlUtil(serverURL, geoCollectionSlug) geoUrl = new AdminUrlUtil(serverURL, geoCollectionSlug)
arrayUrl = new AdminUrlUtil(serverURL, arrayCollectionSlug)
postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug) postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug)
with300DocumentsUrl = new AdminUrlUtil(serverURL, with300DocumentsSlug) with300DocumentsUrl = new AdminUrlUtil(serverURL, with300DocumentsSlug)
baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters') baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters')
@@ -389,6 +392,32 @@ describe('List View', () => {
await expect(page.locator(tableRowLocator)).toHaveCount(2) await expect(page.locator(tableRowLocator)).toHaveCount(2)
}) })
test('should allow to filter in array field', async () => {
await createArray()
await page.goto(arrayUrl.list)
await expect(page.locator(tableRowLocator)).toHaveCount(1)
await addListFilter({
page,
fieldLabel: 'Array > Text',
operatorLabel: 'equals',
value: 'test',
})
await expect(page.locator(tableRowLocator)).toHaveCount(1)
await page.locator('.condition__actions .btn.condition__actions-remove').click()
await addListFilter({
page,
fieldLabel: 'Array > Text',
operatorLabel: 'equals',
value: 'not-matching',
})
await expect(page.locator(tableRowLocator)).toHaveCount(0)
})
test('should reset filter value when a different field is selected', async () => { test('should reset filter value when a different field is selected', async () => {
const id = (await page.locator('.cell-id').first().innerText()).replace('ID: ', '') const id = (await page.locator('.cell-id').first().innerText()).replace('ID: ', '')
@@ -1405,3 +1434,12 @@ async function createGeo(overrides?: Partial<Geo>): Promise<Geo> {
}, },
}) as unknown as Promise<Geo> }) as unknown as Promise<Geo>
} }
async function createArray() {
return payload.create({
collection: arrayCollectionSlug,
data: {
array: [{ text: 'test' }],
},
})
}

View File

@@ -82,6 +82,7 @@ export interface Config {
'group-two-collection-ones': GroupTwoCollectionOne; 'group-two-collection-ones': GroupTwoCollectionOne;
'group-two-collection-twos': GroupTwoCollectionTwo; 'group-two-collection-twos': GroupTwoCollectionTwo;
geo: Geo; geo: Geo;
array: Array;
'disable-duplicate': DisableDuplicate; 'disable-duplicate': DisableDuplicate;
'disable-copy-to-locale': DisableCopyToLocale; 'disable-copy-to-locale': DisableCopyToLocale;
'base-list-filters': BaseListFilter; 'base-list-filters': BaseListFilter;
@@ -108,6 +109,7 @@ export interface Config {
'group-two-collection-ones': GroupTwoCollectionOnesSelect<false> | GroupTwoCollectionOnesSelect<true>; 'group-two-collection-ones': GroupTwoCollectionOnesSelect<false> | GroupTwoCollectionOnesSelect<true>;
'group-two-collection-twos': GroupTwoCollectionTwosSelect<false> | GroupTwoCollectionTwosSelect<true>; 'group-two-collection-twos': GroupTwoCollectionTwosSelect<false> | GroupTwoCollectionTwosSelect<true>;
geo: GeoSelect<false> | GeoSelect<true>; geo: GeoSelect<false> | GeoSelect<true>;
array: ArraySelect<false> | ArraySelect<true>;
'disable-duplicate': DisableDuplicateSelect<false> | DisableDuplicateSelect<true>; 'disable-duplicate': DisableDuplicateSelect<false> | DisableDuplicateSelect<true>;
'disable-copy-to-locale': DisableCopyToLocaleSelect<false> | DisableCopyToLocaleSelect<true>; 'disable-copy-to-locale': DisableCopyToLocaleSelect<false> | DisableCopyToLocaleSelect<true>;
'base-list-filters': BaseListFiltersSelect<false> | BaseListFiltersSelect<true>; 'base-list-filters': BaseListFiltersSelect<false> | BaseListFiltersSelect<true>;
@@ -414,6 +416,21 @@ export interface Geo {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "array".
*/
export interface Array {
id: string;
array?:
| {
text?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disable-duplicate". * via the `definition` "disable-duplicate".
@@ -534,6 +551,10 @@ export interface PayloadLockedDocument {
relationTo: 'geo'; relationTo: 'geo';
value: string | Geo; value: string | Geo;
} | null) } | null)
| ({
relationTo: 'array';
value: string | Array;
} | null)
| ({ | ({
relationTo: 'disable-duplicate'; relationTo: 'disable-duplicate';
value: string | DisableDuplicate; value: string | DisableDuplicate;
@@ -818,6 +839,20 @@ export interface GeoSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "array_select".
*/
export interface ArraySelect<T extends boolean = true> {
array?:
| T
| {
text?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disable-duplicate_select". * via the `definition` "disable-duplicate_select".

View File

@@ -2,6 +2,7 @@ export const usersCollectionSlug = 'users'
export const customViews1CollectionSlug = 'custom-views-one' export const customViews1CollectionSlug = 'custom-views-one'
export const customViews2CollectionSlug = 'custom-views-two' export const customViews2CollectionSlug = 'custom-views-two'
export const geoCollectionSlug = 'geo' export const geoCollectionSlug = 'geo'
export const arrayCollectionSlug = 'array'
export const postsCollectionSlug = 'posts' export const postsCollectionSlug = 'posts'
export const group1Collection1Slug = 'group-one-collection-ones' export const group1Collection1Slug = 'group-one-collection-ones'
export const group1Collection2Slug = 'group-one-collection-twos' export const group1Collection2Slug = 'group-one-collection-twos'
@@ -23,6 +24,7 @@ export const collectionSlugs = [
customViews1CollectionSlug, customViews1CollectionSlug,
customViews2CollectionSlug, customViews2CollectionSlug,
geoCollectionSlug, geoCollectionSlug,
arrayCollectionSlug,
postsCollectionSlug, postsCollectionSlug,
group1Collection1Slug, group1Collection1Slug,
group1Collection2Slug, group1Collection2Slug,