Skip to content

Commit 70f2e16

Browse files
authored
fix: filterOptions for upload fields (#7347)
## Description Fixes uploads `filterOptions` not being respected in the Payload admin UI. Needs a test written, fixes to types in build, as well as any tests that fail due to this change in CI. - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] Existing test suite passes locally with my changes - [ ] I have made corresponding changes to the documentation
1 parent 8ba39aa commit 70f2e16

File tree

4 files changed

+159
-22
lines changed

4 files changed

+159
-22
lines changed

packages/payload/src/fields/config/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/co
2121
import type { CustomComponent, LabelFunction } from '../../config/types.js'
2222
import type { DBIdentifierName } from '../../database/types.js'
2323
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
24-
import type { CollectionSlug, GeneratedTypes } from '../../index.js'
24+
import type { CollectionSlug } from '../../index.js'
2525
import type { DocumentPreferences } from '../../preferences/types.js'
2626
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
2727
import type { ClientFieldConfig } from './client.js'
@@ -123,6 +123,10 @@ export type FilterOptionsProps<TData = any> = {
123123
user: Partial<PayloadRequest['user']>
124124
}
125125

126+
export type FilterOptionsFunc<TData = any> = (
127+
options: FilterOptionsProps<TData>,
128+
) => Promise<Where | boolean> | Where | boolean
129+
126130
export type FilterOptions<TData = any> =
127131
| ((options: FilterOptionsProps<TData>) => Promise<Where | boolean> | Where | boolean)
128132
| Where

packages/ui/src/forms/buildStateFromSchema/addFieldStatePromise.ts

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
Data,
33
DocumentPreferences,
44
Field,
5+
FilterOptionsResult,
56
FormField,
67
FormState,
78
PayloadRequest,
@@ -379,16 +380,31 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
379380
}
380381

