Skip to content

Commit

Permalink
Supported nested fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben-Ho committed Jan 25, 2024
1 parent ded7f6f commit 9e2b9eb
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 13 deletions.
2 changes: 2 additions & 0 deletions packages/admin/cms-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"lodash.isequal": "^4.0.0",
"lodash.set": "^4.3.2",
"mime-db": "^1.0.0",
"object-path": "^0.11.8",
"p-debounce": "^4.0.0",
"pluralize": "^8.0.0",
"prop-types": "^15.7.2",
Expand Down Expand Up @@ -107,6 +108,7 @@
"@types/lodash.set": "^4.3.6",
"@types/mime-db": "^1.43.1",
"@types/node": "^18.0.0",
"@types/object-path": "^0.11.4",
"@types/pluralize": "^0.0.29",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/admin/cms-admin/src/generator/future/generateForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IntrospectionQuery } from "graphql";
import { generateFormField } from "./generateFormField";
import { FormConfig, GeneratorReturn } from "./generator";
import { camelCaseToHumanReadable } from "./utils/camelCaseToHumanReadable";
import { generateFieldListGqlString } from "./utils/generateFieldList";
import { generateImportsCode, Imports } from "./utils/generateImportsCode";

export function generateForm(
Expand All @@ -16,13 +17,12 @@ export function generateForm(
const gqlQueries: Record<string, string> = {};
const imports: Imports = [];

const fieldNamesFromConfig: string[] = config.fields.map((field) => field.name);
const fieldList = generateFieldListGqlString(fieldNamesFromConfig);

const fragmentName = config.fragmentName ?? `${gqlType}Form`;
gqlQueries[`${instanceGqlType}FormFragment`] = `
fragment ${fragmentName} on ${gqlType} {
title
slug
description
}
fragment ${fragmentName} on ${gqlType} ${fieldList}
`;

gqlQueries[`${instanceGqlType}Query`] = `
Expand Down
17 changes: 10 additions & 7 deletions packages/admin/cms-admin/src/generator/future/generateFormField.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IntrospectionObjectType, IntrospectionQuery } from "graphql";
import { IntrospectionQuery } from "graphql";

import { FormConfig, FormFieldConfig, GeneratorReturn } from "./generator";
import { camelCaseToHumanReadable } from "./utils/camelCaseToHumanReadable";
import { generateFieldListFromIntrospection } from "./utils/generateFieldList";
import { Imports } from "./utils/generateImportsCode";

export function generateFormField(
Expand All @@ -17,13 +18,15 @@ export function generateFormField(
const name = String(config.name);
const label = config.label ?? camelCaseToHumanReadable(name);

const introspectionObject = gqlIntrospection.__schema.types.find((type) => type.kind === "OBJECT" && type.name === gqlType) as
| IntrospectionObjectType
| undefined;
if (!introspectionObject) throw new Error(`didn't find object ${gqlType} in gql introspection`);
const introspectedTypes = gqlIntrospection.__schema.types;
const introspectionObject = introspectedTypes.find((type) => type.kind === "OBJECT" && type.name === gqlType);
if (!introspectionObject || introspectionObject.kind !== "OBJECT") throw new Error(`didn't find object ${gqlType} in gql introspection`);

const introspectionField = introspectionObject.fields.find((field) => field.name === name);
if (!introspectionField) throw new Error(`didn't find field ${name} in gql introspection type ${gqlType}`);
const introspectedFields = generateFieldListFromIntrospection(gqlIntrospection, gqlType);

const introspectionFieldWithPath = introspectedFields.find((field) => field.path === name);
if (!introspectionFieldWithPath) throw new Error(`didn't find field ${name} in gql introspection type ${gqlType}`);
const introspectionField = introspectionFieldWithPath.field;

const requiredByIntrospection = introspectionField.type.kind == "NON_NULL";

Expand Down
3 changes: 2 additions & 1 deletion packages/admin/cms-admin/src/generator/future/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { introspectionFromSchema } from "graphql";
import { basename } from "path";

import { generateForm } from "./generateForm";
import { DeepKeyOf } from "./utils/deepKeyOf";
import { writeGenerated } from "./utils/writeGenerated";

type BlockReference = {
Expand All @@ -17,7 +18,7 @@ export type FormFieldConfig<T> = (
| { type: "staticSelect"; values?: string[] }
| { type: "asyncSelect"; values?: string[] }
| { type: "block"; block: BlockReference }
) & { name: keyof T; label?: string; required?: boolean };
) & { name: DeepKeyOf<T>; label?: string; required?: boolean };

export type FormConfig<T extends { __typename?: string }> = {
type: "form";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// https://stackoverflow.com/a/58436959
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]];
type Join<K, P> = K extends string | number ? (P extends string | number ? `${K}${"" extends P ? "" : "."}${P}` : never) : never;

export type DeepKeyOf<T, D extends number = 10> = [D] extends [never]
? never
: T extends object
? { [K in keyof T]-?: Join<K, DeepKeyOf<T[K], Prev[D]>> }[keyof T]
: "";
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { IntrospectionField, IntrospectionQuery, IntrospectionType } from "graphql";
import objectPath from "object-path";

export function generateFieldListGqlString(fields: string[]) {
const fieldsObject = fields.reduce<object>((acc, fieldName) => {
objectPath.set(acc, fieldName, true);
return acc;
}, {});
return JSON.stringify(fieldsObject, null, 4)
.replace(/: true,/g, "")
.replace(/: true/g, "")
.replace(/"/g, "")
.replace(/:/g, "");
}

function fieldListFromIntrospectionTypeRecursive(
types: readonly IntrospectionType[],
type: string,
parentPath?: string,
): { path: string; field: IntrospectionField }[] {
const typeDef = types.find((typeDef) => typeDef.name === type);
if (!typeDef || typeDef.kind !== "OBJECT") return [];

return typeDef.fields.reduce<{ path: string; field: IntrospectionField }[]>((acc, field) => {
const path = `${parentPath ? `${parentPath}.` : ""}${field.name}`;
if (field.type.kind === "OBJECT") {
const subFields = fieldListFromIntrospectionTypeRecursive(types, field.type.name, path);
acc.push(...subFields);
} else {
acc.push({
path: path,
field: field,
});
}
return acc;
}, []);
}
export function generateFieldListFromIntrospection(
gqlIntrospection: IntrospectionQuery,
type: string,
): { path: string; field: IntrospectionField }[] {
return fieldListFromIntrospectionTypeRecursive(gqlIntrospection.__schema.types, type);
}
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9e2b9eb

Please sign in to comment.