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
This commit is contained in:
James Mikrut
2024-07-25 17:42:36 -04:00
committed by GitHub
parent 8ba39aa5ca
commit 70f2e1698a
4 changed files with 157 additions and 20 deletions

View File

@@ -21,7 +21,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/co
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { CollectionSlug, GeneratedTypes } from '../../index.js'
import type { CollectionSlug } from '../../index.js'
import type { DocumentPreferences } from '../../preferences/types.js'
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
import type { ClientFieldConfig } from './client.js'
@@ -123,6 +123,10 @@ export type FilterOptionsProps<TData = any> = {
user: Partial<PayloadRequest['user']>
}
export type FilterOptionsFunc<TData = any> = (
options: FilterOptionsProps<TData>,
) => Promise<Where | boolean> | Where | boolean
export type FilterOptions<TData = any> =
| ((options: FilterOptionsProps<TData>) => Promise<Where | boolean> | Where | boolean)
| Where

View File

@@ -2,6 +2,7 @@ import type {
Data,
DocumentPreferences,
Field,
FilterOptionsResult,
FormField,
FormState,
PayloadRequest,
@@ -379,16 +380,31 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
}
case 'relationship': {
if (typeof field.filterOptions === 'function') {
const query = await getFilterOptionsQuery(field.filterOptions, {
id,
data: fullData,
relationTo: field.relationTo,
siblingData: data,
user: req.user,
})
if (field.filterOptions) {
if (typeof field.filterOptions === 'object') {
if (typeof field.relationTo === 'string') {
fieldState.filterOptions = {
[field.relationTo]: field.filterOptions,
}
} else {
fieldState.filterOptions = field.relationTo.reduce((acc, relation) => {
acc[relation] = field.filterOptions
return acc
}, {})
}
}
fieldState.filterOptions = query
if (typeof field.filterOptions === 'function') {
const query = await getFilterOptionsQuery(field.filterOptions, {
id,
data: fullData,
relationTo: field.relationTo,
siblingData: data,
user: req.user,
})
fieldState.filterOptions = query
}
}
if (field.hasMany) {
@@ -449,16 +465,24 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
}
case 'upload': {
if (typeof field.filterOptions === 'function') {
const query = await getFilterOptionsQuery(field.filterOptions, {
id,
data: fullData,
relationTo: field.relationTo,
siblingData: data,
user: req.user,
})
if (field.filterOptions) {
if (typeof field.filterOptions === 'object') {
fieldState.filterOptions = {
[field.relationTo]: field.filterOptions,
}
}
fieldState.filterOptions = query
if (typeof field.filterOptions === 'function') {
const query = await getFilterOptionsQuery(field.filterOptions, {
id,
data: fullData,
relationTo: field.relationTo,
siblingData: data,
user: req.user,
})
fieldState.filterOptions = query
}
}
const relationshipValue =

View File

@@ -300,6 +300,22 @@ describe('uploads', () => {
)
})
test('should restrict uploads in drawer based on filterOptions', async () => {
await page.goto(audioURL.edit(audioDoc.id))
await page.waitForURL(audioURL.edit(audioDoc.id))
// remove the selection and open the list drawer
await wait(500) // flake workaround
await page.locator('.file-details__remove').click()
await openDocDrawer(page, '.upload__toggler.list-drawer__toggler')
const listDrawer = page.locator('[id^=list-drawer_1_]')
await expect(listDrawer).toBeVisible()
await expect(listDrawer.locator('tbody tr')).toHaveCount(1)
})
test('should throw error when file is larger than the limit and abortOnLimit is true', async () => {
await page.goto(mediaURL.create)
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './2mb.jpg'))

View File

@@ -16,6 +16,9 @@ export interface Config {
'gif-resize': GifResize;
'no-image-sizes': NoImageSize;
'object-fit': ObjectFit;
'with-meta-data': WithMetaDatum;
'without-meta-data': WithoutMetaDatum;
'with-only-jpeg-meta-data': WithOnlyJpegMetaDatum;
'crop-only': CropOnly;
'focal-only': FocalOnly;
'focal-no-sizes': FocalNoSize;
@@ -38,6 +41,9 @@ export interface Config {
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
db: {
defaultIDType: string;
};
globals: {};
locale: null;
user: User & {
@@ -49,13 +55,16 @@ export interface UserAuthOperations {
email: string;
};
login: {
password: string;
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -344,6 +353,90 @@ export interface ObjectFit {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "with-meta-data".
*/
export interface WithMetaDatum {
id: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
sizeOne?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "without-meta-data".
*/
export interface WithoutMetaDatum {
id: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
sizeTwo?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "with-only-jpeg-meta-data".
*/
export interface WithOnlyJpegMetaDatum {
id: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
sizeThree?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "crop-only".