381382
case 'relationship': {
382-
if (typeof field.filterOptions === 'function') {
383-
const query = await getFilterOptionsQuery(field.filterOptions, {
384-
id,
385-
data: fullData,
386-
relationTo: field.relationTo,
387-
siblingData: data,
388-
user: req.user,
389-
})
390-
391-
fieldState.filterOptions = query
383+
if (field.filterOptions) {
384+
if (typeof field.filterOptions === 'object') {
385+
if (typeof field.relationTo === 'string') {
386+
fieldState.filterOptions = {
387+
[field.relationTo]: field.filterOptions,
388+
}
389+
} else {
390+
fieldState.filterOptions = field.relationTo.reduce((acc, relation) => {
391+
acc[relation] = field.filterOptions
392+
return acc
393+
}, {})
394+
}
395+
}
396+
397+
if (typeof field.filterOptions === 'function') {
398+
const query = await getFilterOptionsQuery(field.filterOptions, {
399+
id,
400+
data: fullData,
401+
relationTo: field.relationTo,
402+
siblingData: data,
403+
user: req.user,
404+
})
405+
406+
fieldState.filterOptions = query
407+
}
392408
}
393409

394410
if (field.hasMany) {
@@ -449,16 +465,24 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
449465
}
450466

451467
case 'upload': {
452-
if (typeof field.filterOptions === 'function') {
453-
const query = await getFilterOptionsQuery(field.filterOptions, {
454-
id,
455-
data: fullData,
456-
relationTo: field.relationTo,
457-
siblingData: data,
458-
user: req.user,
459-
})
460-
461-
fieldState.filterOptions = query
468+
if (field.filterOptions) {
469+
if (typeof field.filterOptions === 'object') {
470+
fieldState.filterOptions = {
471+
[field.relationTo]: field.filterOptions,
472+
}
473+
}
474+
475+
if (typeof field.filterOptions === 'function') {
476+
const query = await getFilterOptionsQuery(field.filterOptions, {
477+
id,
478+
data: fullData,
479+
relationTo: field.relationTo,
480+
siblingData: data,
481+
user: req.user,
482+
})
483+
484+
fieldState.filterOptions = query
485+
}
462486
}
463487

464488
const relationshipValue =

test/uploads/e2e.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,22 @@ describe('uploads', () => {
300300
)
301301
})
302302

303+
test('should restrict uploads in drawer based on filterOptions', async () => {
304+
await page.goto(audioURL.edit(audioDoc.id))
305+
await page.waitForURL(audioURL.edit(audioDoc.id))
306+
307+
// remove the selection and open the list drawer
308+
await wait(500) // flake workaround
309+
await page.locator('.file-details__remove').click()
310+
311+
await openDocDrawer(page, '.upload__toggler.list-drawer__toggler')
312+
313+
const listDrawer = page.locator('[id^=list-drawer_1_]')
314+
await expect(listDrawer).toBeVisible()
315+
316+
await expect(listDrawer.locator('tbody tr')).toHaveCount(1)
317+
})
318+
303319
test('should throw error when file is larger than the limit and abortOnLimit is true', async () => {
304320
await page.goto(mediaURL.create)
305321
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './2mb.jpg'))

test/uploads/payload-types.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export interface Config {
1616
'gif-resize': GifResize;
1717
'no-image-sizes': NoImageSize;
1818
'object-fit': ObjectFit;
19+
'with-meta-data': WithMetaDatum;
20+
'without-meta-data': WithoutMetaDatum;
21+
'with-only-jpeg-meta-data': WithOnlyJpegMetaDatum;
1922
'crop-only': CropOnly;
2023
'focal-only': FocalOnly;
2124
'focal-no-sizes': FocalNoSize;
@@ -38,6 +41,9 @@ export interface Config {
3841
'payload-preferences': PayloadPreference;
3942
'payload-migrations': PayloadMigration;
4043
};
44+
db: {
45+
defaultIDType: string;
46+
};
4147
globals: {};
4248
locale: null;
4349
user: User & {
@@ -49,13 +55,16 @@ export interface UserAuthOperations {
4955
email: string;
5056
};
5157
login: {
52-
password: string;
5358
email: string;
59+
password: string;
5460
};
5561
registerFirstUser: {
5662
email: string;
5763
password: string;
5864
};
65+
unlock: {
66+
email: string;
67+
};
5968
}
6069
/**
6170
* This interface was referenced by `Config`'s JSON-Schema
@@ -344,6 +353,90 @@ export interface ObjectFit {
344353
};
345354
};
346355
}
356+
/**
357+
* This interface was referenced by `Config`'s JSON-Schema
358+
* via the `definition` "with-meta-data".
359+
*/
360+
export interface WithMetaDatum {
361+
id: string;
362+
updatedAt: string;
363+
createdAt: string;
364+
url?: string | null;
365+
thumbnailURL?: string | null;
366+
filename?: string | null;
367+
mimeType?: string | null;
368+
filesize?: number | null;
369+
width?: number | null;
370+
height?: number | null;
371+
focalX?: number | null;
372+
focalY?: number | null;
373+
sizes?: {
374+
sizeOne?: {
375+
url?: string | null;
376+
width?: number | null;
377+
height?: number | null;
378+
mimeType?: string | null;
379+
filesize?: number | null;
380+
filename?: string | null;
381+
};
382+
};
383+
}
384+
/**
385+
* This interface was referenced by `Config`'s JSON-Schema
386+
* via the `definition` "without-meta-data".
387+
*/
388+
export interface WithoutMetaDatum {
389+
id: string;
390+
updatedAt: string;
391+
createdAt: string;
392+
url?: string | null;
393+
thumbnailURL?: string | null;
394+
filename?: string | null;
395+
mimeType?: string | null;
396+
filesize?: number | null;
397+
width?: number | null;
398+
height?: number | null;
399+
focalX?: number | null;
400+
focalY?: number | null;
401+
sizes?: {
402+
sizeTwo?: {
403+
url?: string | null;
404+
width?: number | null;
405+
height?: number | null;
406+
mimeType?: string | null;
407+
filesize?: number | null;
408+
filename?: string | null;
409+
};
410+
};
411+
}
412+
/**
413+
* This interface was referenced by `Config`'s JSON-Schema
414+
* via the `definition` "with-only-jpeg-meta-data".
415+
*/
416+
export interface WithOnlyJpegMetaDatum {
417+
id: string;
418+
updatedAt: string;
419+
createdAt: string;
420+
url?: string | null;
421+
thumbnailURL?: string | null;
422+
filename?: string | null;
423+
mimeType?: string | null;
424+
filesize?: number | null;
425+
width?: number | null;
426+
height?: number | null;
427+
focalX?: number | null;
428+
focalY?: number | null;
429+
sizes?: {
430+
sizeThree?: {
431+
url?: string | null;
432+
width?: number | null;
433+
height?: number | null;
434+
mimeType?: string | null;
435+
filesize?: number | null;
436+
filename?: string | null;
437+
};
438+
};
439+
}
347440
/**
348441
* This interface was referenced by `Config`'s JSON-Schema
349442
* via the `definition` "crop-only".

0 commit comments

Comments
 (0)