diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx
index f79cb198f..7356d720e 100644
--- a/src/Explorer/Controls/Settings/SettingsComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx
@@ -903,7 +903,7 @@ export class SettingsComponent extends React.Component
+ content:
});
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx
index 2f1261b59..40cf77cc8 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/ClassDescriptors.tsx
@@ -1,5 +1,5 @@
-import { Descriptor, Info } from "../../../SmartUi/SmartUiComponent";
-import { addPropertyToMap, DescriptorType, toSmartUiDescriptor } from "./SelfServeUtils";
+import { Descriptor, Info, InputType } from "../../../SmartUi/SmartUiComponent";
+import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils";
interface SelfServeBaseCLass {
toSmartUiDescriptor: () => Descriptor;
@@ -19,6 +19,12 @@ export const SmartUi = (): ClassDecorator => {
export const ClassInfo = (info: Info): ClassDecorator => {
return (target: Function) => {
- addPropertyToMap(target, "root", target.name, "info", info, DescriptorType.ClassDescriptor);
+ addPropertyToMap(target, "root", target.name, "info", info);
+ };
+};
+
+export const OnSubmit = (onSubmit: (currentValues: Map) => Promise): ClassDecorator => {
+ return (target: Function) => {
+ addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit);
};
};
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx
index d02c1bfe3..ec4d42985 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/PropertyDescriptors.tsx
@@ -1,23 +1,25 @@
-import { EnumItem, Info, InputTypeValue } from "../../../SmartUi/SmartUiComponent";
-import { addPropertyToMap, DescriptorType } from "./SelfServeUtils";
+import { EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
+import { addPropertyToMap } from "./SelfServeUtils";
const addToMap = (descriptorName: string, descriptorValue: any): PropertyDecorator => {
return (target, property) => {
const className = (target as Function).name;
+ var propertyType = Reflect.getMetadata("design:type", target, property);
+ addPropertyToMap(target, property.toString(), className, "type", propertyType.name);
+
if (!className) {
throw new Error("property descriptor applied to non static field!");
}
- addPropertyToMap(
- target,
- property.toString(),
- className,
- descriptorName,
- descriptorValue,
- DescriptorType.PropertyDescriptor
- );
+ addPropertyToMap(target, property.toString(), className, descriptorName, descriptorValue);
};
};
+export const OnChange = (
+ onChange: (currentState: Map, newValue: InputType) => Map
+): PropertyDecorator => {
+ return addToMap("onChange", onChange);
+};
+
export const PropertyInfo = (info: Info): PropertyDecorator => {
return addToMap("info", info);
};
@@ -30,10 +32,6 @@ 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);
};
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx
index fabeaa5a8..89c88c14c 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeComponent.tsx
@@ -2,26 +2,13 @@ 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);
- }
+export class SelfServeCmponent extends React.Component {
+ private onSubmit = async (currentValues: Map): Promise => {
+ console.log(currentValues.get("instanceCount"), currentValues.get("instanceSize"));
+ };
private selfServeData: Descriptor = {
+ onSubmit: this.onSubmit,
root: {
id: "root",
info: {
@@ -37,12 +24,19 @@ export class SelfServeCmponent extends React.Component
input: {
label: "Instance Count",
dataFieldName: "instanceCount",
- type: "number",
+ type: "Number",
min: 1,
max: 5,
step: 1,
defaultValue: 1,
- inputType: "slider"
+ inputType: "slider",
+ onChange: (currentState: Map, newValue: InputType): Map => {
+ currentState.set("instanceCount", newValue);
+ if ((newValue as number) === 1) {
+ currentState.set("instanceSize", "1Core4Gb");
+ }
+ return currentState;
+ }
}
},
{
@@ -50,33 +44,25 @@ export class SelfServeCmponent extends React.Component
input: {
label: "Instance Size",
dataFieldName: "instanceSize",
- type: "enum",
+ type: "Object",
choices: [
{ label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" },
{ label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" },
{ label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
],
- defaultKey: "1Core4Gb"
+ defaultKey: "1Core4Gb",
+ onChange: (currentState: Map, newValue: InputType): Map => {
+ currentState.set("instanceSize", newValue);
+ return currentState;
+ }
}
}
]
}
};
- 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 ;
+ //return
+ return ;
}
}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx
index 726fe404e..b61d6f86d 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SelfServeUtils.tsx
@@ -9,7 +9,8 @@ import {
NumberInput,
StringInput,
BooleanInput,
- EnumInput
+ EnumInput,
+ InputType
} from "../../../SmartUi/SmartUiComponent";
export interface CommonInputTypes {
@@ -29,11 +30,8 @@ export interface CommonInputTypes {
choices?: EnumItem[];
defaultKey?: string;
inputType?: string;
-}
-
-export enum DescriptorType {
- ClassDescriptor,
- PropertyDescriptor
+ onChange?: (currentState: Map, newValue: InputType) => Map;
+ onSubmit?: (currentValues: Map) => Promise;
}
const setValue = (
@@ -53,13 +51,11 @@ const getValue = {
- const propertyKey = property.toString();
const descriptorKey = descriptorName.toString() as keyof CommonInputTypes;
let context = Reflect.getMetadata(metadataKey, target) as Map;
@@ -67,12 +63,16 @@ export const addPropertyToMap = (
context = new Map();
}
+ if (!(context instanceof Map)) {
+ throw new Error("@SmartUi should be the first decorator for the class.");
+ }
+
let propertyObject = context.get(propertyKey);
if (!propertyObject) {
propertyObject = { id: propertyKey };
}
- if (getValue(descriptorKey, propertyObject)) {
+ if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type") {
throw new Error("duplicate descriptor");
}
@@ -125,7 +125,14 @@ export const toSmartUiDescriptor = (metadataKey: string, target: Object): void =
const root = context.get("root");
context.delete("root");
+ if (!root || !("onSubmit" in root)) {
+ throw new Error(
+ "@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
+ );
+ }
+
let smartUiDescriptor = {
+ onSubmit: root.onSubmit,
root: {
id: "root",
info: root.info,
@@ -187,25 +194,27 @@ const getChildFromRoot = (key: String, smartUiDescriptor: Descriptor): CommonInp
};
const getInput = (value: CommonInputTypes): AnyInput => {
+ if (!value.label || !value.type || !value.dataFieldName) {
+ throw new Error("label, onChange, type and dataFieldName are required.");
+ }
+ console.log(value.type);
switch (value.type) {
- case "number":
+ 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":
+ case "String":
return value as StringInput;
- case "boolean":
- if (!value.trueLabel || !value.falseLabel || !value.defaultValue) {
+ case "Boolean":
+ if (!value.trueLabel || !value.falseLabel || value.defaultValue === undefined) {
throw new Error("truelabel, falselabel and defaultValue are needed for boolean type");
}
return value as BooleanInput;
- case "enum":
+ default:
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 e865aab40..0244e85e3 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SelfServe/SqlX.tsx
@@ -6,27 +6,37 @@ import {
Step,
DefaultKey,
DefaultValue,
- Type,
NumberInputType,
Choices,
ParentOf,
- PropertyInfo
+ PropertyInfo,
+ OnChange,
+ TrueLabel,
+ FalseLabel,
+ Placeholder
} from "./PropertyDescriptors";
-import { Descriptor, EnumItem, Info } from "../../../SmartUi/SmartUiComponent";
-import { SmartUi, ClassInfo, SelfServeClass } from "./ClassDescriptors";
+import { Descriptor, EnumItem, Info, InputType } from "../../../SmartUi/SmartUiComponent";
+import { SmartUi, ClassInfo, SelfServeClass, OnSubmit } from "./ClassDescriptors";
+
+enum Sizes {
+ OneCore4Gb = "OneCore4Gb",
+ TwoCore8Gb = "TwoCore8Gb",
+ FourCore16Gb = "FourCore16Gb"
+}
@SmartUi()
-@SelfServeClass()
@ClassInfo(SqlX.sqlXInfo)
+@OnSubmit(SqlX.onSubmit)
+@SelfServeClass()
export class SqlX {
@PropertyInfo(SqlX.instanceSizeInfo)
@Label("Instance Size")
@DataFieldName("instanceSize")
@Choices(SqlX.instanceSizeOptions)
- @DefaultKey("1Core4Gb")
- @Type("enum")
- static instanceSize: any;
+ @DefaultKey(Sizes.OneCore4Gb)
+ static instanceSize: EnumItem;
+ @OnChange(SqlX.onInstanceCountChange)
@Label("Instance Count")
@DataFieldName("instanceCount")
@Min(1)
@@ -34,14 +44,25 @@ export class SqlX {
@Step(1)
@DefaultValue(1)
@NumberInputType("slider")
- @Type("number")
- @ParentOf(["instanceSize"])
- static instanceCount: any;
+ @ParentOf(["instanceSize", "instanceName", "isAllowed"])
+ static instanceCount: number;
+
+ @Label("Feature Allowed")
+ @DataFieldName("isAllowed")
+ @DefaultValue(false)
+ @TrueLabel("allowed")
+ @FalseLabel("not allowed")
+ static isAllowed: boolean;
+
+ @Label("Instance Name")
+ @DataFieldName("instanceName")
+ @Placeholder("instance name")
+ static instanceName: string;
static instanceSizeOptions: EnumItem[] = [
- { label: "1Core4Gb", key: "1Core4Gb", value: "1Core4Gb" },
- { label: "2Core8Gb", key: "2Core8Gb", value: "2Core8Gb" },
- { label: "4Core16Gb", key: "4Core16Gb", value: "4Core16Gb" }
+ { label: Sizes.OneCore4Gb, key: Sizes.OneCore4Gb, value: Sizes.OneCore4Gb },
+ { label: Sizes.TwoCore8Gb, key: Sizes.TwoCore8Gb, value: Sizes.TwoCore8Gb },
+ { label: Sizes.FourCore16Gb, key: Sizes.FourCore16Gb, value: Sizes.FourCore16Gb }
];
static sqlXInfo: Info = {
@@ -52,6 +73,30 @@ export class SqlX {
message: "instance size will be updated in the future"
};
+ static onInstanceCountChange = (
+ currentState: Map,
+ newValue: InputType
+ ): Map => {
+ currentState.set("instanceCount", newValue);
+ if ((newValue as number) === 1) {
+ currentState.set("isAllowed", false);
+ }
+ return currentState;
+ };
+
+ static onSubmit = async (currentValues: Map): Promise => {
+ console.log(
+ "instanceCount:" +
+ currentValues.get("instanceCount") +
+ ", instanceSize:" +
+ currentValues.get("instanceSize") +
+ ", instanceName:" +
+ currentValues.get("instanceName") +
+ ", isAllowed:" +
+ currentValues.get("isAllowed")
+ );
+ };
+
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 b455a59fa..7084c7795 100644
--- a/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx
+++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.test.tsx
@@ -4,6 +4,7 @@ import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
describe("SmartUiComponent", () => {
const exampleData: Descriptor = {
+ onSubmit: async (currentValues: Map) => {},
root: {
id: "root",
info: {
@@ -24,7 +25,8 @@ describe("SmartUiComponent", () => {
max: 500,
step: 10,
defaultValue: 400,
- inputType: "spin"
+ inputType: "spin",
+ onChange: undefined
}
},
{
@@ -37,7 +39,8 @@ describe("SmartUiComponent", () => {
max: 500,
step: 10,
defaultValue: 400,
- inputType: "slider"
+ inputType: "slider",
+ onChange: undefined
}
},
{
@@ -45,7 +48,8 @@ describe("SmartUiComponent", () => {
input: {
label: "Container id",
dataFieldName: "containerId",
- type: "string"
+ type: "string",
+ onChange: undefined
}
},
{
@@ -56,7 +60,8 @@ describe("SmartUiComponent", () => {
falseLabel: "Disabled",
defaultValue: true,
dataFieldName: "analyticalStore",
- type: "boolean"
+ type: "boolean",
+ onChange: undefined
}
},
{
@@ -70,6 +75,7 @@ describe("SmartUiComponent", () => {
{ label: "Database 2", key: "db2", value: "database2" },
{ label: "Database 3", key: "db3", value: "database3" }
],
+ onChange: undefined,
defaultKey: "db2"
}
}
@@ -77,12 +83,8 @@ describe("SmartUiComponent", () => {
}
};
- const exampleCallbacks = (newValues: Map): void => {
- console.log("New values:", newValues);
- };
-
it("should render", () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
});
diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx
index b7340e412..2e1f6e188 100644
--- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx
+++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx
@@ -8,7 +8,7 @@ import { Text } from "office-ui-fabric-react/lib/Text";
import { InputType } from "../../Tables/Constants";
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
-import { Link, MessageBar, MessageBarType } from "office-ui-fabric-react";
+import { Link, MessageBar, MessageBarType, PrimaryButton } from "office-ui-fabric-react";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
@@ -21,17 +21,18 @@ import "./SmartUiComponent.less";
* - a descriptor of the UX.
*/
-export type InputTypeValue = "number" | "string" | "boolean" | "enum";
+export type InputTypeValue = "Number" | "String" | "Boolean" | "Object";
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export type EnumItem = { label: string; key: string; value: any };
-export type InputType = number | string | boolean | EnumItem;
+export type InputType = Number | String | Boolean | EnumItem;
interface BaseInput {
label: string;
dataFieldName: string;
type: InputTypeValue;
+ onChange?: (currentState: Map, newValue: InputType) => Map;
placeholder?: string;
}
@@ -80,13 +81,13 @@ export interface Node {
export interface Descriptor {
root: Node;
+ onSubmit: (currentValues: Map) => Promise;
}
/************************** Component implementation starts here ************************************* */
export interface SmartUiComponentProps {
descriptor: Descriptor;
- onChange: (newValues: Map) => void;
}
interface SmartUiComponentState {
@@ -104,11 +105,37 @@ export class SmartUiComponent extends React.Component => {
+ const defaults = new Map();
+ this.setDefaults(this.props.descriptor.root, defaults);
+ return defaults;
+ };
+
+ private setDefaults = (currentNode: Node, defaults: Map) => {
+ if (currentNode.input?.dataFieldName) {
+ defaults.set(currentNode.input.dataFieldName, this.getDefault(currentNode.input));
+ }
+ currentNode.children?.map((child: Node) => this.setDefaults(child, defaults));
+ };
+
+ private getDefault = (input: AnyInput): InputType => {
+ switch (input.type) {
+ case "String":
+ return (input as StringInput).defaultValue;
+ case "Number":
+ return (input as NumberInput).defaultValue;
+ case "Boolean":
+ return (input as BooleanInput).defaultValue;
+ default:
+ return (input as EnumInput).defaultKey;
+ }
+ };
+
private renderInfo(info: Info): JSX.Element {
return (
@@ -122,10 +149,16 @@ export class SmartUiComponent extends React.Component {
- const { currentValues } = this.state;
- currentValues.set(dataFieldName, newValue);
- this.setState({ currentValues }, () => this.props.onChange(this.state.currentValues));
+ private onInputChange = (input: AnyInput, newValue: InputType) => {
+ if (input.onChange) {
+ const newValues = input.onChange(this.state.currentValues, newValue);
+ this.setState({ currentValues: newValues });
+ } else {
+ const dataFieldName = input.dataFieldName;
+ const { currentValues } = this.state;
+ currentValues.set(dataFieldName, newValue);
+ this.setState({ currentValues });
+ }
};
private renderStringInput(input: StringInput): JSX.Element {
@@ -138,7 +171,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(newValue, input.dataFieldName)}
+ onChange={(_, newValue) => this.onInputChange(input, newValue)}
styles={{
subComponentStyles: {
label: {
@@ -161,10 +194,11 @@ export class SmartUiComponent extends React.Component {
+ private onValidate = (input: AnyInput, value: string, min: number, max: number): string => {
const newValue = InputUtils.onValidateValueChange(value, min, max);
+ const dataFieldName = input.dataFieldName;
if (newValue) {
- this.onInputChange(newValue, dataFieldName);
+ this.onInputChange(input, newValue);
this.clearError(dataFieldName);
return newValue.toString();
} else {
@@ -175,20 +209,22 @@ export class SmartUiComponent extends React.Component {
+ private onIncrement = (input: AnyInput, value: string, step: number, max: number): string => {
const newValue = InputUtils.onIncrementValue(value, step, max);
+ const dataFieldName = input.dataFieldName;
if (newValue) {
- this.onInputChange(newValue, dataFieldName);
+ this.onInputChange(input, newValue);
this.clearError(dataFieldName);
return newValue.toString();
}
return undefined;
};
- private onDecrement = (value: string, step: number, min: number, dataFieldName: string): string => {
+ private onDecrement = (input: AnyInput, value: string, step: number, min: number): string => {
const newValue = InputUtils.onDecrementValue(value, step, min);
+ const dataFieldName = input.dataFieldName;
if (newValue) {
- this.onInputChange(newValue, dataFieldName);
+ this.onInputChange(input, newValue);
this.clearError(dataFieldName);
return newValue.toString();
}
@@ -205,9 +241,9 @@ export class SmartUiComponent extends React.Component this.onValidate(newValue, min, max, dataFieldName)}
- onIncrement={newValue => this.onIncrement(newValue, step, max, dataFieldName)}
- onDecrement={newValue => this.onDecrement(newValue, step, min, dataFieldName)}
+ onValidate={newValue => this.onValidate(input, newValue, min, max)}
+ onIncrement={newValue => this.onIncrement(input, newValue, step, max)}
+ onDecrement={newValue => this.onDecrement(input, newValue, step, min)}
labelPosition={Position.top}
styles={{
label: {
@@ -228,7 +264,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(newValue, dataFieldName)}
+ onChange={newValue => this.onInputChange(input, newValue)}
styles={{
titleLabel: {
...SmartUiComponent.labelStyle,
@@ -257,12 +293,12 @@ export class SmartUiComponent extends React.Component this.onInputChange(false, dataFieldName)
+ onSelect: () => this.onInputChange(input, false)
},
{
label: input.trueLabel,
key: "true",
- onSelect: () => this.onInputChange(true, dataFieldName)
+ onSelect: () => this.onInputChange(input, true)
}
]}
selectedKey={
@@ -278,7 +314,7 @@ export class SmartUiComponent extends React.Component this.onInputChange(item.key.toString(), dataFieldName)}
+ onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
placeholder={placeholder}
options={choices.map(c => ({
key: c.key,
@@ -306,16 +342,14 @@ export class SmartUiComponent extends React.Component{this.renderNode(this.props.descriptor.root)}>;
+ const containerStackTokens: IStackTokens = { childrenGap: 20 };
+
+ return (
+
+ {this.renderNode(this.props.descriptor.root)}
+ await this.props.descriptor.onSubmit(this.state.currentValues)}
+ />
+
+ );
}
}