feat: working PoC for reusing relationship filters in validate

This commit is contained in:
James
2022-04-04 21:20:21 -04:00
parent 485991bd48
commit df934dfeff
9 changed files with 76 additions and 21 deletions

View File

@@ -38,7 +38,6 @@ export type Props = {
initialData?: Data
waitForAutocomplete?: boolean
log?: boolean
validationOperation?: 'create' | 'update'
}
export type SubmitOptions = {

View File

@@ -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',

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {

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

@@ -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 }) => {

View File

@@ -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'],
},