feat: working PoC for reusing relationship filters in validate
This commit is contained in:
@@ -38,7 +38,6 @@ export type Props = {
|
||||
initialData?: Data
|
||||
waitForAutocomplete?: boolean
|
||||
log?: boolean
|
||||
validationOperation?: 'create' | 'update'
|
||||
}
|
||||
|
||||
export type SubmitOptions = {
|
||||
|
||||
@@ -78,7 +78,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1);
|
||||
const [lastLoadedPage, setLastLoadedPage] = useState(1);
|
||||
const [errorLoading, setErrorLoading] = useState('');
|
||||
const [optionFilters, setOptionFilters] = useState<{[relation: string]: Where}>({});
|
||||
const [optionFilters, setOptionFilters] = useState<{[relation: string]: Where}>();
|
||||
const [hasLoadedValueOptions, setHasLoadedValueOptions] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
@@ -311,11 +311,13 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
}, [relationTo, filterOptions, optionFilters, id, getData, getSiblingData, path, user]);
|
||||
|
||||
useEffect(() => {
|
||||
setHasLoadedValueOptions(false);
|
||||
getResults({
|
||||
value: initialValue,
|
||||
});
|
||||
}, [initialValue, getResults]);
|
||||
if (optionFilters) {
|
||||
setHasLoadedValueOptions(false);
|
||||
getResults({
|
||||
value: initialValue,
|
||||
});
|
||||
}
|
||||
}, [initialValue, getResults, optionFilters]);
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
|
||||
@@ -53,7 +53,6 @@ const AddUploadModal: React.FC<Props> = (props) => {
|
||||
action={`${serverURL}${api}/${collection.slug}`}
|
||||
onSuccess={onSuccess}
|
||||
disableSuccessStatus
|
||||
validationOperation="create"
|
||||
>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<div>
|
||||
|
||||
@@ -65,7 +65,6 @@ const CreateFirstUser: React.FC<Props> = (props) => {
|
||||
method="post"
|
||||
redirect={admin}
|
||||
action={`${serverURL}${api}/${userSlug}/first-register`}
|
||||
validationOperation="create"
|
||||
>
|
||||
<NegativeFieldGutterProvider allow>
|
||||
<RenderFields
|
||||
|
||||
@@ -85,7 +85,6 @@ const DefaultEditView: React.FC<Props> = (props) => {
|
||||
onSuccess={onSave}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
validationOperation={isEditing ? 'update' : 'create'}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Meta
|
||||
|
||||
@@ -218,6 +218,16 @@ export type RelationshipField = FieldBase & {
|
||||
filterOptions?: Where | ((options: filterOptionsProps) => Where);
|
||||
}
|
||||
|
||||
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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
TextField,
|
||||
UploadField,
|
||||
Validate,
|
||||
RelationshipValue,
|
||||
ValueWithRelation,
|
||||
} from './config/types';
|
||||
import canUseDOM from '../utilities/canUseDOM';
|
||||
import payload from '../index';
|
||||
@@ -152,14 +154,19 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, {
|
||||
return defaultMessage;
|
||||
};
|
||||
|
||||
export const relationship: Validate<unknown, unknown, RelationshipField> = async (value: string | string[], { required, relationTo, filterOptions, id, data, siblingData, user }) => {
|
||||
export const relationship: Validate<unknown, unknown, RelationshipField> = async (value: RelationshipValue, { required, relationTo, filterOptions, id, data, siblingData, user }) => {
|
||||
if ((!value || (Array.isArray(value) && value.length === 0)) && required) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
if (!canUseDOM && typeof filterOptions !== 'undefined' && value) {
|
||||
const options = [];
|
||||
const options: {
|
||||
[collection: string]: (string | number)[]
|
||||
} = {};
|
||||
|
||||
const collections = typeof relationTo === 'string' ? [relationTo] : relationTo;
|
||||
const values: string[] = typeof value === 'string' ? [value] : value;
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
|
||||
await Promise.all(collections.map(async (collection) => {
|
||||
const optionFilter = typeof filterOptions === 'function' ? filterOptions({
|
||||
id,
|
||||
@@ -168,23 +175,63 @@ export const relationship: Validate<unknown, unknown, RelationshipField> = async
|
||||
user,
|
||||
relationTo: collection,
|
||||
}) : filterOptions;
|
||||
const result = await payload.find({
|
||||
|
||||
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: values } },
|
||||
{ id: { in: valueIDs } },
|
||||
optionFilter,
|
||||
],
|
||||
},
|
||||
});
|
||||
options.concat(result.docs.map((item: TypeWithID) => String(item.id)));
|
||||
|
||||
options[collection] = result.docs.map((doc) => doc.id);
|
||||
}));
|
||||
if (values.some((input) => options.some((option) => (option === input)))) {
|
||||
return 'This field has an invalid selection';
|
||||
|
||||
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 array: Validate<unknown, unknown, ArrayField> = (value, { minRows, maxRows, required }) => {
|
||||
|
||||
@@ -56,7 +56,7 @@ export default (config: SanitizedConfig): Configuration => ({
|
||||
'payload-user-css': config.admin.css,
|
||||
'payload-scss-overrides': config.admin.scss,
|
||||
dotenv: mockDotENVPath,
|
||||
[path.resolve(__dirname, '../index.ts')]: mockModulePath,
|
||||
[path.resolve(__dirname, '../index')]: mockModulePath,
|
||||
},
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user