From 135019d2209e279f5d117b03504bb5d3d04b6bb7 Mon Sep 17 00:00:00 2001 From: MikeFuster Date: Fri, 16 Sep 2022 16:34:49 -0300 Subject: [PATCH 1/5] add nesting support --- src/DynamicJsonformsV4.tsx | 39 ++------------ src/ExampleDynamic.tsx | 47 +++++++++++++---- src/constants.ts | 92 ++++++++++++++------------------- src/utils/sdk.ts | 101 ++++++++++++++++++++++--------------- 4 files changed, 139 insertions(+), 140 deletions(-) diff --git a/src/DynamicJsonformsV4.tsx b/src/DynamicJsonformsV4.tsx index 9b21b7e..219fac0 100644 --- a/src/DynamicJsonformsV4.tsx +++ b/src/DynamicJsonformsV4.tsx @@ -2,10 +2,9 @@ import React, { useState } from "react"; import { JsonForms } from "@jsonforms/react"; import { materialRenderers, - materialCells + materialCells, } from "@jsonforms/material-renderers"; import { MonacoEditorControl } from "@fusebit/monaco-jsonforms"; -import dot from "dot-object"; interface Props { title: string; @@ -17,28 +16,20 @@ interface Props { data?: any; } -const DynamicJsonformsV4 = ({ - title, - description, - uischema, - data, - schema, - sourceToTransformation, - onSubmit -}: Props) => { - const [submitData, setSubmitData] = useState(); +const DynamicJsonformsV4 = ({ uischema, data, schema, onSubmit }: Props) => { + const [submitData, setSubmitData] = useState(); return (
{schema && uischema && ( <> { + onChange={(data) => { if ((data?.errors || []).length > 0) { return; } @@ -56,26 +47,6 @@ const DynamicJsonformsV4 = ({ > Create Recipe - {submitData && ( - <> -

This is the source data

-
- {JSON.stringify(sourceToTransformation, null, "\t")} -
-

This is the submitData:

-
- {JSON.stringify(dot.dot(submitData), null, "\t")} -
-

This is the transformed data

-
- {JSON.stringify( - dot.transform(dot.dot(submitData), sourceToTransformation), - null, - "\t" - )} -
- - )} )}
diff --git a/src/ExampleDynamic.tsx b/src/ExampleDynamic.tsx index 905789f..cc09a0f 100644 --- a/src/ExampleDynamic.tsx +++ b/src/ExampleDynamic.tsx @@ -1,34 +1,39 @@ import React from "react"; +import { useState } from "react"; import { leadJsonSchema, customerJsonSchema, - sourceToTransformation + sourceToTransformation, } from "./constants"; import DynamicJsonformsV4 from "./DynamicJsonformsV4"; -import { generateJsonform } from "./utils/generateJsonform"; import * as sdk from "./utils/sdk"; +import dot from "dot-object"; const uischema = { type: "VerticalLayout", elements: [ { type: "Control", - scope: "#/properties/keys" - } - ] + scope: "#/properties/keys", + }, + ], }; -const { keys, schema } = sdk.createSchema({ - source: customerJsonSchema, - target: leadJsonSchema -}); - const ExampleDynamic = () => { + const [recipe, setRecipe] = useState(); + const handleSubmit = (data: any) => { const recipe = sdk.createRecipe(data); - console.log("recipe", recipe); + setRecipe(recipe); }; + const { keys, schema } = sdk.createSchema({ + source: customerJsonSchema, + target: leadJsonSchema, + }); + + console.log("keys:", keys); + return (
{ schema={schema} sourceToTransformation={sourceToTransformation} /> + {recipe && ( + <> +

This is the source data

+
+ {JSON.stringify(sourceToTransformation, null, "\t")} +
+

This is the submitData:

+
+ {JSON.stringify(dot.dot(recipe), null, "\t")} +
+

This is the transformed data

+
+ {JSON.stringify( + dot.transform(dot.dot(recipe), sourceToTransformation), + null, + "\t" + )} +
+ + )}
); }; diff --git a/src/constants.ts b/src/constants.ts index 8f5f551..65e6a31 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,6 +15,17 @@ export const customerJsonSchema = { Organization: { type: "string", }, + Nested: { + Object: { + Is: { + properties: { + here: { + type: "string", + }, + }, + }, + }, + }, }, }; @@ -35,66 +46,32 @@ export const leadJsonSchema = { Company: { type: "string", }, - }, -}; - - -const _schema = { - type: "object", - properties: { - keys: { - type: "array", - items: { - type: "object", + Nested: { + Keys: { properties: { - source: { + oneNestedKey: { type: "string", - enum: ["first.name", "last.name"] }, - target: { - type: "string", - enum: ["firstName", "lastName"] - } - } - } - } - } -}; - - -export const magicFunction = (schema1: any, schema2: any) => { - return { - type: "object", - properties: { - keys: { - type: "array", - items: { - type: "object", - properties: { - left: { - type: "string", - enum: Object.keys(schema1.properties) - }, - right: { - type: "string", - enum: Object.keys(schema2.properties) + anotherNestedKey: { + type: "object", + NestedInsideNestedKey: { + moreNesting: { + properties: { + superNestedFirstKey: { + type: "string", + }, + superNestedSecondKey: { + type: "string", + }, + }, + }, }, }, }, - } - } - } -} - -magicFunction(customerJsonSchema, leadJsonSchema) - - - - - - - - + }, + }, + }, +}; // this is some sample data that will get transformed export const sourceToTransformation = { @@ -102,4 +79,11 @@ export const sourceToTransformation = { First: "Shehzad", Last: "Akbar", Organization: "Fusebit", + Nested: { + Object: { + Is: { + here: "Nesting Objects Worked!", + }, + }, + }, }; diff --git a/src/utils/sdk.ts b/src/utils/sdk.ts index a42c336..1939b1a 100644 --- a/src/utils/sdk.ts +++ b/src/utils/sdk.ts @@ -1,46 +1,65 @@ -export const createSchema = ({ source, target }) => { - console.log(source.properties) - const schema = { - type: "object", - properties: { - keys: { - type: "array", - items: { - type: "object", - required: ["target", "source"], - properties: { - source: { - type: "string", - enum: Object.keys(source.properties), - }, - target: { - type: "string", - enum: Object.keys(target.properties) - } - } - } - } - } - } +import dot from "dot-object"; + +const parseKey = (key: string) => { + const keyWithoutProperties = key.replaceAll("properties.", ""); + const splittedKey = keyWithoutProperties.split("."); + const objectKey = splittedKey.slice(0, splittedKey.length - 1).join("."); + return objectKey; +}; - return { - schema: schema, - keys: Object.keys(source.properties).map( - key => ({ - source: key - }) - ) +const generateEnum = (schema) => { + const schemaEnum = []; + + Object.keys(dot.dot(schema.properties)).forEach((key) => { + const objectKey = parseKey(key); + if (!schemaEnum.includes(objectKey)) { + schemaEnum.push(objectKey); } -} + }); -export const createRecipe = (data: any) => { - return data.keys.reduce( - //@ts-ignore - (acc, { source, target }) => { - acc[source] = target; - return acc; + return schemaEnum; +}; + +export const createSchema = ({ source, target }) => { + const sourceEnum = generateEnum(source); + const targetEnum = generateEnum(target); + + const schema = { + type: "object", + properties: { + keys: { + type: "array", + items: { + type: "object", + required: ["target", "source"], + properties: { + source: { + type: "string", + enum: sourceEnum, + }, + target: { + type: "string", + enum: targetEnum, + }, + }, }, - {} - ); -} + }, + }, + }; + return { + schema: schema, + keys: sourceEnum.map((val) => ({ source: val })), + }; +}; + +export const createRecipe = (data: any) => { + return data.keys.reduce( + //@ts-ignore + (acc, { source, target }) => { + acc[source] = target; + return acc; + }, + {} + ); +}; From a88779f6d3ef5ede7bfd6f7cec829c9853e27111 Mon Sep 17 00:00:00 2001 From: MikeFuster Date: Mon, 19 Sep 2022 16:21:19 -0300 Subject: [PATCH 2/5] add custom renderer --- src/App.css | 3 +- src/DynamicJsonformsV4.tsx | 10 ++-- src/ExampleDynamic.tsx | 15 +++-- src/constants.ts | 8 ++- src/customRenderer/Row.tsx | 57 ++++++++++++++++++ src/customRenderer/customRenderer.tsx | 84 +++++++++++++++++++++++++++ src/utils/sdk.ts | 33 ++++++++--- 7 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 src/customRenderer/Row.tsx create mode 100644 src/customRenderer/customRenderer.tsx diff --git a/src/App.css b/src/App.css index c879c80..1724437 100644 --- a/src/App.css +++ b/src/App.css @@ -66,7 +66,6 @@ } .btn { - margin-left: auto; font-size: 16px; padding: 15px; background-color: #FB310A; @@ -74,6 +73,8 @@ border: none; border-radius: 40px; cursor: pointer; + margin-top: 40px; + margin-bottom: 40px; } diff --git a/src/DynamicJsonformsV4.tsx b/src/DynamicJsonformsV4.tsx index 219fac0..f7683aa 100644 --- a/src/DynamicJsonformsV4.tsx +++ b/src/DynamicJsonformsV4.tsx @@ -5,13 +5,11 @@ import { materialCells, } from "@jsonforms/material-renderers"; import { MonacoEditorControl } from "@fusebit/monaco-jsonforms"; +import { DynamicControl } from "./customRenderer/customRenderer"; interface Props { - title: string; - description: string; schema: any; uischema: any; - sourceToTransformation: any; onSubmit?: (data: any) => void; data?: any; } @@ -28,7 +26,11 @@ const DynamicJsonformsV4 = ({ uischema, data, schema, onSubmit }: Props) => { schema={schema} uischema={uischema} cells={materialCells} - renderers={[...materialRenderers, MonacoEditorControl]} + renderers={[ + ...materialRenderers, + MonacoEditorControl, + DynamicControl, + ]} onChange={(data) => { if ((data?.errors || []).length > 0) { return; diff --git a/src/ExampleDynamic.tsx b/src/ExampleDynamic.tsx index cc09a0f..0e86dbe 100644 --- a/src/ExampleDynamic.tsx +++ b/src/ExampleDynamic.tsx @@ -13,7 +13,7 @@ const uischema = { type: "VerticalLayout", elements: [ { - type: "Control", + type: "Dynamic", scope: "#/properties/keys", }, ], @@ -27,26 +27,25 @@ const ExampleDynamic = () => { setRecipe(recipe); }; - const { keys, schema } = sdk.createSchema({ + const { data, schema } = sdk.createSchema({ source: customerJsonSchema, target: leadJsonSchema, }); - console.log("keys:", keys); - return (
{recipe && ( <> +

This is the schema

+
+ {JSON.stringify(schema, null, "\t")} +

This is the source data

{JSON.stringify(sourceToTransformation, null, "\t")} diff --git a/src/constants.ts b/src/constants.ts index 65e6a31..b89b2e5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,10 +1,11 @@ -// this schema is read only +// this schema is the source export const customerJsonSchema = { type: "object", title: "Budgetly Customer", properties: { Title: { type: "string", + label: "Budgetly Title", }, First: { type: "string", @@ -21,6 +22,7 @@ export const customerJsonSchema = { properties: { here: { type: "string", + label: "Budgetly Nested Property", }, }, }, @@ -29,13 +31,14 @@ export const customerJsonSchema = { }, }; -// this schema gets mapped +// this schema is the target export const leadJsonSchema = { type: "object", title: "Salesforce Lead", properties: { Salutation: { type: "string", + label: "Salesforce Salutation", }, FirstName: { type: "string", @@ -62,6 +65,7 @@ export const leadJsonSchema = { }, superNestedSecondKey: { type: "string", + label: "Salesforce Nested Label", }, }, }, diff --git a/src/customRenderer/Row.tsx b/src/customRenderer/Row.tsx new file mode 100644 index 0000000..811db31 --- /dev/null +++ b/src/customRenderer/Row.tsx @@ -0,0 +1,57 @@ +import React, { useState } from "react"; +import { + FormControl, + InputLabel, + MenuItem, + Select, + TextField, +} from "@material-ui/core"; + +interface Enum { + value: string; + label?: string; +} + +interface Props { + source: Enum; + targetEnum: Enum[]; + onChange: (source: string, target: Enum) => void; +} + +const Row = ({ source, targetEnum, onChange }: Props) => { + const [targetValue, setTargetValue] = useState(""); + + return ( +
+ + + Select a Property + + +
+ ); +}; + +export default Row; diff --git a/src/customRenderer/customRenderer.tsx b/src/customRenderer/customRenderer.tsx new file mode 100644 index 0000000..fad4ce7 --- /dev/null +++ b/src/customRenderer/customRenderer.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { withJsonFormsControlProps } from "@jsonforms/react"; +import { + rankWith, + ControlProps, + JsonSchema, + and, + uiTypeIs, +} from "@jsonforms/core"; +import Row from "./Row"; + +interface Enum { + value: string; + label?: string; +} + +type CustomProps = { + isExpandable: boolean; + language: string; + items: { + properties: { + source: { + title?: string; + enum: Enum[]; + }; + target: { + title?: string; + enum: Enum[]; + }; + }; + }; +}; + +type JsonSchemaWithCustomProps = JsonSchema & CustomProps; + +const DynamicControlVanillaRenderer = ({ + data, + handleChange, + path, + ...props +}: ControlProps) => { + const schema = props.schema as JsonSchemaWithCustomProps; + + const handleRowChange = (sourceValue: string, target: Enum) => { + const i = data.findIndex((row) => row.source.value === sourceValue); + data[i] = { ...data[i], target }; + handleChange(path, data); + }; + + return ( +
+
+

+ {schema.items.properties.source.title} +

+

{schema.items.properties.target.title}

+
+ {schema.items.properties.source.enum.map((e) => { + return ( + + ); + })} +
+ ); +}; + +const DynamicControlTester = rankWith(3, and(uiTypeIs("Dynamic"))); +const DynamicControlRenderer = withJsonFormsControlProps( + DynamicControlVanillaRenderer +); + +const DynamicControl = { + tester: DynamicControlTester, + renderer: DynamicControlRenderer, +}; + +export { DynamicControl, DynamicControlTester, DynamicControlRenderer }; + +export default DynamicControl; diff --git a/src/utils/sdk.ts b/src/utils/sdk.ts index 1939b1a..31dd648 100644 --- a/src/utils/sdk.ts +++ b/src/utils/sdk.ts @@ -11,16 +11,27 @@ const generateEnum = (schema) => { const schemaEnum = []; Object.keys(dot.dot(schema.properties)).forEach((key) => { - const objectKey = parseKey(key); - if (!schemaEnum.includes(objectKey)) { - schemaEnum.push(objectKey); + const value = parseKey(key); + const splittedKey = key.split("."); + const baseKey = splittedKey.slice(0, splittedKey.length - 1).join("."); + const objectProperties = dot.pick(baseKey, schema.properties); + const baseObject = { value, ...objectProperties }; + const isEnumIncluded = schemaEnum.find((val) => val.value === value); + if (!isEnumIncluded) { + schemaEnum.push(baseObject); } }); return schemaEnum; }; -export const createSchema = ({ source, target }) => { +export const createSchema = ({ + source, + target, +}: { + source: any; + target: any; +}) => { const sourceEnum = generateEnum(source); const targetEnum = generateEnum(target); @@ -31,14 +42,16 @@ export const createSchema = ({ source, target }) => { type: "array", items: { type: "object", - required: ["target", "source"], + required: ["target"], properties: { source: { - type: "string", + type: "object", + title: source?.title, enum: sourceEnum, }, target: { - type: "string", + type: "object", + title: target?.title, enum: targetEnum, }, }, @@ -49,15 +62,17 @@ export const createSchema = ({ source, target }) => { return { schema: schema, - keys: sourceEnum.map((val) => ({ source: val })), + data: { keys: sourceEnum.map((val) => ({ source: val })) }, }; }; export const createRecipe = (data: any) => { + console.log("data", data); + return data.keys.reduce( //@ts-ignore (acc, { source, target }) => { - acc[source] = target; + acc[source.value] = target.value; return acc; }, {} From 3c0bec9f7cf03a1e05f6de5a940fa0ec0f0ce863 Mon Sep 17 00:00:00 2001 From: MikeFuster Date: Mon, 19 Sep 2022 17:14:41 -0300 Subject: [PATCH 3/5] use editor --- src/ExampleDynamic.tsx | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/ExampleDynamic.tsx b/src/ExampleDynamic.tsx index 0e86dbe..05ebce8 100644 --- a/src/ExampleDynamic.tsx +++ b/src/ExampleDynamic.tsx @@ -8,6 +8,7 @@ import { import DynamicJsonformsV4 from "./DynamicJsonformsV4"; import * as sdk from "./utils/sdk"; import dot from "dot-object"; +import { MonacoEditor } from "@fusebit/monaco-jsonforms"; const uischema = { type: "VerticalLayout", @@ -33,7 +34,7 @@ const ExampleDynamic = () => { }); return ( -
+
{ <>

This is the schema

- {JSON.stringify(schema, null, "\t")} + {}} + language="json" + />

This is the source data

- {JSON.stringify(sourceToTransformation, null, "\t")} + {}} + isExpandable + language="json" + />

This is the submitData:

- {JSON.stringify(dot.dot(recipe), null, "\t")} + {}} + isExpandable + language="json" + />

This is the transformed data

- {JSON.stringify( - dot.transform(dot.dot(recipe), sourceToTransformation), - null, - "\t" - )} + {}} + isExpandable + language="json" + />
)} From 4c07ef054fc72f4a1019e65da1bb4b8b0f408aa6 Mon Sep 17 00:00:00 2001 From: MikeFuster Date: Tue, 20 Sep 2022 15:11:11 -0300 Subject: [PATCH 4/5] add table --- src/App.css | 10 ++++ src/ExampleDynamic.tsx | 1 + src/customRenderer/customRenderer.tsx | 69 +++++++++++++++++++++++++-- src/utils/sdk.ts | 5 +- 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/App.css b/src/App.css index 1724437..537a84b 100644 --- a/src/App.css +++ b/src/App.css @@ -80,4 +80,14 @@ .code { width: 100%; +} + +th { + padding: 5px; + border: 1px solid black; +} + +td { + padding: 5px; + border: 1px solid black; } \ No newline at end of file diff --git a/src/ExampleDynamic.tsx b/src/ExampleDynamic.tsx index 05ebce8..4bbb95f 100644 --- a/src/ExampleDynamic.tsx +++ b/src/ExampleDynamic.tsx @@ -31,6 +31,7 @@ const ExampleDynamic = () => { const { data, schema } = sdk.createSchema({ source: customerJsonSchema, target: leadJsonSchema, + dataToTransform: sourceToTransformation, }); return ( diff --git a/src/customRenderer/customRenderer.tsx b/src/customRenderer/customRenderer.tsx index fad4ce7..9aecb3c 100644 --- a/src/customRenderer/customRenderer.tsx +++ b/src/customRenderer/customRenderer.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo, useState } from "react"; import { withJsonFormsControlProps } from "@jsonforms/react"; import { rankWith, @@ -8,6 +8,8 @@ import { uiTypeIs, } from "@jsonforms/core"; import Row from "./Row"; +import dot from "dot-object"; +import * as sdk from "../utils/sdk"; interface Enum { value: string; @@ -15,8 +17,6 @@ interface Enum { } type CustomProps = { - isExpandable: boolean; - language: string; items: { properties: { source: { @@ -39,16 +39,79 @@ const DynamicControlVanillaRenderer = ({ path, ...props }: ControlProps) => { + const [recipe, setRecipe] = useState({}); const schema = props.schema as JsonSchemaWithCustomProps; + const rootSchema = props.rootSchema as JsonSchema; const handleRowChange = (sourceValue: string, target: Enum) => { const i = data.findIndex((row) => row.source.value === sourceValue); data[i] = { ...data[i], target }; handleChange(path, data); + + const dataToTransform = { + keys: data, + }; + const recipe = sdk.createRecipe(dataToTransform); + setRecipe(recipe); }; + const baseTable = useMemo(() => { + return dot.dot(rootSchema.properties.dataToTransform); + }, [rootSchema]); + + const tranformedTable = useMemo(() => { + return dot.transform( + dot.dot(recipe), + rootSchema.properties.dataToTransform + ); + }, [recipe, rootSchema]); + + console.log(tranformedTable); + return (
+

Base Table

+ + + + {Object.keys(baseTable).map((val) => ( + + ))} + + + + + {Object.keys(baseTable).map((val) => ( + + ))} + + +
{val}
+ {dot.pick(val, rootSchema.properties.dataToTransform)} +
+ {Object.keys(tranformedTable).length > 0 && ( + <> +

Transformed Table

+ + + + {Object.keys(dot.dot(tranformedTable)).map((val) => ( + + ))} + + + + + {Object.keys(tranformedTable).map((val) => ( + + ))} + + +
{val}
+ {JSON.stringify(dot.pick(val, tranformedTable))} +
+ + )}

{schema.items.properties.source.title} diff --git a/src/utils/sdk.ts b/src/utils/sdk.ts index 31dd648..1a95da9 100644 --- a/src/utils/sdk.ts +++ b/src/utils/sdk.ts @@ -28,9 +28,11 @@ const generateEnum = (schema) => { export const createSchema = ({ source, target, + dataToTransform, }: { source: any; target: any; + dataToTransform: any; }) => { const sourceEnum = generateEnum(source); const targetEnum = generateEnum(target); @@ -57,6 +59,7 @@ export const createSchema = ({ }, }, }, + dataToTransform, }, }; @@ -67,8 +70,6 @@ export const createSchema = ({ }; export const createRecipe = (data: any) => { - console.log("data", data); - return data.keys.reduce( //@ts-ignore (acc, { source, target }) => { From 4a0980acb3affc9589e010cb8862f1c435c452a1 Mon Sep 17 00:00:00 2001 From: MikeFuster Date: Tue, 20 Sep 2022 15:33:33 -0300 Subject: [PATCH 5/5] add mui tables --- src/App.css | 10 --- src/customRenderer/customRenderer.tsx | 89 ++++++++++++++++----------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/App.css b/src/App.css index 537a84b..f006a97 100644 --- a/src/App.css +++ b/src/App.css @@ -81,13 +81,3 @@ .code { width: 100%; } - -th { - padding: 5px; - border: 1px solid black; -} - -td { - padding: 5px; - border: 1px solid black; -} \ No newline at end of file diff --git a/src/customRenderer/customRenderer.tsx b/src/customRenderer/customRenderer.tsx index 9aecb3c..34c49b1 100644 --- a/src/customRenderer/customRenderer.tsx +++ b/src/customRenderer/customRenderer.tsx @@ -10,6 +10,15 @@ import { import Row from "./Row"; import dot from "dot-object"; import * as sdk from "../utils/sdk"; +import { + Table, + TableBody, + TableCell, + TableRow, + TableHead, + TableContainer, + Paper, +} from "@material-ui/core"; interface Enum { value: string; @@ -71,45 +80,53 @@ const DynamicControlVanillaRenderer = ({ return (

Base Table

- - - - {Object.keys(baseTable).map((val) => ( - - ))} - - - - - {Object.keys(baseTable).map((val) => ( - - ))} - - -
{val}
- {dot.pick(val, rootSchema.properties.dataToTransform)} -
+ + + + + {Object.keys(baseTable).map((val) => ( + + {val} + + ))} + + + + + {Object.keys(baseTable).map((val) => ( + + {dot.pick(val, rootSchema.properties.dataToTransform)} + + ))} + + +
+
{Object.keys(tranformedTable).length > 0 && ( <>

Transformed Table

- - - - {Object.keys(dot.dot(tranformedTable)).map((val) => ( - - ))} - - - - - {Object.keys(tranformedTable).map((val) => ( - - ))} - - -
{val}
- {JSON.stringify(dot.pick(val, tranformedTable))} -
+ + + + + {Object.keys(dot.dot(tranformedTable)).map((val) => ( + + {val} + + ))} + + + + + {Object.keys(tranformedTable).map((val) => ( + + {JSON.stringify(dot.pick(val, tranformedTable))} + + ))} + + +
+
)}