feat: ensures nested fields not affecting data can be traversed properly
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import flattenTopLevelFields from '../../../../utilities/flattenTopLevelFields';
|
||||
import flattenTopLevelFields from '../../../utilities/flattenTopLevelFields';
|
||||
import Pill from '../Pill';
|
||||
import Plus from '../../icons/Plus';
|
||||
import X from '../../icons/X';
|
||||
@@ -16,7 +16,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
|
||||
setColumns,
|
||||
} = props;
|
||||
|
||||
const [fields] = useState(() => flattenTopLevelFields(collection.fields));
|
||||
const [fields] = useState(() => flattenTopLevelFields(collection.fields, true));
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import Button from '../Button';
|
||||
import reducer from './reducer';
|
||||
import Condition from './Condition';
|
||||
import fieldTypes from './field-types';
|
||||
import flattenTopLevelFields from '../../../../utilities/flattenTopLevelFields';
|
||||
import flattenTopLevelFields from '../../../utilities/flattenTopLevelFields';
|
||||
import { useSearchParams } from '../../utilities/SearchParams';
|
||||
import validateWhereQuery from './validateWhereQuery';
|
||||
import { Where } from '../../../../types';
|
||||
|
||||
@@ -3,75 +3,51 @@ import Cell from './Cell';
|
||||
import SortColumn from '../../../elements/SortColumn';
|
||||
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
|
||||
import { Column } from '../../../elements/Table/types';
|
||||
import { fieldHasSubFields, Field, fieldAffectsData, fieldIsPresentationalOnly } from '../../../../../fields/config/types';
|
||||
import { fieldIsPresentationalOnly } from '../../../../../fields/config/types';
|
||||
import flattenFields from '../../../../utilities/flattenTopLevelFields';
|
||||
|
||||
const buildColumns = (collection: SanitizedCollectionConfig, columns: string[]): Column[] => (columns || []).reduce((cols, col, colIndex) => {
|
||||
let field = null;
|
||||
const buildColumns = (collection: SanitizedCollectionConfig, columns: string[]): Column[] => {
|
||||
const flattenedFields = flattenFields(collection.fields, true);
|
||||
|
||||
const fields = [
|
||||
...collection.fields,
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
label: 'ID',
|
||||
} as Field,
|
||||
{
|
||||
name: 'updatedAt',
|
||||
type: 'date',
|
||||
label: 'Updated At',
|
||||
} as Field,
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
label: 'Created At',
|
||||
} as Field,
|
||||
];
|
||||
return (columns || []).reduce((cols, col, colIndex) => {
|
||||
let field = null;
|
||||
|
||||
fields.forEach((fieldToCheck) => {
|
||||
if ((fieldAffectsData(fieldToCheck) || fieldIsPresentationalOnly(fieldToCheck)) && fieldToCheck.name === col) {
|
||||
field = fieldToCheck;
|
||||
}
|
||||
flattenedFields.forEach((fieldToCheck) => {
|
||||
if (fieldToCheck.name === col) {
|
||||
field = fieldToCheck;
|
||||
}
|
||||
});
|
||||
|
||||
if (!fieldAffectsData(fieldToCheck) && fieldHasSubFields(fieldToCheck)) {
|
||||
fieldToCheck.fields.forEach((subField) => {
|
||||
if (fieldAffectsData(subField) && subField.name === col) {
|
||||
field = subField;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (field) {
|
||||
return [
|
||||
...cols,
|
||||
{
|
||||
accessor: field.name,
|
||||
components: {
|
||||
Heading: (
|
||||
<SortColumn
|
||||
label={field.label || field.name}
|
||||
name={field.name}
|
||||
disable={(field.disableSort || fieldIsPresentationalOnly(field)) || undefined}
|
||||
/>
|
||||
),
|
||||
renderCell: (rowData, cellData) => (
|
||||
<Cell
|
||||
key={JSON.stringify(cellData)}
|
||||
field={field}
|
||||
colIndex={colIndex}
|
||||
collection={collection}
|
||||
rowData={rowData}
|
||||
cellData={cellData}
|
||||
/>
|
||||
),
|
||||
if (field) {
|
||||
return [
|
||||
...cols,
|
||||
{
|
||||
accessor: field.name,
|
||||
components: {
|
||||
Heading: (
|
||||
<SortColumn
|
||||
label={field.label || field.name}
|
||||
name={field.name}
|
||||
disable={(field.disableSort || fieldIsPresentationalOnly(field)) || undefined}
|
||||
/>
|
||||
),
|
||||
renderCell: (rowData, cellData) => (
|
||||
<Cell
|
||||
key={JSON.stringify(cellData)}
|
||||
field={field}
|
||||
colIndex={colIndex}
|
||||
collection={collection}
|
||||
rowData={rowData}
|
||||
cellData={cellData}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return cols;
|
||||
}, []);
|
||||
return cols;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default buildColumns;
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
import { Field, fieldHasSubFields, fieldAffectsData } from '../../../../../fields/config/types';
|
||||
|
||||
const getRemainingColumns = (fields: Field[], useAsTitle: string): string[] => fields.reduce((remaining, field) => {
|
||||
if (fieldAffectsData(field) && field.name === useAsTitle) {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
if (!fieldAffectsData(field) && fieldHasSubFields(field)) {
|
||||
return [
|
||||
...remaining,
|
||||
...getRemainingColumns(field.fields, useAsTitle),
|
||||
];
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
return [
|
||||
...remaining,
|
||||
...field.tabs.reduce((tabFieldColumns, tab) => [
|
||||
...tabFieldColumns,
|
||||
...getRemainingColumns(tab.fields, useAsTitle),
|
||||
], []),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...remaining,
|
||||
field.name,
|
||||
];
|
||||
}, []);
|
||||
|
||||
const getInitialColumnState = (fields: Field[], useAsTitle: string, defaultColumns: string[]): string[] => {
|
||||
let initialColumns = [];
|
||||
|
||||
@@ -12,32 +40,7 @@ const getInitialColumnState = (fields: Field[], useAsTitle: string, defaultColum
|
||||
initialColumns.push(useAsTitle);
|
||||
}
|
||||
|
||||
const remainingColumns = fields.reduce((remaining, field) => {
|
||||
if (fieldAffectsData(field) && field.name === useAsTitle) {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
if (!fieldAffectsData(field) && fieldHasSubFields(field)) {
|
||||
return [
|
||||
...remaining,
|
||||
...field.fields.reduce((subFields, subField) => {
|
||||
if (fieldAffectsData(subField)) {
|
||||
return [
|
||||
...subFields,
|
||||
subField.name,
|
||||
];
|
||||
}
|
||||
|
||||
return subFields;
|
||||
}, []),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...remaining,
|
||||
field.name,
|
||||
];
|
||||
}, []);
|
||||
const remainingColumns = getRemainingColumns(fields, useAsTitle);
|
||||
|
||||
initialColumns = initialColumns.concat(remainingColumns);
|
||||
initialColumns = initialColumns.slice(0, 4);
|
||||
|
||||
35
src/admin/utilities/flattenTopLevelFields.ts
Normal file
35
src/admin/utilities/flattenTopLevelFields.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Field, FieldAffectingData, fieldAffectsData, fieldHasSubFields, fieldIsPresentationalOnly, FieldPresentationalOnly } from '../../fields/config/types';
|
||||
|
||||
const flattenFields = (fields: Field[], keepPresentationalFields?: boolean): (FieldAffectingData | FieldPresentationalOnly)[] => {
|
||||
return fields.reduce((fieldsToUse, field) => {
|
||||
if (fieldAffectsData(field) || (keepPresentationalFields && fieldIsPresentationalOnly(field))) {
|
||||
return [
|
||||
...fieldsToUse,
|
||||
field,
|
||||
];
|
||||
}
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...flattenFields(field.fields, keepPresentationalFields),
|
||||
];
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...field.tabs.reduce((tabFields, tab) => {
|
||||
return [
|
||||
...tabFields,
|
||||
...flattenFields(tab.fields, keepPresentationalFields),
|
||||
];
|
||||
}, []),
|
||||
];
|
||||
}
|
||||
|
||||
return fieldsToUse;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default flattenFields;
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
|
||||
import formatName from '../../graphql/utilities/formatName';
|
||||
import buildPaginatedListType from '../../graphql/schema/buildPaginatedListType';
|
||||
import { BaseFields } from './types';
|
||||
import buildMutationInputType, { getCollectionIDType } from '../../graphql/schema/buildMutationInputType';
|
||||
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields';
|
||||
import createResolver from './resolvers/create';
|
||||
@@ -31,7 +30,7 @@ import unlock from '../../auth/graphql/resolvers/unlock';
|
||||
import refresh from '../../auth/graphql/resolvers/refresh';
|
||||
import { Payload } from '../..';
|
||||
import { Field, fieldAffectsData } from '../../fields/config/types';
|
||||
import buildObjectType from '../../graphql/schema/buildObjectType';
|
||||
import buildObjectType, { ObjectTypeConfig } from '../../graphql/schema/buildObjectType';
|
||||
import buildWhereInputType from '../../graphql/schema/buildWhereInputType';
|
||||
import getDeleteResolver from './resolvers/delete';
|
||||
|
||||
@@ -66,7 +65,7 @@ function initCollectionsGraphQL(payload: Payload): void {
|
||||
const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id');
|
||||
const idType = getCollectionIDType(collection.config);
|
||||
|
||||
const baseFields: BaseFields = {};
|
||||
const baseFields: ObjectTypeConfig = {};
|
||||
|
||||
const whereInputFields = [
|
||||
...fields,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
GraphQLFloat,
|
||||
GraphQLInputFieldConfig,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
@@ -15,7 +16,7 @@ import { GraphQLJSON } from 'graphql-type-json';
|
||||
import withNullableType from './withNullableType';
|
||||
import formatName from '../utilities/formatName';
|
||||
import combineParentName from '../utilities/combineParentName';
|
||||
import { ArrayField, CodeField, DateField, EmailField, Field, fieldHasSubFields, fieldAffectsData, fieldIsPresentationalOnly, GroupField, NumberField, PointField, RadioField, RelationshipField, RichTextField, RowField, SelectField, TextareaField, TextField, UploadField, CollapsibleField, TabsField } from '../../fields/config/types';
|
||||
import { ArrayField, CodeField, DateField, EmailField, Field, fieldAffectsData, fieldIsPresentationalOnly, GroupField, NumberField, PointField, RadioField, RelationshipField, RichTextField, RowField, SelectField, TextareaField, TextField, UploadField, CollapsibleField, TabsField, CheckboxField, BlockField } from '../../fields/config/types';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
import { Payload } from '../../index';
|
||||
import { SanitizedCollectionConfig } from '../../collections/config/types';
|
||||
@@ -31,23 +32,60 @@ export const getCollectionIDType = (config: SanitizedCollectionConfig): GraphQLS
|
||||
}
|
||||
};
|
||||
|
||||
export type InputObjectTypeConfig = {
|
||||
[path: string]: GraphQLInputFieldConfig
|
||||
}
|
||||
|
||||
function buildMutationInputType(payload: Payload, name: string, fields: Field[], parentName: string, forceNullable = false): GraphQLInputObjectType {
|
||||
const fieldToSchemaMap = {
|
||||
number: (field: NumberField) => {
|
||||
number: (inputObjectTypeConfig: InputObjectTypeConfig, field: NumberField) => {
|
||||
const type = field.name === 'id' ? GraphQLInt : GraphQLFloat;
|
||||
return { type: withNullableType(field, type, forceNullable) };
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, type, forceNullable) },
|
||||
};
|
||||
},
|
||||
text: (field: TextField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
email: (field: EmailField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
textarea: (field: TextareaField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
richText: (field: RichTextField) => ({ type: withNullableType(field, GraphQLJSON, forceNullable) }),
|
||||
code: (field: CodeField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
date: (field: DateField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
upload: (field: UploadField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
radio: (field: RadioField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
|
||||
point: (field: PointField) => ({ type: withNullableType(field, GraphQLList(GraphQLFloat), forceNullable) }),
|
||||
checkbox: () => ({ type: GraphQLBoolean }),
|
||||
select: (field: SelectField) => {
|
||||
text: (inputObjectTypeConfig: InputObjectTypeConfig, field: TextField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
email: (inputObjectTypeConfig: InputObjectTypeConfig, field: EmailField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
textarea: (inputObjectTypeConfig: InputObjectTypeConfig, field: TextareaField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
richText: (inputObjectTypeConfig: InputObjectTypeConfig, field: RichTextField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLJSON, forceNullable) },
|
||||
}),
|
||||
code: (inputObjectTypeConfig: InputObjectTypeConfig, field: CodeField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
date: (inputObjectTypeConfig: InputObjectTypeConfig, field: DateField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
upload: (inputObjectTypeConfig: InputObjectTypeConfig, field: UploadField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
radio: (inputObjectTypeConfig: InputObjectTypeConfig, field: RadioField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
}),
|
||||
point: (inputObjectTypeConfig: InputObjectTypeConfig, field: PointField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLList(GraphQLFloat), forceNullable) },
|
||||
}),
|
||||
checkbox: (inputObjectTypeConfig: InputObjectTypeConfig, field: CheckboxField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: GraphQLBoolean },
|
||||
}),
|
||||
select: (inputObjectTypeConfig: InputObjectTypeConfig, field: SelectField) => {
|
||||
const formattedName = `${combineParentName(parentName, field.name)}_MutationInput`;
|
||||
let type: GraphQLType = new GraphQLEnumType({
|
||||
name: formattedName,
|
||||
@@ -77,9 +115,12 @@ function buildMutationInputType(payload: Payload, name: string, fields: Field[],
|
||||
type = field.hasMany ? new GraphQLList(type) : type;
|
||||
type = withNullableType(field, type, forceNullable);
|
||||
|
||||
return { type };
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
relationship: (field: RelationshipField) => {
|
||||
relationship: (inputObjectTypeConfig: InputObjectTypeConfig, field: RelationshipField) => {
|
||||
const { relationTo } = field;
|
||||
type PayloadGraphQLRelationshipType = GraphQLScalarType | GraphQLList<GraphQLScalarType> | GraphQLInputObjectType;
|
||||
let type: PayloadGraphQLRelationshipType;
|
||||
@@ -107,138 +148,65 @@ function buildMutationInputType(payload: Payload, name: string, fields: Field[],
|
||||
type = getCollectionIDType(payload.collections[relationTo].config);
|
||||
}
|
||||
|
||||
return { type: field.hasMany ? new GraphQLList(type) : type };
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: field.hasMany ? new GraphQLList(type) : type },
|
||||
};
|
||||
},
|
||||
array: (field: ArrayField) => {
|
||||
array: (inputObjectTypeConfig: InputObjectTypeConfig, field: ArrayField) => {
|
||||
const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
|
||||
let type: GraphQLType | GraphQLList<GraphQLType> = buildMutationInputType(payload, fullName, field.fields, fullName);
|
||||
type = new GraphQLList(withNullableType(field, type, forceNullable));
|
||||
return { type };
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
group: (field: GroupField) => {
|
||||
group: (inputObjectTypeConfig: InputObjectTypeConfig, field: GroupField) => {
|
||||
const requiresAtLeastOneField = field.fields.some((subField) => (!fieldIsPresentationalOnly(subField) && subField.required && !subField.localized));
|
||||
const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
|
||||
let type: GraphQLType = buildMutationInputType(payload, fullName, field.fields, fullName);
|
||||
if (requiresAtLeastOneField) type = new GraphQLNonNull(type);
|
||||
return { type };
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
blocks: () => ({ type: GraphQLJSON }),
|
||||
row: (field: RowField) => field.fields.reduce((acc, rowField: RowField) => {
|
||||
const getFieldSchema = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(rowField);
|
||||
|
||||
return [
|
||||
...acc,
|
||||
fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []),
|
||||
collapsible: (field: CollapsibleField) => field.fields.reduce((acc, collapsibleField: CollapsibleField) => {
|
||||
const getFieldSchema = fieldToSchemaMap[collapsibleField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(collapsibleField);
|
||||
|
||||
return [
|
||||
...acc,
|
||||
fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []),
|
||||
tabs: (field: TabsField) => field.tabs.reduce((acc, tab) => {
|
||||
const test = [
|
||||
blocks: (inputObjectTypeConfig: InputObjectTypeConfig, field: BlockField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: GraphQLJSON },
|
||||
}),
|
||||
row: (inputObjectTypeConfig: InputObjectTypeConfig, field: RowField) => field.fields.reduce((acc, subField: Field) => {
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(acc, subField);
|
||||
}, inputObjectTypeConfig),
|
||||
collapsible: (inputObjectTypeConfig: InputObjectTypeConfig, field: CollapsibleField) => field.fields.reduce((acc, subField: CollapsibleField) => {
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(acc, subField);
|
||||
}, inputObjectTypeConfig),
|
||||
tabs: (inputObjectTypeConfig: InputObjectTypeConfig, field: TabsField) => field.tabs.reduce((acc, tab) => {
|
||||
return {
|
||||
...acc,
|
||||
...tab.fields.reduce((subAcc, rowField: TabsField) => {
|
||||
const getFieldSchema = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(rowField);
|
||||
|
||||
return [
|
||||
...subAcc,
|
||||
fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return subAcc;
|
||||
}, []),
|
||||
];
|
||||
|
||||
return test;
|
||||
}, []),
|
||||
...tab.fields.reduce((subFieldSchema, subField) => {
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(subFieldSchema, subField);
|
||||
}, acc),
|
||||
};
|
||||
}, inputObjectTypeConfig),
|
||||
};
|
||||
|
||||
const fieldTypes = fields.reduce((schema, field: Field) => {
|
||||
if (!fieldIsPresentationalOnly(field) && !field.hidden) {
|
||||
const getFieldSchema: (field: Field) => { type: GraphQLType } = fieldToSchemaMap[field.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(field);
|
||||
|
||||
if (Array.isArray(fieldSchema)) {
|
||||
let subFields: Field[] = [];
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
subFields = field.fields;
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
subFields = field.tabs.reduce((flattenedFields, tab) => {
|
||||
return [
|
||||
...flattenedFields,
|
||||
...tab.fields,
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
|
||||
if (subFields.length > 0) {
|
||||
return fieldSchema.reduce((acc, subField, i) => {
|
||||
const currentSubField = subFields[i];
|
||||
if (fieldAffectsData(currentSubField)) {
|
||||
return {
|
||||
...acc,
|
||||
[currentSubField.name]: subField,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...fieldSchema,
|
||||
};
|
||||
}, schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
return {
|
||||
...schema,
|
||||
[field.name]: fieldSchema,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...schema,
|
||||
...fieldSchema,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
}, {});
|
||||
|
||||
const fieldName = formatName(name);
|
||||
|
||||
return new GraphQLInputObjectType({
|
||||
name: `mutation${fieldName}Input`,
|
||||
fields: {
|
||||
...fieldTypes,
|
||||
},
|
||||
fields: fields.reduce((inputObjectTypeConfig, field) => {
|
||||
const fieldSchema = fieldToSchemaMap[field.type];
|
||||
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
...fieldSchema(inputObjectTypeConfig, field),
|
||||
};
|
||||
}, {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import {
|
||||
GraphQLBoolean, GraphQLEnumType,
|
||||
GraphQLFieldConfig,
|
||||
GraphQLFloat,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
@@ -14,11 +15,10 @@ import {
|
||||
GraphQLUnionType,
|
||||
} from 'graphql';
|
||||
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars';
|
||||
import { Field, RadioField, RelationshipField, SelectField, UploadField, ArrayField, GroupField, RichTextField, fieldAffectsData, NumberField, TextField, EmailField, TextareaField, CodeField, DateField, PointField, CheckboxField, BlockField, RowField, fieldIsPresentationalOnly } from '../../fields/config/types';
|
||||
import { Field, RadioField, RelationshipField, SelectField, UploadField, ArrayField, GroupField, RichTextField, fieldAffectsData, NumberField, TextField, EmailField, TextareaField, CodeField, DateField, PointField, CheckboxField, BlockField, RowField, fieldIsPresentationalOnly, CollapsibleField, TabsField } from '../../fields/config/types';
|
||||
import formatName from '../utilities/formatName';
|
||||
import combineParentName from '../utilities/combineParentName';
|
||||
import withNullableType from './withNullableType';
|
||||
import { BaseFields } from '../../collections/graphql/types';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
import createRichTextRelationshipPromise from '../../fields/richText/relationshipPromise';
|
||||
import formatOptions from '../utilities/formatOptions';
|
||||
@@ -39,37 +39,65 @@ type LocaleInputType = {
|
||||
}
|
||||
}
|
||||
|
||||
function buildObjectType(payload: Payload, name: string, fields: Field[], parentName: string, baseFields: BaseFields = {}): GraphQLObjectType {
|
||||
const fieldToSchemaMap = {
|
||||
number: (field: NumberField) => ({ type: withNullableType(field, GraphQLFloat) }),
|
||||
text: (field: TextField) => ({ type: withNullableType(field, GraphQLString) }),
|
||||
email: (field: EmailField) => ({ type: withNullableType(field, EmailAddressResolver) }),
|
||||
textarea: (field: TextareaField) => ({ type: withNullableType(field, GraphQLString) }),
|
||||
code: (field: CodeField) => ({ type: withNullableType(field, GraphQLString) }),
|
||||
date: (field: DateField) => ({ type: withNullableType(field, DateTimeResolver) }),
|
||||
point: (field: PointField) => ({ type: withNullableType(field, new GraphQLList(GraphQLFloat)) }),
|
||||
richText: (field: RichTextField) => ({
|
||||
type: withNullableType(field, GraphQLJSON),
|
||||
async resolve(parent, args, context) {
|
||||
if (args.depth > 0) {
|
||||
await createRichTextRelationshipPromise({
|
||||
req: context.req,
|
||||
siblingDoc: parent,
|
||||
depth: args.depth,
|
||||
field,
|
||||
showHiddenFields: false,
|
||||
});
|
||||
}
|
||||
export type ObjectTypeConfig = {
|
||||
[path: string]: GraphQLFieldConfig<any, any>
|
||||
}
|
||||
|
||||
return parent[field.name];
|
||||
},
|
||||
args: {
|
||||
depth: {
|
||||
type: GraphQLInt,
|
||||
function buildObjectType(payload: Payload, name: string, fields: Field[], parentName: string, baseFields: ObjectTypeConfig = {}): GraphQLObjectType {
|
||||
const fieldToSchemaMap = {
|
||||
number: (objectTypeConfig: ObjectTypeConfig, field: NumberField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLFloat) },
|
||||
}),
|
||||
text: (objectTypeConfig: ObjectTypeConfig, field: TextField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString) },
|
||||
}),
|
||||
email: (objectTypeConfig: ObjectTypeConfig, field: EmailField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, EmailAddressResolver) },
|
||||
}),
|
||||
textarea: (objectTypeConfig: ObjectTypeConfig, field: TextareaField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString) },
|
||||
}),
|
||||
code: (objectTypeConfig: ObjectTypeConfig, field: CodeField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString) },
|
||||
}),
|
||||
date: (objectTypeConfig: ObjectTypeConfig, field: DateField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, DateTimeResolver) },
|
||||
}),
|
||||
point: (objectTypeConfig: ObjectTypeConfig, field: PointField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, new GraphQLList(GraphQLFloat)) },
|
||||
}),
|
||||
richText: (objectTypeConfig: ObjectTypeConfig, field: RichTextField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(field, GraphQLJSON),
|
||||
async resolve(parent, args, context) {
|
||||
if (args.depth > 0) {
|
||||
await createRichTextRelationshipPromise({
|
||||
req: context.req,
|
||||
siblingDoc: parent,
|
||||
depth: args.depth,
|
||||
field,
|
||||
showHiddenFields: false,
|
||||
});
|
||||
}
|
||||
|
||||
return parent[field.name];
|
||||
},
|
||||
args: {
|
||||
depth: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
upload: (field: UploadField) => {
|
||||
upload: (objectTypeConfig: ObjectTypeConfig, field: UploadField) => {
|
||||
const { relationTo, label } = field;
|
||||
|
||||
const uploadName = combineParentName(parentName, label === false ? toWords(field.name, true) : label);
|
||||
@@ -149,19 +177,28 @@ function buildObjectType(payload: Payload, name: string, fields: Field[], parent
|
||||
),
|
||||
};
|
||||
|
||||
return upload;
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: upload,
|
||||
};
|
||||
},
|
||||
radio: (field: RadioField) => ({
|
||||
type: withNullableType(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: combineParentName(parentName, field.name),
|
||||
values: formatOptions(field),
|
||||
}),
|
||||
),
|
||||
radio: (objectTypeConfig: ObjectTypeConfig, field: RadioField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: combineParentName(parentName, field.name),
|
||||
values: formatOptions(field),
|
||||
}),
|
||||
),
|
||||
},
|
||||
}),
|
||||
checkbox: (field: CheckboxField) => ({ type: withNullableType(field, GraphQLBoolean) }),
|
||||
select: (field: SelectField) => {
|
||||
checkbox: (objectTypeConfig: ObjectTypeConfig, field: CheckboxField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLBoolean) },
|
||||
}),
|
||||
select: (objectTypeConfig: ObjectTypeConfig, field: SelectField) => {
|
||||
const fullName = combineParentName(parentName, field.name);
|
||||
|
||||
let type: GraphQLType = new GraphQLEnumType({
|
||||
@@ -172,9 +209,12 @@ function buildObjectType(payload: Payload, name: string, fields: Field[], parent
|
||||
type = field.hasMany ? new GraphQLList(type) : type;
|
||||
type = withNullableType(field, type);
|
||||
|
||||
return { type };
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
relationship: (field: RelationshipField) => {
|
||||
relationship: (objectTypeConfig: ObjectTypeConfig, field: RelationshipField) => {
|
||||
const { relationTo, label } = field;
|
||||
const isRelatedToManyCollections = Array.isArray(relationTo);
|
||||
const hasManyValues = field.hasMany;
|
||||
@@ -388,22 +428,31 @@ function buildObjectType(payload: Payload, name: string, fields: Field[], parent
|
||||
};
|
||||
}
|
||||
|
||||
return relationship;
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: relationship,
|
||||
};
|
||||
},
|
||||
array: (field: ArrayField) => {
|
||||
array: (objectTypeConfig: ObjectTypeConfig, field: ArrayField) => {
|
||||
const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
|
||||
const type = buildObjectType(payload, fullName, field.fields, fullName);
|
||||
const arrayType = new GraphQLList(withNullableType(field, type));
|
||||
|
||||
return { type: arrayType };
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: arrayType },
|
||||
};
|
||||
},
|
||||
group: (field: GroupField) => {
|
||||
group: (objectTypeConfig: ObjectTypeConfig, field: GroupField) => {
|
||||
const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
|
||||
const type = buildObjectType(payload, fullName, field.fields, fullName);
|
||||
|
||||
return { type };
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
blocks: (field: BlockField) => {
|
||||
blocks: (objectTypeConfig: ObjectTypeConfig, field: BlockField) => {
|
||||
const blockTypes = field.blocks.map((block) => {
|
||||
buildBlockType(payload, block);
|
||||
return payload.types.blockTypes[block.slug];
|
||||
@@ -417,72 +466,38 @@ function buildObjectType(payload: Payload, name: string, fields: Field[], parent
|
||||
resolveType: (data) => payload.types.blockTypes[data.blockType].name,
|
||||
}));
|
||||
|
||||
return { type };
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type },
|
||||
};
|
||||
},
|
||||
row: (field) => field.fields.reduce((subFieldSchema, subField) => {
|
||||
const buildSchemaType = fieldToSchemaMap[subField.type];
|
||||
|
||||
if (!fieldIsPresentationalOnly(subField) && buildSchemaType) {
|
||||
return {
|
||||
...subFieldSchema,
|
||||
[formatName(subField.name)]: buildSchemaType(subField),
|
||||
};
|
||||
}
|
||||
|
||||
return subFieldSchema;
|
||||
}, {}),
|
||||
collapsible: (field) => field.fields.reduce((subFieldSchema, subField) => {
|
||||
const buildSchemaType = fieldToSchemaMap[subField.type];
|
||||
|
||||
if (!fieldIsPresentationalOnly(subField) && buildSchemaType) {
|
||||
return {
|
||||
...subFieldSchema,
|
||||
[formatName(subField.name)]: buildSchemaType(subField),
|
||||
};
|
||||
}
|
||||
|
||||
return subFieldSchema;
|
||||
}, {}),
|
||||
tabs: (field) => field.tabs.reduce((tabSchema, tab) => {
|
||||
row: (objectTypeConfig: ObjectTypeConfig, field: RowField) => field.fields.reduce((objectTypeConfigWithRowFields, subField) => {
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(objectTypeConfigWithRowFields, subField);
|
||||
}, objectTypeConfig),
|
||||
collapsible: (objectTypeConfig: ObjectTypeConfig, field: CollapsibleField) => field.fields.reduce((objectTypeConfigWithCollapsibleFields, subField) => {
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(objectTypeConfigWithCollapsibleFields, subField);
|
||||
}, objectTypeConfig),
|
||||
tabs: (objectTypeConfig: ObjectTypeConfig, field: TabsField) => field.tabs.reduce((tabSchema, tab) => {
|
||||
return {
|
||||
...tabSchema,
|
||||
...tab.fields.reduce((subFieldSchema, subField) => {
|
||||
const buildSchemaType = fieldToSchemaMap[subField.type];
|
||||
|
||||
if (!fieldIsPresentationalOnly(subField) && buildSchemaType) {
|
||||
return {
|
||||
...subFieldSchema,
|
||||
[formatName(subField.name)]: buildSchemaType(subField),
|
||||
};
|
||||
}
|
||||
|
||||
return subFieldSchema;
|
||||
}, {}),
|
||||
const addSubField = fieldToSchemaMap[subField.type];
|
||||
return addSubField(subFieldSchema, subField);
|
||||
}, tabSchema),
|
||||
};
|
||||
}, {}),
|
||||
}, objectTypeConfig),
|
||||
};
|
||||
|
||||
const objectSchema = {
|
||||
name,
|
||||
fields: () => fields.reduce((schema, field) => {
|
||||
if (!fieldIsPresentationalOnly(field) && !field.hidden) {
|
||||
const fieldSchema = fieldToSchemaMap[field.type];
|
||||
if (fieldSchema) {
|
||||
if (fieldAffectsData(field)) {
|
||||
return {
|
||||
...schema,
|
||||
[formatName(field.name)]: fieldSchema(field),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...schema,
|
||||
...fieldSchema(field),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
fields: () => fields.reduce((objectTypeConfig, field) => {
|
||||
const fieldSchema = fieldToSchemaMap[field.type];
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
...fieldSchema(objectTypeConfig, field),
|
||||
};
|
||||
}, baseFields),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { GraphQLNonNull, GraphQLType } from 'graphql';
|
||||
import { NonPresentationalField } from '../../fields/config/types';
|
||||
|
||||
|
||||
const withNullableType = (field: NonPresentationalField, type: GraphQLType, forceNullable = false): GraphQLType => {
|
||||
const hasReadAccessControl = field.access && field.access.read;
|
||||
const condition = field.admin && field.admin.condition;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
const flattenTopLevelFields = (fields) => fields.reduce((flattened, field) => {
|
||||
if (!field.name && Array.isArray(field.fields)) {
|
||||
return [
|
||||
...flattened,
|
||||
...field.fields.filter((subField) => subField.name),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...flattened,
|
||||
field,
|
||||
];
|
||||
}, []);
|
||||
|
||||
export default flattenTopLevelFields;
|
||||
Reference in New Issue
Block a user