diff --git a/src/App.css b/src/App.css index c879c80..f006a97 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,9 +73,11 @@ border: none; border-radius: 40px; cursor: pointer; + margin-top: 40px; + margin-bottom: 40px; } .code { width: 100%; -} \ No newline at end of file +} diff --git a/src/DynamicJsonformsV4.tsx b/src/DynamicJsonformsV4.tsx index 9b21b7e..f7683aa 100644 --- a/src/DynamicJsonformsV4.tsx +++ b/src/DynamicJsonformsV4.tsx @@ -2,43 +2,36 @@ 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"; +import { DynamicControl } from "./customRenderer/customRenderer"; interface Props { - title: string; - description: string; schema: any; uischema: any; - sourceToTransformation: any; onSubmit?: (data: any) => void; 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 && ( <> { + renderers={[ + ...materialRenderers, + MonacoEditorControl, + DynamicControl, + ]} + onChange={(data) => { if ((data?.errors || []).length > 0) { return; } @@ -56,26 +49,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..4bbb95f 100644 --- a/src/ExampleDynamic.tsx +++ b/src/ExampleDynamic.tsx @@ -1,45 +1,90 @@ 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"; +import { MonacoEditor } from "@fusebit/monaco-jsonforms"; const uischema = { type: "VerticalLayout", elements: [ { - type: "Control", - scope: "#/properties/keys" - } - ] + type: "Dynamic", + 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 { data, schema } = sdk.createSchema({ + source: customerJsonSchema, + target: leadJsonSchema, + dataToTransform: sourceToTransformation, + }); + return ( -
+
+ {recipe && ( + <> +

This is the schema

+
+ {}} + language="json" + /> +
+

This is the source data

+
+ {}} + isExpandable + language="json" + /> +
+

This is the submitData:

+
+ {}} + isExpandable + language="json" + /> +
+

This is the transformed data

+
+ {}} + isExpandable + language="json" + /> +
+ + )}
); }; diff --git a/src/constants.ts b/src/constants.ts index 8f5f551..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", @@ -15,16 +16,29 @@ export const customerJsonSchema = { Organization: { type: "string", }, + Nested: { + Object: { + Is: { + properties: { + here: { + type: "string", + label: "Budgetly Nested Property", + }, + }, + }, + }, + }, }, }; -// 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", @@ -35,66 +49,33 @@ 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", + label: "Salesforce Nested Label", + }, + }, + }, }, }, }, - } - } - } -} - -magicFunction(customerJsonSchema, leadJsonSchema) - - - - - - - - + }, + }, + }, +}; // this is some sample data that will get transformed export const sourceToTransformation = { @@ -102,4 +83,11 @@ export const sourceToTransformation = { First: "Shehzad", Last: "Akbar", Organization: "Fusebit", + Nested: { + Object: { + Is: { + here: "Nesting Objects Worked!", + }, + }, + }, }; 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..34c49b1 --- /dev/null +++ b/src/customRenderer/customRenderer.tsx @@ -0,0 +1,164 @@ +import React, { useMemo, useState } from "react"; +import { withJsonFormsControlProps } from "@jsonforms/react"; +import { + rankWith, + ControlProps, + JsonSchema, + and, + uiTypeIs, +} from "@jsonforms/core"; +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; + label?: string; +} + +type CustomProps = { + items: { + properties: { + source: { + title?: string; + enum: Enum[]; + }; + target: { + title?: string; + enum: Enum[]; + }; + }; + }; +}; + +type JsonSchemaWithCustomProps = JsonSchema & CustomProps; + +const DynamicControlVanillaRenderer = ({ + data, + handleChange, + 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) => ( + + {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) => ( + + {val} + + ))} + + + + + {Object.keys(tranformedTable).map((val) => ( + + {JSON.stringify(dot.pick(val, tranformedTable))} + + ))} + + +
+
+ + )} +
+

+ {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 a42c336..1a95da9 100644 --- a/src/utils/sdk.ts +++ b/src/utils/sdk.ts @@ -1,46 +1,81 @@ -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 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); } -} + }); -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, + dataToTransform, +}: { + source: any; + target: any; + dataToTransform: any; +}) => { + const sourceEnum = generateEnum(source); + const targetEnum = generateEnum(target); + + const schema = { + type: "object", + properties: { + keys: { + type: "array", + items: { + type: "object", + required: ["target"], + properties: { + source: { + type: "object", + title: source?.title, + enum: sourceEnum, + }, + target: { + type: "object", + title: target?.title, + enum: targetEnum, + }, + }, }, - {} - ); -} + }, + dataToTransform, + }, + }; + return { + schema: schema, + data: { keys: sourceEnum.map((val) => ({ source: val })) }, + }; +}; + +export const createRecipe = (data: any) => { + return data.keys.reduce( + //@ts-ignore + (acc, { source, target }) => { + acc[source.value] = target.value; + return acc; + }, + {} + ); +};