Skip to content

Commit

Permalink
Admin Generator (Future): Fix generated code for optional number fiel…
Browse files Browse the repository at this point in the history
…ds (#1759)

Make type optional and check if value is defined before parsing into
string or number.
  • Loading branch information
Ben-Ho authored Mar 5, 2024
1 parent 9bed756 commit ee8a04e
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 16 deletions.
6 changes: 3 additions & 3 deletions demo/admin/src/products/ProductForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const rootBlocks = {
};

type FormValues = Omit<GQLProductFormManualFragment, "image" | "price"> & {
price: string;
price?: string;
image: BlockState<typeof rootBlocks.image>;
};

Expand All @@ -76,7 +76,7 @@ function ProductForm({ id }: FormProps): React.ReactElement {
const initialValues: Partial<FormValues> = data?.product
? {
...filter<GQLProductFormManualFragment>(productFormFragment, data.product),
price: String(data.product.price),
price: data.product.price ? String(data.product.price) : undefined,
image: rootBlocks.image.input2State(data.product.image),
}
: {
Expand Down Expand Up @@ -107,7 +107,7 @@ function ProductForm({ id }: FormProps): React.ReactElement {
articleNumbers: [],
discounts: [],
statistics: { views: 0 },
price: parseFloat(formValues.price),
price: formValues.price ? parseFloat(formValues.price) : null,
};
if (mode === "edit") {
if (!id) throw new Error();
Expand Down
6 changes: 3 additions & 3 deletions demo/admin/src/products/ProductPriceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface FormProps {
}

type FormValues = Omit<GQLProductPriceFormFragment, "price"> & {
price: string;
price?: string;
};

function ProductPriceForm({ id }: FormProps): React.ReactElement {
Expand All @@ -36,7 +36,7 @@ function ProductPriceForm({ id }: FormProps): React.ReactElement {
const initialValues: Partial<FormValues> = data?.product
? {
...filter<GQLProductPriceFormFragment>(productPriceFormFragment, data.product),
price: String(data.product.price),
price: data.product.price ? String(data.product.price) : undefined,
}
: {};

Expand All @@ -55,7 +55,7 @@ function ProductPriceForm({ id }: FormProps): React.ReactElement {
if (await saveConflict.checkForConflicts()) throw new Error("Conflicts detected");
const output = {
...formValues,
price: parseFloat(formValues.price),
price: formValues.price ? parseFloat(formValues.price) : null,
};
await client.mutate<GQLProductPriceFormUpdateProductMutation, GQLProductPriceFormUpdateProductMutationVariables>({
mutation: updateProductPriceFormMutation,
Expand Down
6 changes: 3 additions & 3 deletions demo/admin/src/products/future/generated/ProductForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const rootBlocks = {
};

type FormValues = Omit<GQLProductFormDetailsFragment, "price"> & {
price: string;
price?: string;
image: BlockState<typeof rootBlocks.image>;
};

Expand All @@ -79,7 +79,7 @@ export function ProductForm({ id }: FormProps): React.ReactElement {
data?.product
? {
...filter<GQLProductFormDetailsFragment>(productFormFragment, data.product),
price: String(data.product.price),
price: data.product.price ? String(data.product.price) : undefined,
image: rootBlocks.image.input2State(data.product.image),
}
: {
Expand All @@ -105,7 +105,7 @@ export function ProductForm({ id }: FormProps): React.ReactElement {
const output = {
...formValues,
category: formValues.category?.id,
price: parseFloat(formValues.price),
price: formValues.price ? parseFloat(formValues.price) : null,
image: rootBlocks.image.state2Output(formValues.image),
};
if (mode === "edit") {
Expand Down
20 changes: 17 additions & 3 deletions packages/admin/cms-admin/src/generator/future/generateForm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IntrospectionQuery } from "graphql";

import { generateFormField } from "./generateFormField";
import { FormConfig, GeneratorReturn } from "./generator";
import { FormConfig, FormFieldConfig, GeneratorReturn } from "./generator";
import { camelCaseToHumanReadable } from "./utils/camelCaseToHumanReadable";
import { findRootBlocks } from "./utils/findRootBlocks";
import { generateImportsCode, Imports } from "./utils/generateImportsCode";
import { isFieldOptional } from "./utils/isFieldOptional";

export function generateForm(
{
Expand All @@ -28,6 +29,11 @@ export function generateForm(
const numberFields = config.fields.filter((field) => field.type == "number");
const booleanFields = config.fields.filter((field) => field.type == "boolean");

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isOptional = (fieldConfig: FormFieldConfig<any>) => {
return isFieldOptional({ config: fieldConfig, gqlIntrospection: gqlIntrospection, gqlType: gqlType });
};

let hooksCode = "";
let formValueToGqlInputCode = "";

Expand Down Expand Up @@ -159,7 +165,7 @@ export function generateForm(
} ${
numberFields.length > 0 || Object.keys(rootBlocks).length > 0
? `& {
${numberFields.map((field) => `${String(field.name)}: string;`).join("\n")}
${numberFields.map((field) => `${String(field.name)}${isOptional(field) ? `?` : ``}: string;`).join("\n")}
${Object.keys(rootBlocks)
.map((rootBlockKey) => `${rootBlockKey}: BlockState<typeof rootBlocks.${rootBlockKey}>;`)
.join("\n")}
Expand All @@ -186,7 +192,15 @@ export function generateForm(
const initialValues = React.useMemo<Partial<FormValues>>(() => data?.${instanceGqlType}
? {
...filter<GQL${fragmentName}Fragment>(${instanceGqlType}FormFragment, data.${instanceGqlType}),
${numberFields.map((field) => `${String(field.name)}: String(data.${instanceGqlType}.${String(field.name)}),`).join("\n")}
${numberFields
.map((field) => {
let assignment = `String(data.${instanceGqlType}.${String(field.name)})`;
if (isOptional(field)) {
assignment = `data.${instanceGqlType}.${String(field.name)} ? ${assignment} : undefined`;
}
return `${String(field.name)}: ${assignment},`;
})
.join("\n")}
${Object.keys(rootBlocks)
.map((rootBlockKey) => `${rootBlockKey}: rootBlocks.${rootBlockKey}.input2State(data.${instanceGqlType}.${rootBlockKey}),`)
.join("\n")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IntrospectionEnumType, IntrospectionNamedTypeRef, IntrospectionObjectTy
import { FormConfig, FormFieldConfig, GeneratorReturn } from "./generator";
import { camelCaseToHumanReadable } from "./utils/camelCaseToHumanReadable";
import { Imports } from "./utils/generateImportsCode";
import { isFieldOptional } from "./utils/isFieldOptional";

export function generateFormField(
{ gqlIntrospection }: { gqlIntrospection: IntrospectionQuery },
Expand All @@ -26,9 +27,7 @@ export function generateFormField(
if (!introspectionField) throw new Error(`didn't find field ${name} in gql introspection type ${gqlType}`);
const introspectionFieldType = introspectionField.type.kind === "NON_NULL" ? introspectionField.type.ofType : introspectionField.type;

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

const required = config.required ?? requiredByIntrospection; //if undefined default to requiredByIntrospection
const required = !isFieldOptional({ config, gqlIntrospection, gqlType });

//TODO verify introspectionField.type is compatbile with config.type

Expand Down Expand Up @@ -88,7 +87,11 @@ export function generateFormField(
${validateCode}
/>`;
//TODO MUI suggest not using type=number https://mui.com/material-ui/react-text-field/#type-quot-number-quot
formValueToGqlInputCode = `${name}: parseFloat(formValues.${name}),`;
let assignment = `parseFloat(formValues.${String(name)})`;
if (isFieldOptional({ config, gqlIntrospection: gqlIntrospection, gqlType: gqlType })) {
assignment = `formValues.${name} ? ${assignment} : null`;
}
formValueToGqlInputCode = `${name}: ${assignment},`;
} else if (config.type == "boolean") {
code = `<Field name="${name}" label="" type="checkbox" fullWidth ${validateCode}>
{(props) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IntrospectionQuery } from "graphql";

import { FormFieldConfig } from "../generator";

export const isFieldOptional = ({
config,
gqlIntrospection,
gqlType,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
config: FormFieldConfig<any>;
gqlIntrospection: IntrospectionQuery;
gqlType: string;
}): boolean => {
if (config.required) return false;
const schemaEntity = gqlIntrospection.__schema.types.find((type) => type.kind === "OBJECT" && type.name === gqlType);
if (!schemaEntity) throw new Error(`didn't find entity ${gqlType} in schema types`);
if (schemaEntity.kind !== "OBJECT") throw new Error(`kind of ${gqlType} is not object, but should be.`); // this should not happen
const fieldDef = schemaEntity.fields.find((field) => field.name === String(config.name));
if (!fieldDef) throw new Error(`didn't find field ${String(config.name)} of ${gqlType} in introspected gql-schema.`);
return fieldDef.type.kind !== "NON_NULL";
};

0 comments on commit ee8a04e

Please sign in to comment.