merge: relationship options filter

This commit is contained in:
James
2022-04-05 14:59:01 -04:00
19 changed files with 368 additions and 71 deletions

View File

@@ -185,6 +185,10 @@ export const upload = baseField.keys({
relationTo: joi.string().required(),
name: joi.string().required(),
maxDepth: joi.number(),
filterOptions: joi.alternatives().try(
joi.object(),
joi.func(),
),
});
export const checkbox = baseField.keys({
@@ -208,6 +212,10 @@ export const relationship = baseField.keys({
),
name: joi.string().required(),
maxDepth: joi.number(),
filterOptions: joi.alternatives().try(
joi.object(),
joi.func(),
),
});
export const blocks = baseField.keys({

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-use-before-define */
import { CSSProperties } from 'react';
import { Editor } from 'slate';
import { Operation } from '../../types';
import { Operation, Where } from '../../types';
import { TypeWithID } from '../../collections/config/types';
import { PayloadRequest } from '../../express/types';
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
@@ -29,6 +29,16 @@ export type FieldAccess<T extends TypeWithID = any, P = any> = (args: {
export type Condition<T extends TypeWithID = any, P = any> = (data: Partial<T>, siblingData: Partial<P>) => boolean;
export type FilterOptionsProps = {
id: string | number,
user: Partial<User>,
data: unknown,
siblingData: unknown,
relationTo: string | string[],
}
export type FilterOptions = Where | ((options: FilterOptionsProps) => Where);
type Admin = {
position?: 'sidebar';
width?: string;
@@ -183,6 +193,7 @@ export type UploadField = FieldBase & {
type: 'upload'
relationTo: string
maxDepth?: number
filterOptions?: FilterOptions;
}
type CodeAdmin = Admin & {
@@ -207,8 +218,19 @@ export type RelationshipField = FieldBase & {
relationTo: string | string[];
hasMany?: boolean;
maxDepth?: number;
filterOptions?: FilterOptions;
}
export type ValueWithRelation = {
relationTo: string
value: string | number
}
export type RelationshipValue = (string | number)
| (string | number)[]
| ValueWithRelation
| ValueWithRelation[]
type RichTextPlugin = (editor: Editor) => Editor;
export type RichTextCustomElement = {

View File

@@ -116,8 +116,8 @@ export default async function performFieldOperations(this: Payload, entityConfig
const hookResults = hookPromises.map((promise) => promise());
await Promise.all(hookResults);
validationPromises.forEach((promise) => promise());
await Promise.all(validationPromises);
const validationResults = validationPromises.map((promise) => promise());
await Promise.all(validationResults);
if (errors.length > 0) {
throw new ValidationError(errors);

View File

@@ -3,12 +3,14 @@ import {
ArrayField,
BlockField,
CheckboxField,
CodeField, DateField,
CodeField,
DateField,
EmailField,
NumberField,
PointField,
RadioField,
RelationshipField,
RelationshipValue,
RichTextField,
SelectField,
TextareaField,
@@ -16,6 +18,9 @@ import {
UploadField,
Validate,
} from './config/types';
import { TypeWithID } from '../collections/config/types';
import canUseDOM from '../utilities/canUseDOM';
import payload from '../index';
const defaultMessage = 'This field is required.';
@@ -84,7 +89,11 @@ export const email: Validate<unknown, unknown, EmailField> = (value: string, { r
return true;
};
export const textarea: Validate<unknown, unknown, TextareaField> = (value: string, { required, maxLength, minLength }) => {
export const textarea: Validate<unknown, unknown, TextareaField> = (value: string, {
required,
maxLength,
minLength,
}) => {
if (value && maxLength && value.length > maxLength) {
return `This value must be shorter than the max length of ${maxLength} characters.`;
}
@@ -143,14 +152,96 @@ export const date: Validate<unknown, unknown, DateField> = (value, { required })
return true;
};
export const upload: Validate<unknown, unknown, UploadField> = (value: string, { required }) => {
if (value || !required) return true;
return defaultMessage;
const validateFilterOptions: Validate = async (value, { filterOptions, id, user, data, siblingData, relationTo }) => {
if (!canUseDOM && typeof filterOptions !== 'undefined' && value) {
const options: {
[collection: string]: (string | number)[]
} = {};
const collections = typeof relationTo === 'string' ? [relationTo] : relationTo;
const values = Array.isArray(value) ? value : [value];
await Promise.all(collections.map(async (collection) => {
const optionFilter = typeof filterOptions === 'function' ? filterOptions({
id,
data,
siblingData,
user,
relationTo: collection,
}) : filterOptions;
const valueIDs: (string | number)[] = [];
values.forEach((val) => {
if (typeof val === 'object' && val?.value) {
valueIDs.push(val.value);
}
if (typeof val === 'string' || typeof val === 'number') {
valueIDs.push(val);
}
});
const result = await payload.find<TypeWithID>({
collection,
depth: 0,
where: {
and: [
{ id: { in: valueIDs } },
optionFilter,
],
},
});
options[collection] = result.docs.map((doc) => doc.id);
}));
const invalidRelationships = values.filter((val) => {
let collection: string;
let requestedID: string | number;
if (typeof relationTo === 'string') {
collection = relationTo;
if (typeof val === 'string' || typeof val === 'number') {
requestedID = val;
}
}
if (Array.isArray(relationTo) && typeof val === 'object' && val?.relationTo) {
collection = val.relationTo;
requestedID = val.value;
}
return options[collection].indexOf(requestedID) === -1;
});
if (invalidRelationships.length > 0) {
return invalidRelationships.reduce((err, invalid, i) => {
return `${err} ${JSON.stringify(invalid)}${invalidRelationships.length === i + 1 ? ',' : ''} `;
}, 'This field has the following invalid selections:') as string;
}
return true;
}
return true;
};
export const relationship: Validate<unknown, unknown, RelationshipField> = (value, { required }) => {
if (value || !required) return true;
return defaultMessage;
export const upload: Validate<unknown, unknown, UploadField> = (value: string, options) => {
if (!value && options.required) {
return defaultMessage;
}
return validateFilterOptions(value, options);
};
export const relationship: Validate<unknown, unknown, RelationshipField> = async (value: RelationshipValue, options) => {
if ((!value || (Array.isArray(value) && value.length === 0)) && options.required) {
return defaultMessage;
}
return validateFilterOptions(value, options);
};
export const array: Validate<unknown, unknown, ArrayField> = (value, { minRows, maxRows, required }) => {