diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 97907e643..f79cb198f 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -45,7 +45,7 @@ import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readM import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { isEmpty } from "underscore"; -import { SelfServeCmponent as SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServe"; +import { SelfServeCmponent as SelfServeComponent } from "./SettingsSubComponents/SelfServe/SelfServeComponent"; interface SettingsV2TabInfo { tab: SettingsV2TabTypes; @@ -900,11 +900,11 @@ export class SettingsComponent extends React.Component - }) + content: + }); if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) { tabs.push({ @@ -975,4 +975,4 @@ export class SettingsComponent extends React.Component ); } -} \ No newline at end of file +} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx new file mode 100644 index 000000000..2f1261b59 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx @@ -0,0 +1,24 @@ +import { Descriptor, Info } from "../../../SmartUi/SmartUiComponent"; +import { addPropertyToMap, DescriptorType, toSmartUiDescriptor } from "./SelfServeUtils"; + +interface SelfServeBaseCLass { + toSmartUiDescriptor: () => Descriptor; +} + +export function SelfServeClass() { + return (constructor: U) => { + constructor; + }; +} + +export const SmartUi = (): ClassDecorator => { + return (target: Function) => { + toSmartUiDescriptor(target.name, target); + }; +}; + +export const ClassInfo = (info: Info): ClassDecorator => { + return (target: Function) => { + addPropertyToMap(target, "root", target.name, "info", info, DescriptorType.ClassDescriptor); + }; +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx new file mode 100644 index 000000000..d02c1bfe3 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx @@ -0,0 +1,79 @@ +import { EnumItem, Info, InputTypeValue } from "../../../SmartUi/SmartUiComponent"; +import { addPropertyToMap, DescriptorType } from "./SelfServeUtils"; + +const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => { + return (target, property) => { + const className = (target as Function).name; + if (!className) { + throw new Error("property descriptor applied to non static field!"); + } + addPropertyToMap( + target, + property.toString(), + className, + descriptorName, + descriptorValue, + DescriptorType.PropertyDescriptor + ); + }; +}; + +export const PropertyInfo = (info: Info): PropertyDecorator => { + return addToMap("info", info); +}; + +export const Placeholder = (placeholder: string): PropertyDecorator => { + return addToMap("placeholder", placeholder); +}; + +export const ParentOf = (children: string[]): PropertyDecorator => { + return addToMap("parentOf", children); +}; + +export const Type = (type: InputTypeValue): PropertyDecorator => { + return addToMap("type", type); +}; + +export const Label = (label: string): PropertyDecorator => { + return addToMap("label", label); +}; + +export const DataFieldName = (dataFieldName: string): PropertyDecorator => { + return addToMap("dataFieldName", dataFieldName); +}; + +export const Min = (min: number): PropertyDecorator => { + return addToMap("min", min); +}; + +export const Max = (max: number): PropertyDecorator => { + return addToMap("max", max); +}; + +export const Step = (step: number): PropertyDecorator => { + return addToMap("step", step); +}; + +export const DefaultValue = (defaultValue: any): PropertyDecorator => { + return addToMap("defaultValue", defaultValue); +}; + +export const TrueLabel = (trueLabel: string): PropertyDecorator => { + return addToMap("trueLabel", trueLabel); +}; + +export const FalseLabel = (falseLabel: string): PropertyDecorator => { + return addToMap("falseLabel", falseLabel); +}; + +export const Choices = (choices: EnumItem[]): PropertyDecorator => { + return addToMap("choices", choices); +}; + +export const DefaultKey = (defaultKey: string): PropertyDecorator => { + return addToMap("defaultKey", defaultKey); +}; + +export const NumberInputType = (numberInputType: string): PropertyDecorator => { + return addToMap("inputType", numberInputType); +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServe.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServe.tsx deleted file mode 100644 index 6ea3e42f1..000000000 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServe.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from "react"; -import { Descriptor, InputType, SmartUiComponent } from "../../../SmartUi/SmartUiComponent"; -import { SqlX } from "./SqlX"; - -interface SelfServeComponentProps { - propertyNames: string[] -} - -export class SelfServeCmponent extends React.Component { - - private properties: any = {} - - constructor(props: SelfServeComponentProps) { - super(props) - let stringer = "{" - for (var i =0; i < props.propertyNames.length; i++) { - stringer += `"${props.propertyNames[i]}":null,` - } - stringer = stringer.substring(0, stringer.length-1) - console.log(stringer) - stringer += "}" - this.properties = JSON.parse(stringer) - } - - - private selfServeData: Descriptor = { - root: { - id: "root", - info: { - message: "Start at $24/mo per database", - link: { - href: "https://aka.ms/azure-cosmos-db-pricing", - text: "More Details" - } - }, - children: { - "instanceCount" : { - id: "instanceCount", - input: { - label: "Instance Count", - dataFieldName: "instanceCount", - type: "number", - min: 1, - max: 5, - step: 1, - defaultValue: 1, - inputType: "slider" - } - }, - "instanceSize": { - id: "instanceSize", - input: { - label: "Instance Size", - dataFieldName: "instanceSize", - type: "enum", - choices: [ - { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, - { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, - { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } - ], - defaultKey: "1Core4Gb" - } - } - } - } - }; - - - - private exampleCallbacks = (newValues: Map): void => { - for (var i =0; i < this.props.propertyNames.length; i++) { - const prop = this.props.propertyNames[i] - const newVal = newValues.get(prop) - if (newVal) { - this.properties[`${prop}`] = newVal - } - } - - console.log(this.properties) - }; - - public render() : JSX.Element { - const data : Descriptor = {root: SqlX.toJson()} - //return - return - } -} \ No newline at end of file diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx new file mode 100644 index 000000000..fabeaa5a8 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx @@ -0,0 +1,82 @@ +import React from "react"; +import { Descriptor, InputType, SmartUiComponent } from "../../../SmartUi/SmartUiComponent"; +import { SqlX } from "./SqlX"; + +interface SelfServeComponentProps { + propertyNames: string[]; +} + +export class SelfServeCmponent extends React.Component { + private properties: any = {}; + + constructor(props: SelfServeComponentProps) { + super(props); + let stringer = "{"; + for (var i = 0; i < props.propertyNames.length; i++) { + stringer += `"${props.propertyNames[i]}":null,`; + } + stringer = stringer.substring(0, stringer.length - 1); + console.log(stringer); + stringer += "}"; + this.properties = JSON.parse(stringer); + } + + private selfServeData: Descriptor = { + root: { + id: "root", + info: { + message: "Start at $24/mo per database", + link: { + href: "https://aka.ms/azure-cosmos-db-pricing", + text: "More Details" + } + }, + children: [ + { + id: "instanceCount", + input: { + label: "Instance Count", + dataFieldName: "instanceCount", + type: "number", + min: 1, + max: 5, + step: 1, + defaultValue: 1, + inputType: "slider" + } + }, + { + id: "instanceSize", + input: { + label: "Instance Size", + dataFieldName: "instanceSize", + type: "enum", + choices: [ + { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, + { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, + { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } + ], + defaultKey: "1Core4Gb" + } + } + ] + } + }; + + private exampleCallbacks = (newValues: Map): void => { + for (var i = 0; i < this.props.propertyNames.length; i++) { + const prop = this.props.propertyNames[i]; + const newVal = newValues.get(prop); + if (newVal) { + this.properties[`${prop}`] = newVal; + } + } + + console.log(this.properties); + }; + + public render(): JSX.Element { + //return + return ; + } +} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeTypes.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeTypes.tsx deleted file mode 100644 index 89821431e..000000000 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeTypes.tsx +++ /dev/null @@ -1,110 +0,0 @@ - -import "reflect-metadata"; -import { EnumItem, Info, InputTypeValue } from "../../../SmartUi/SmartUiComponent"; - -const modifyParentProperty = (children: {[key: string]: any}, parentProperty: string, property: string | symbol) : any => { - if (parentProperty in children) { - children[parentProperty][property] ={id: property, input: {}} - return children - } else { - const keys = Object.keys(children) - for(var i =0; i< keys.length; i++) { - children[keys[i]] = modifyParentProperty(children[keys[i]], parentProperty, property) - return children - } - } - return children -} - -export const InfoBar = (metadataKey: string, info: Info) => { - return (target: any) => { - let context = Reflect.getMetadata(metadataKey, target) - if(!context) { - context = {id: "root", info: info, input: undefined, children: {} } - } else { - context.info = info - } - Reflect.defineMetadata(metadataKey, context, target) - }; -}; - -export const Property = (metadataKey: string, parentProperty?: string): PropertyDecorator => { - return (target, property) => { - let context = Reflect.getMetadata(metadataKey, target) - if(!context) { - context = {id: "root", info: undefined, input: undefined, children: {} } - context.children[property] = {id: property, input: {}} - } - if (parentProperty) { - const prevContextValue = JSON.stringify(context) - context.children = modifyParentProperty(context.children, parentProperty, property) - if (JSON.stringify(context) === prevContextValue) { - throw new Error(`${parentProperty} not defined. declare it before the child property with @Property decorator.`) - } - } else { - context.children[property] = {id: property, input: {}} - } - Reflect.defineMetadata(metadataKey, context, target) - }; -}; - -export const modifyInputTypes = (metadataKey: string, fieldName: string, value: any) : PropertyDecorator => { - return (target, property) => { - let context = Reflect.getMetadata(metadataKey, target) - if(!context) { - throw new Error("Incorrect order") - } - context.children[property].input[fieldName] = value - //TODO: recurse to find correct child - console.log("props context:" + JSON.stringify(context)) - Reflect.defineMetadata(metadataKey, context, target) - }; -} - -export const Type = (metadataKey: string, type: InputTypeValue): PropertyDecorator => { - return modifyInputTypes(metadataKey, "type", type) -}; - -export const Label = (metadataKey: string, label: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "label", label) -}; - -export const DataFieldName = (metadataKey: string, dataFieldName: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "dataFieldName", dataFieldName) -}; - -export const Min = (metadataKey: string, min: number): PropertyDecorator => { - return modifyInputTypes(metadataKey, "min", min) -}; - -export const Max = (metadataKey: string, max: number): PropertyDecorator => { - return modifyInputTypes(metadataKey, "max", max) -}; - -export const Step = (metadataKey: string, step: number): PropertyDecorator => { - return modifyInputTypes(metadataKey, "step", step) -}; - -export const DefaultValue = (metadataKey: string, defaultValue: any): PropertyDecorator => { - return modifyInputTypes(metadataKey, "defaultValue", defaultValue) -}; - -export const TrueLabel = (metadataKey: string, trueLabel: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "trueLabel", trueLabel) -}; - -export const FalseLabel = (metadataKey: string, falseLabel: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "falseLabel", falseLabel) -}; - -export const Choices = (metadataKey: string, choices: EnumItem[]): PropertyDecorator => { - return modifyInputTypes(metadataKey, "choices", choices) -}; - -export const DefaultKey = (metadataKey: string, defaultKey: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "defaultKey", defaultKey) -}; - -export const NumberInputType = (metadataKey: string, numberInputType: string): PropertyDecorator => { - return modifyInputTypes(metadataKey, "inputType", numberInputType) -}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx new file mode 100644 index 000000000..726fe404e --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx @@ -0,0 +1,211 @@ +import "reflect-metadata"; +import { + EnumItem, + Node, + Info, + InputTypeValue, + Descriptor, + AnyInput, + NumberInput, + StringInput, + BooleanInput, + EnumInput +} from "../../../SmartUi/SmartUiComponent"; + +export interface CommonInputTypes { + id: string; + info?: Info; + parentOf?: string[]; + type?: InputTypeValue; + label?: string; + placeholder?: string; + dataFieldName?: string; + min?: number; + max?: number; + step?: number; + defaultValue?: any; + trueLabel?: string; + falseLabel?: string; + choices?: EnumItem[]; + defaultKey?: string; + inputType?: string; +} + +export enum DescriptorType { + ClassDescriptor, + PropertyDescriptor +} + +const setValue = ( + name: T, + value: K, + fieldObject: CommonInputTypes +): void => { + fieldObject[name] = value; +}; + +const getValue = ( + name: T, + fieldObject: CommonInputTypes +): K => { + return fieldObject[name]; +}; + +export const addPropertyToMap = ( + target: Object, + property: string, + metadataKey: string, + descriptorName: string, + descriptorValue: any, + descriptorType: DescriptorType +): void => { + const propertyKey = property.toString(); + const descriptorKey = descriptorName.toString() as keyof CommonInputTypes; + let context = Reflect.getMetadata(metadataKey, target) as Map; + + if (!context) { + context = new Map(); + } + + let propertyObject = context.get(propertyKey); + if (!propertyObject) { + propertyObject = { id: propertyKey }; + } + + if (getValue(descriptorKey, propertyObject)) { + throw new Error("duplicate descriptor"); + } + + setValue(descriptorKey, descriptorValue, propertyObject); + context.set(propertyKey, propertyObject); + + Reflect.defineMetadata(metadataKey, context, target); +}; + +/* +const modifyParentProperty = (children: {[key: string]: any}, parentProperty: string, property: string | symbol) : any => { + if (parentProperty in children) { + children[parentProperty][property] ={id: property, input: {}} + return children + } else { + const keys = Object.keys(children) + for(var i =0; i< keys.length; i++) { + children[keys[i]] = modifyParentProperty(children[keys[i]], parentProperty, property) + return children + } + } + return children +} + +export const PropertyParser = (metadataKey: string, parentProperty?: string): PropertyDecorator => { + return (target, property) => { + let context = Reflect.getMetadata(metadataKey, target) + if(!context) { + context = {id: "root", info: undefined, input: undefined, children: {} } + context.children[property] = {id: property, input: {}} + } + if (parentProperty) { + const prevContextValue = JSON.stringify(context) + context.children = modifyParentProperty(context.children, parentProperty, property) + if (JSON.stringify(context) === prevContextValue) { + throw new Error(`${parentProperty} not defined. declare it before the child property with @Property decorator.`) + } + } else { + context.children[property] = {id: property, input: {}} + } + Reflect.defineMetadata(metadataKey, context, target) + }; +}; +*/ + +export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => { + const context = Reflect.getMetadata(metadataKey, target) as Map; + Reflect.defineMetadata(metadataKey, context, target); + + const root = context.get("root"); + context.delete("root"); + + let smartUiDescriptor = { + root: { + id: "root", + info: root.info, + children: [] + } as Node + } as Descriptor; + + while (context.size > 0) { + const key = context.keys().next().value; + addToDescriptor(context, smartUiDescriptor, smartUiDescriptor.root, key); + } + + Reflect.defineMetadata(metadataKey, smartUiDescriptor, target); +}; + +const addToDescriptor = ( + context: Map, + smartUiDescriptor: Descriptor, + root: Node, + key: String +): void => { + let value = context.get(key); + if (!value) { + // should already be added to root + value = getChildFromRoot(key, smartUiDescriptor); + if (!value) { + // if not found at root level, error out + throw new Error("Either child does not exist or child has been assigned to more than one parent"); + } + root.children.push(value); + return; + } + + const childrenKeys = value.parentOf; + const element = { + id: value.id, + info: value.info, + input: getInput(value), + children: [] + } as Node; + context.delete(key); + for (let childKey in childrenKeys) { + addToDescriptor(context, smartUiDescriptor, element, childrenKeys[childKey]); + } + root.children.push(element); +}; + +const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInputTypes => { + let i = 0; + const children = smartUiDescriptor.root.children; + for (; i < children.length; i++) { + if (children[i].id === key) { + const value = children[i]; + delete children[i]; + return value; + } + } + return undefined; +}; + +const getInput = (value: CommonInputTypes): AnyInput => { + switch (value.type) { + case "number": + if (!value.step || !value.defaultValue || !value.inputType) { + throw new Error("step, defaultValue and inputType are needed for number type"); + } + return value as NumberInput; + case "string": + return value as StringInput; + case "boolean": + if (!value.trueLabel || !value.falseLabel || !value.defaultValue) { + throw new Error("truelabel, falselabel and defaultValue are needed for boolean type"); + } + return value as BooleanInput; + case "enum": + if (!value.choices || !value.defaultKey) { + throw new Error("choices and defaultKey are needed for enum type"); + } + return value as EnumInput; + default: + throw new Error("Unknown type"); + } +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx index ab4bf3d53..e865aab40 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx @@ -1,34 +1,58 @@ -import { DataFieldName, Label, Min, Max, Step, DefaultKey, DefaultValue, Property, Type, NumberInputType, Choices } from "./SelfServeTypes"; -import { EnumItem } from "../../../SmartUi/SmartUiComponent"; -const SqlXRoot = 'SqlXRoot'; +import { + DataFieldName, + Label, + Min, + Max, + Step, + DefaultKey, + DefaultValue, + Type, + NumberInputType, + Choices, + ParentOf, + PropertyInfo +} from "./PropertyDescriptors"; +import { Descriptor, EnumItem, Info } from "../../../SmartUi/SmartUiComponent"; +import { SmartUi, ClassInfo, SelfServeClass } from "./ClassDescriptors"; +@SmartUi() +@SelfServeClass() +@ClassInfo(SqlX.sqlXInfo) export class SqlX { - @Label(SqlXRoot, "Instance Count") - @DataFieldName(SqlXRoot, "instanceCount") - @Min(SqlXRoot, 1) - @Max(SqlXRoot, 5) - @Step(SqlXRoot, 1) - @DefaultValue(SqlXRoot, 1) - @NumberInputType(SqlXRoot, "slider") - @Type(SqlXRoot, "number") - @Property(SqlXRoot) - static instanceCount: any; + @PropertyInfo(SqlX.instanceSizeInfo) + @Label("Instance Size") + @DataFieldName("instanceSize") + @Choices(SqlX.instanceSizeOptions) + @DefaultKey("1Core4Gb") + @Type("enum") + static instanceSize: any; - @Label(SqlXRoot, "Instance Size") - @DataFieldName(SqlXRoot, "instanceSize") - @Choices(SqlXRoot, SqlX.instanceTypeOptions) - @DefaultKey(SqlXRoot, "1Core4Gb") - @Type(SqlXRoot, "enum") - @Property(SqlXRoot) - static instanceType: any; + @Label("Instance Count") + @DataFieldName("instanceCount") + @Min(1) + @Max(5) + @Step(1) + @DefaultValue(1) + @NumberInputType("slider") + @Type("number") + @ParentOf(["instanceSize"]) + static instanceCount: any; - static instanceTypeOptions : EnumItem[] = [ - { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, - { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, - { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } - ] - - public static toJson = () : any => { - return Reflect.getMetadata(SqlXRoot, SqlX) - } -} \ No newline at end of file + static instanceSizeOptions: EnumItem[] = [ + { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" }, + { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" }, + { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" } + ]; + + static sqlXInfo: Info = { + message: "SqlX is a self serve class" + }; + + static instanceSizeInfo: Info = { + message: "instance size will be updated in the future" + }; + + public static toSmartUiDescriptor = (): Descriptor => { + return Reflect.getMetadata(SqlX.name, SqlX) as Descriptor; + }; +} diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx index 7a7d63d02..b455a59fa 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx @@ -13,8 +13,8 @@ describe("SmartUiComponent", () => { text: "More Details" } }, - children: { - "throughput": { + children: [ + { id: "throughput", input: { label: "Throughput (input)", @@ -27,7 +27,7 @@ describe("SmartUiComponent", () => { inputType: "spin" } }, - "throughput2": { + { id: "throughput2", input: { label: "Throughput (Slider)", @@ -40,7 +40,7 @@ describe("SmartUiComponent", () => { inputType: "slider" } }, - "containerId": { + { id: "containerId", input: { label: "Container id", @@ -48,7 +48,7 @@ describe("SmartUiComponent", () => { type: "string" } }, - "analyticalStore": { + { id: "analyticalStore", input: { label: "Analytical Store", @@ -59,7 +59,7 @@ describe("SmartUiComponent", () => { type: "boolean" } }, - "database": { + { id: "database", input: { label: "Database", @@ -73,8 +73,8 @@ describe("SmartUiComponent", () => { defaultKey: "db2" } } + ] } - } }; const exampleCallbacks = (newValues: Map): void => { diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx index d0f59c9d8..b7340e412 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx @@ -75,7 +75,7 @@ export interface Node { id: string; info?: Info; input?: AnyInput; - children?: { [id: string]: Node} ; + children?: Node[]; } export interface Descriptor { @@ -113,9 +113,11 @@ export class SmartUiComponent extends React.Component {info.message} - - {info.link.text} - + {info.link && ( + + {info.link.text} + + )} ); } @@ -324,10 +326,7 @@ export class SmartUiComponent extends React.Component {node.info && this.renderInfo(node.info)} {node.input && this.renderInput(node.input)} - {node.children && Object.entries(node.children).map(([key, value]) => { - return
{this.renderNode(value as Node)}
- }) - } + {node.children && node.children.map(child =>
{this.renderNode(child)}
)} ); }