Skip to content

Commit 56667cd

Browse files
authored
fix(ui): fixed many bugs in the WhereBuilder relationship select menu (#10553)
Following #10551, I found and fixed a handful more bugs: - When writing to the input, the results that were already there were not cleaned, causing incorrect results to appear. - the scroll was causing an infinite loop that showed repeated elements - optimization: only the required field is selected (not required) - refs are set to the initial value to avoid a state where nothing can be searched
1 parent 2d70269 commit 56667cd

File tree

7 files changed

+125
-18
lines changed

7 files changed

+125
-18
lines changed

packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,17 @@ export const RelationshipField: React.FC<Props> = (props) => {
7676
const fieldToSearch = collection?.admin?.useAsTitle || 'id'
7777
const pageIndex = nextPageByRelationshipRef.current.get(relationSlug)
7878

79-
const query: {
80-
depth?: number
81-
limit?: number
82-
page?: number
83-
where: Where
84-
} = {
79+
const where: Where = {
80+
and: [],
81+
}
82+
const query = {
8583
depth: 0,
8684
limit: maxResultsPerRequest,
8785
page: pageIndex,
88-
where: {
89-
and: [],
86+
select: {
87+
[fieldToSearch]: true,
9088
},
89+
where,
9190
}
9291

9392
if (debouncedSearch) {
@@ -115,15 +114,13 @@ export const RelationshipField: React.FC<Props> = (props) => {
115114
if (data.docs.length > 0) {
116115
addOptions(data, relationSlug)
117116

118-
if (!debouncedSearch) {
119-
if (data.nextPage) {
120-
nextPageByRelationshipRef.current.set(relationSlug, data.nextPage)
121-
} else {
122-
partiallyLoadedRelationshipSlugs.current =
123-
partiallyLoadedRelationshipSlugs.current.filter(
124-
(partiallyLoadedRelation) => partiallyLoadedRelation !== relationSlug,
125-
)
126-
}
117+
if (data.nextPage) {
118+
nextPageByRelationshipRef.current.set(relationSlug, data.nextPage)
119+
} else {
120+
partiallyLoadedRelationshipSlugs.current =
121+
partiallyLoadedRelationshipSlugs.current.filter(
122+
(partiallyLoadedRelation) => partiallyLoadedRelation !== relationSlug,
123+
)
127124
}
128125
}
129126
} else {
@@ -209,7 +206,9 @@ export const RelationshipField: React.FC<Props> = (props) => {
209206
}, [hasMany, hasMultipleRelations, value, options])
210207

211208
const handleInputChange = (input: string) => {
209+
dispatchOptions({ type: 'CLEAR', i18n, required: false })
212210
const relationSlug = partiallyLoadedRelationshipSlugs.current[0]
211+
partiallyLoadedRelationshipSlugs.current = relationSlugs
213212
nextPageByRelationshipRef.current.set(relationSlug, 1)
214213
setSearch(input)
215214
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { with300DocumentsSlug } from '../slugs.js'
4+
5+
export const with300Documents: CollectionConfig = {
6+
slug: with300DocumentsSlug,
7+
admin: {
8+
useAsTitle: 'text',
9+
},
10+
fields: [
11+
{
12+
name: 'text',
13+
type: 'text',
14+
},
15+
{
16+
name: 'selfRelation',
17+
type: 'relationship',
18+
relationTo: with300DocumentsSlug,
19+
},
20+
],
21+
}

test/admin/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { CollectionNotInView } from './collections/NotInView.js'
1919
import { Posts } from './collections/Posts.js'
2020
import { UploadCollection } from './collections/Upload.js'
2121
import { Users } from './collections/Users.js'
22+
import { with300Documents } from './collections/With300Documents.js'
2223
import { CustomGlobalViews1 } from './globals/CustomViews1.js'
2324
import { CustomGlobalViews2 } from './globals/CustomViews2.js'
2425
import { Global } from './globals/Global.js'
@@ -155,6 +156,7 @@ export default buildConfigWithDefaults({
155156
Geo,
156157
DisableDuplicate,
157158
BaseListFilter,
159+
with300Documents,
158160
],
159161
globals: [
160162
GlobalHidden,

test/admin/e2e/list-view/e2e.spec.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import {
1616
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
1717
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
1818
import { customAdminRoutes } from '../../shared.js'
19-
import { customViews1CollectionSlug, geoCollectionSlug, postsCollectionSlug } from '../../slugs.js'
19+
import {
20+
customViews1CollectionSlug,
21+
geoCollectionSlug,
22+
postsCollectionSlug,
23+
with300DocumentsSlug,
24+
} from '../../slugs.js'
2025

2126
const { beforeAll, beforeEach, describe } = test
2227

@@ -48,6 +53,7 @@ describe('List View', () => {
4853
let postsUrl: AdminUrlUtil
4954
let baseListFiltersUrl: AdminUrlUtil
5055
let customViewsUrl: AdminUrlUtil
56+
let with300DocumentsUrl: AdminUrlUtil
5157

5258
let serverURL: string
5359
let adminRoutes: ReturnType<typeof getRoutes>
@@ -65,6 +71,7 @@ describe('List View', () => {
6571

6672
geoUrl = new AdminUrlUtil(serverURL, geoCollectionSlug)
6773
postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug)
74+
with300DocumentsUrl = new AdminUrlUtil(serverURL, with300DocumentsSlug)
6875
baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters')
6976
customViewsUrl = new AdminUrlUtil(serverURL, customViews1CollectionSlug)
7077

@@ -608,6 +615,35 @@ describe('List View', () => {
608615
})
609616
})
610617

618+
describe('WhereBuilder', () => {
619+
test('should render where builder', async () => {
620+
await page.goto(
621+
`${with300DocumentsUrl.list}?limit=10&page=1&where%5Bor%5D%5B0%5D%5Band%5D%5B0%5D%5BselfRelation%5D%5Bequals%5D=null`,
622+
)
623+
const valueField = page.locator('.condition__value')
624+
await valueField.click()
625+
await page.keyboard.type('4')
626+
const options = page.getByRole('option')
627+
expect(options).toHaveCount(10)
628+
for (const option of await options.all()) {
629+
expect(option).toHaveText('4')
630+
}
631+
await page.keyboard.press('Backspace')
632+
await page.keyboard.type('5')
633+
expect(options).toHaveCount(10)
634+
for (const option of await options.all()) {
635+
expect(option).toHaveText('5')
636+
}
637+
// await options.last().scrollIntoViewIfNeeded()
638+
await options.first().hover()
639+
// three times because react-select is not very reliable
640+
await page.mouse.wheel(0, 50)
641+
await page.mouse.wheel(0, 50)
642+
await page.mouse.wheel(0, 50)
643+
expect(options).toHaveCount(20)
644+
})
645+
})
646+
611647
describe('table columns', () => {
612648
test('should hide field column when field.hidden is true', async () => {
613649
await page.goto(postsUrl.list)

test/admin/payload-types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface Config {
2727
geo: Geo;
2828
'disable-duplicate': DisableDuplicate;
2929
'base-list-filters': BaseListFilter;
30+
with300documents: With300Document;
3031
'payload-locked-documents': PayloadLockedDocument;
3132
'payload-preferences': PayloadPreference;
3233
'payload-migrations': PayloadMigration;
@@ -49,6 +50,7 @@ export interface Config {
4950
geo: GeoSelect<false> | GeoSelect<true>;
5051
'disable-duplicate': DisableDuplicateSelect<false> | DisableDuplicateSelect<true>;
5152
'base-list-filters': BaseListFiltersSelect<false> | BaseListFiltersSelect<true>;
53+
with300documents: With300DocumentsSelect<false> | With300DocumentsSelect<true>;
5254
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
5355
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
5456
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
@@ -374,6 +376,17 @@ export interface BaseListFilter {
374376
updatedAt: string;
375377
createdAt: string;
376378
}
379+
/**
380+
* This interface was referenced by `Config`'s JSON-Schema
381+
* via the `definition` "with300documents".
382+
*/
383+
export interface With300Document {
384+
id: string;
385+
text?: string | null;
386+
selfRelation?: (string | null) | With300Document;
387+
updatedAt: string;
388+
createdAt: string;
389+
}
377390
/**
378391
* This interface was referenced by `Config`'s JSON-Schema
379392
* via the `definition` "payload-locked-documents".
@@ -444,6 +457,10 @@ export interface PayloadLockedDocument {
444457
| ({
445458
relationTo: 'base-list-filters';
446459
value: string | BaseListFilter;
460+
} | null)
461+
| ({
462+
relationTo: 'with300documents';
463+
value: string | With300Document;
447464
} | null);
448465
globalSlug?: string | null;
449466
user: {
@@ -734,6 +751,16 @@ export interface BaseListFiltersSelect<T extends boolean = true> {
734751
updatedAt?: T;
735752
createdAt?: T;
736753
}
754+
/**
755+
* This interface was referenced by `Config`'s JSON-Schema
756+
* via the `definition` "with300documents_select".
757+
*/
758+
export interface With300DocumentsSelect<T extends boolean = true> {
759+
text?: T;
760+
selfRelation?: T;
761+
updatedAt?: T;
762+
createdAt?: T;
763+
}
737764
/**
738765
* This interface was referenced by `Config`'s JSON-Schema
739766
* via the `definition` "payload-locked-documents_select".

test/admin/seed.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
noApiViewCollectionSlug,
1212
postsCollectionSlug,
1313
usersCollectionSlug,
14+
with300DocumentsSlug,
1415
} from './slugs.js'
1516

1617
export const seed = async (_payload) => {
@@ -117,6 +118,26 @@ export const seed = async (_payload) => {
117118
],
118119
false,
119120
)
121+
122+
// delete all with300Documents
123+
await _payload.delete({
124+
collection: with300DocumentsSlug,
125+
where: {},
126+
})
127+
128+
// Create 300 documents of with300Documents
129+
const manyDocumentsPromises: Promise<unknown>[] = Array.from({ length: 300 }, (_, i) => {
130+
const index = (i + 1).toString().padStart(3, '0')
131+
return _payload.create({
132+
collection: with300DocumentsSlug,
133+
data: {
134+
id: index,
135+
text: `document ${index}`,
136+
},
137+
})
138+
})
139+
140+
await Promise.all([...manyDocumentsPromises])
120141
}
121142

122143
export async function clearAndSeedEverything(_payload: Payload) {

test/admin/slugs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ export const globalSlugs = [
4848
hiddenGlobalSlug,
4949
noApiViewGlobalSlug,
5050
]
51+
export const with300DocumentsSlug = 'with300documents'

0 commit comments

Comments
 (0)