fix(ui): relationship filterOptions not applied within the list view (#11008)
Fixes #10440. When `filterOptions` are set on a relationship field, those same filters are not applied to the `Filter` component within the list view. This is because `filterOptions` is not being thread into the `RelationshipFilter` component responsible for populating the available options. To do this, we first need to be resolve the filter options on the server as they accept functions. Once resolved, they can be prop-drilled into the proper component and appended onto the client-side "where" query. Reliant on #11080.
This commit is contained in:
@@ -302,8 +302,9 @@ describe('List View', () => {
|
||||
|
||||
await page.waitForURL(new RegExp(encodedQueryString))
|
||||
|
||||
const whereBuilder = page.locator('.list-controls__where.rah-static.rah-static--height-auto')
|
||||
await expect(whereBuilder).toBeVisible()
|
||||
await expect(
|
||||
page.locator('.list-controls__where.rah-static.rah-static--height-auto'),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should respect base list filters', async () => {
|
||||
@@ -356,30 +357,31 @@ describe('List View', () => {
|
||||
test('should reset filter value when a different field is selected', async () => {
|
||||
const id = (await page.locator('.cell-id').first().innerText()).replace('ID: ', '')
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'ID',
|
||||
operatorLabel: 'equals',
|
||||
value: id,
|
||||
})
|
||||
|
||||
const filterField = page.locator('.condition__field')
|
||||
const filterField = whereBuilder.locator('.condition__field')
|
||||
await filterField.click()
|
||||
|
||||
// select new filter field of Number
|
||||
const dropdownFieldOption = filterField.locator('.rs__option', {
|
||||
hasText: exactText('Status'),
|
||||
})
|
||||
|
||||
await dropdownFieldOption.click()
|
||||
await expect(filterField).toContainText('Status')
|
||||
|
||||
await expect(page.locator('.condition__value input')).toHaveValue('')
|
||||
await expect(whereBuilder.locator('.condition__value input')).toHaveValue('')
|
||||
})
|
||||
|
||||
test('should remove condition from URL when value is cleared', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Relationship',
|
||||
operatorLabel: 'equals',
|
||||
@@ -391,7 +393,7 @@ describe('List View', () => {
|
||||
|
||||
await page.waitForURL(new RegExp(encodedQueryString + '[^&]*'))
|
||||
|
||||
await page.locator('.condition__value .clear-indicator').click()
|
||||
await whereBuilder.locator('.condition__value .clear-indicator').click()
|
||||
|
||||
await page.waitForURL(new RegExp(encodedQueryString))
|
||||
})
|
||||
@@ -403,15 +405,13 @@ describe('List View', () => {
|
||||
test('should refresh relationship values when a different field is selected', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Relationship',
|
||||
operatorLabel: 'equals',
|
||||
value: 'post1',
|
||||
})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
|
||||
const conditionField = whereBuilder.locator('.condition__field')
|
||||
await conditionField.click()
|
||||
|
||||
@@ -565,15 +565,15 @@ describe('List View', () => {
|
||||
test('should reset filter values for every additional filter', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Tab 1 > Title',
|
||||
operatorLabel: 'equals',
|
||||
value: 'Test',
|
||||
})
|
||||
|
||||
await page.locator('.condition__actions-add').click()
|
||||
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
await whereBuilder.locator('.condition__actions-add').click()
|
||||
const secondLi = whereBuilder.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
await expect(secondLi).toBeVisible()
|
||||
|
||||
await expect(
|
||||
@@ -587,14 +587,13 @@ describe('List View', () => {
|
||||
test('should not re-render page upon typing in a value in the filter value field', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Tab 1 > Title',
|
||||
operatorLabel: 'equals',
|
||||
skipValueInput: true,
|
||||
})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
const valueInput = whereBuilder.locator('.condition__value >> input')
|
||||
|
||||
// Type into the input field instead of filling it
|
||||
@@ -611,7 +610,7 @@ describe('List View', () => {
|
||||
test('should still show second filter if two filters exist and first filter is removed', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Tab 1 > Title',
|
||||
operatorLabel: 'equals',
|
||||
@@ -620,9 +619,9 @@ describe('List View', () => {
|
||||
|
||||
await wait(500)
|
||||
|
||||
await page.locator('.condition__actions-add').click()
|
||||
await whereBuilder.locator('.condition__actions-add').click()
|
||||
|
||||
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
const secondLi = whereBuilder.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
await expect(secondLi).toBeVisible()
|
||||
const secondConditionField = secondLi.locator('.condition__field')
|
||||
const secondOperatorField = secondLi.locator('.condition__operator')
|
||||
@@ -705,15 +704,13 @@ describe('List View', () => {
|
||||
test('should properly paginate many documents', async () => {
|
||||
await page.goto(with300DocumentsUrl.list)
|
||||
|
||||
await addListFilter({
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Self Relation',
|
||||
operatorLabel: 'equals',
|
||||
skipValueInput: true,
|
||||
})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
|
||||
const valueField = whereBuilder.locator('.condition__value')
|
||||
await valueField.click()
|
||||
await page.keyboard.type('4')
|
||||
|
||||
@@ -72,6 +72,22 @@ export const Relationship: CollectionConfig = {
|
||||
'This will filter the relationship options based on id, which is the same as the relationship field in this document',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relationshipFilteredByField',
|
||||
filterOptions: () => {
|
||||
return {
|
||||
filter: {
|
||||
equals: 'Include me',
|
||||
},
|
||||
}
|
||||
},
|
||||
admin: {
|
||||
description:
|
||||
'This will filter the relationship options if the filter field in this document is set to "Include me"',
|
||||
},
|
||||
relationTo: slug,
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
name: 'relationshipFilteredAsync',
|
||||
filterOptions: (args: FilterOptionsProps<FieldsRelationship>) => {
|
||||
|
||||
@@ -316,6 +316,41 @@ describe('Relationship Field', () => {
|
||||
await runFilterOptionsTest('relationshipFilteredAsync', 'Relationship Filtered Async')
|
||||
})
|
||||
|
||||
test('should apply filter options within list view filter controls', async () => {
|
||||
const { id: idToInclude } = await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
filter: 'Include me',
|
||||
},
|
||||
})
|
||||
|
||||
// first ensure that filter options are applied in the edit view
|
||||
await page.goto(url.edit(idToInclude))
|
||||
const field = page.locator('#field-relationshipFilteredByField')
|
||||
await field.click({ delay: 100 })
|
||||
const options = field.locator('.rs__option')
|
||||
await expect(options).toHaveCount(1)
|
||||
await expect(options).toContainText(idToInclude)
|
||||
|
||||
// now ensure that the same filter options are applied in the list view
|
||||
await page.goto(url.list)
|
||||
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Relationship Filtered By Field',
|
||||
operatorLabel: 'equals',
|
||||
skipValueInput: true,
|
||||
})
|
||||
|
||||
const valueInput = page.locator('.condition__value input')
|
||||
await valueInput.click()
|
||||
const valueOptions = whereBuilder.locator('.condition__value .rs__option')
|
||||
|
||||
await expect(valueOptions).toHaveCount(2)
|
||||
await expect(valueOptions.locator(`text=None`)).toBeVisible()
|
||||
await expect(valueOptions.locator(`text=${idToInclude}`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('should allow usage of relationTo in filterOptions', async () => {
|
||||
const { id: include } = (await payload.create({
|
||||
collection: relationOneSlug,
|
||||
|
||||
@@ -6,6 +6,60 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
@@ -118,6 +172,10 @@ export interface FieldsRelationship {
|
||||
* This will filter the relationship options based on id, which is the same as the relationship field in this document
|
||||
*/
|
||||
relationshipFilteredByID?: (string | null) | RelationOne;
|
||||
/**
|
||||
* This will filter the relationship options if the filter field in this document is set to "Include me"
|
||||
*/
|
||||
relationshipFilteredByField?: (string | null) | FieldsRelationship;
|
||||
relationshipFilteredAsync?: (string | null) | RelationOne;
|
||||
relationshipManyFiltered?:
|
||||
| (
|
||||
@@ -446,6 +504,7 @@ export interface FieldsRelationshipSelect<T extends boolean = true> {
|
||||
relationshipRestricted?: T;
|
||||
relationshipWithTitle?: T;
|
||||
relationshipFilteredByID?: T;
|
||||
relationshipFilteredByField?: T;
|
||||
relationshipFilteredAsync?: T;
|
||||
relationshipManyFiltered?: T;
|
||||
filter?: T;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
|
||||
import { expect } from '@playwright/test'
|
||||
import { exactText } from 'helpers.js'
|
||||
@@ -18,7 +18,7 @@ export const addListFilter = async ({
|
||||
replaceExisting?: boolean
|
||||
skipValueInput?: boolean
|
||||
value?: string
|
||||
}) => {
|
||||
}): Promise<Locator> => {
|
||||
await openListFilters(page, {})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
@@ -52,4 +52,6 @@ export const addListFilter = async ({
|
||||
await valueOptions.locator(`text=${value}`).click()
|
||||
}
|
||||
}
|
||||
|
||||
return whereBuilder
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user