mirror of
				https://github.com/Azure/cosmos-explorer.git
				synced 2025-11-04 00:53:03 +00:00 
			
		
		
		
	Resolved PR comments
Added tests Moved onSubmt and initialize inside base class Moved testExplorer to separate folder made fields of SelfServe Class non static
This commit is contained in:
		
							parent
							
								
									373327dc88
								
							
						
					
					
						commit
						318842624f
					
				@ -1,3 +1,4 @@
 | 
				
			|||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
  presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"]
 | 
					  presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
 | 
				
			||||||
 | 
					  plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]]
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -393,7 +393,6 @@
 | 
				
			|||||||
      "version": "7.12.1",
 | 
					      "version": "7.12.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==",
 | 
					      "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "@babel/helper-function-name": "^7.10.4",
 | 
					        "@babel/helper-function-name": "^7.10.4",
 | 
				
			||||||
        "@babel/helper-member-expression-to-functions": "^7.12.1",
 | 
					        "@babel/helper-member-expression-to-functions": "^7.12.1",
 | 
				
			||||||
@ -620,6 +619,25 @@
 | 
				
			|||||||
        "@babel/plugin-syntax-async-generators": "^7.8.0"
 | 
					        "@babel/plugin-syntax-async-generators": "^7.8.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@babel/plugin-proposal-class-properties": {
 | 
				
			||||||
 | 
					      "version": "7.12.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@babel/helper-create-class-features-plugin": "^7.12.1",
 | 
				
			||||||
 | 
					        "@babel/helper-plugin-utils": "^7.10.4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@babel/plugin-proposal-decorators": {
 | 
				
			||||||
 | 
					      "version": "7.12.12",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.12.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-fhkE9lJYpw2mjHelBpM2zCbaA11aov2GJs7q4cFaXNrWx0H3bW58H9Esy2rdtYOghFBEYUDRIpvlgi+ZD+AvvQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@babel/helper-create-class-features-plugin": "^7.12.1",
 | 
				
			||||||
 | 
					        "@babel/helper-plugin-utils": "^7.10.4",
 | 
				
			||||||
 | 
					        "@babel/plugin-syntax-decorators": "^7.12.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@babel/plugin-proposal-dynamic-import": {
 | 
					    "@babel/plugin-proposal-dynamic-import": {
 | 
				
			||||||
      "version": "7.12.1",
 | 
					      "version": "7.12.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz",
 | 
				
			||||||
@ -729,6 +747,14 @@
 | 
				
			|||||||
        "@babel/helper-plugin-utils": "^7.10.4"
 | 
					        "@babel/helper-plugin-utils": "^7.10.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@babel/plugin-syntax-decorators": {
 | 
				
			||||||
 | 
					      "version": "7.12.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@babel/helper-plugin-utils": "^7.10.4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@babel/plugin-syntax-dynamic-import": {
 | 
					    "@babel/plugin-syntax-dynamic-import": {
 | 
				
			||||||
      "version": "7.8.3",
 | 
					      "version": "7.8.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,8 @@
 | 
				
			|||||||
    "@azure/cosmos": "3.9.0",
 | 
					    "@azure/cosmos": "3.9.0",
 | 
				
			||||||
    "@azure/cosmos-language-service": "0.0.5",
 | 
					    "@azure/cosmos-language-service": "0.0.5",
 | 
				
			||||||
    "@azure/identity": "1.1.0",
 | 
					    "@azure/identity": "1.1.0",
 | 
				
			||||||
 | 
					    "@babel/plugin-proposal-class-properties": "7.12.1",
 | 
				
			||||||
 | 
					    "@babel/plugin-proposal-decorators": "7.12.12",
 | 
				
			||||||
    "@jupyterlab/services": "6.0.0-rc.2",
 | 
					    "@jupyterlab/services": "6.0.0-rc.2",
 | 
				
			||||||
    "@jupyterlab/terminal": "3.0.0-rc.2",
 | 
					    "@jupyterlab/terminal": "3.0.0-rc.2",
 | 
				
			||||||
    "@microsoft/applicationinsights-web": "2.5.9",
 | 
					    "@microsoft/applicationinsights-web": "2.5.9",
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ import * as ReactDOM from "react-dom";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface ReactAdapter {
 | 
					export interface ReactAdapter {
 | 
				
			||||||
  parameters: any;
 | 
					  parameters: any;
 | 
				
			||||||
  renderComponent: () => JSX.Element;
 | 
					  renderComponent: (() => Promise<JSX.Element>) | (() => JSX.Element);
 | 
				
			||||||
  setElement?: (elt: Element) => void;
 | 
					  setElement?: (elt: Element) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -36,12 +36,12 @@ export class Registerer {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If any of the ko observable change inside parameters, trigger a new render.
 | 
					        // If any of the ko observable change inside parameters, trigger a new render.
 | 
				
			||||||
        ko.computed(() => ko.toJSON(adapter.parameters)).subscribe(() =>
 | 
					        ko.computed(() => ko.toJSON(adapter.parameters)).subscribe(async () =>
 | 
				
			||||||
          ReactDOM.render(adapter.renderComponent(), element)
 | 
					          ReactDOM.render(await adapter.renderComponent(), element)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initial rendering at mount point
 | 
					        // Initial rendering at mount point
 | 
				
			||||||
        ReactDOM.render(adapter.renderComponent(), element);
 | 
					        Promise.resolve(adapter.renderComponent()).then(component => ReactDOM.render(component, element));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } as ko.BindingHandler;
 | 
					    } as ko.BindingHandler;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
 | 
				
			|||||||
    { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
 | 
					    { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
 | 
				
			||||||
    { key: "feature.enablettl", label: "Enable TTL", value: "true" },
 | 
					    { key: "feature.enablettl", label: "Enable TTL", value: "true" },
 | 
				
			||||||
    { key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
 | 
					    { key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
 | 
				
			||||||
    { key: "feature.selfServeTypeForTest", label: "self serve type passed on for testing", value: "sample" },
 | 
					    { key: "feature.selfServeTypeForTest", label: "Self serve type passed on for testing", value: "sample" },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "feature.enableLinkInjection",
 | 
					      key: "feature.enableLinkInjection",
 | 
				
			||||||
      label: "Enable Injecting Notebook Viewer Link into the first cell",
 | 
					      label: "Enable Injecting Notebook Viewer Link into the first cell",
 | 
				
			||||||
 | 
				
			|||||||
@ -157,14 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
        <StyledCheckboxBase
 | 
					        <StyledCheckboxBase
 | 
				
			||||||
          checked={false}
 | 
					          checked={false}
 | 
				
			||||||
          key="feature.enableLinkInjection"
 | 
					          key="feature.selfServeTypeForTest"
 | 
				
			||||||
          label="Enable Injecting Notebook Viewer Link into the first cell"
 | 
					          label="Self serve type passed on for testing"
 | 
				
			||||||
          onChange={[Function]}
 | 
					          onChange={[Function]}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <StyledCheckboxBase
 | 
					        <StyledCheckboxBase
 | 
				
			||||||
          checked={false}
 | 
					          checked={false}
 | 
				
			||||||
          key="feature.canexceedmaximumvalue"
 | 
					          key="feature.enableLinkInjection"
 | 
				
			||||||
          label="Can exceed max value"
 | 
					          label="Enable Injecting Notebook Viewer Link into the first cell"
 | 
				
			||||||
          onChange={[Function]}
 | 
					          onChange={[Function]}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </Stack>
 | 
					      </Stack>
 | 
				
			||||||
@ -172,6 +172,12 @@ exports[`Feature panel renders all flags 1`] = `
 | 
				
			|||||||
        className="checkboxRow"
 | 
					        className="checkboxRow"
 | 
				
			||||||
        horizontalAlign="space-between"
 | 
					        horizontalAlign="space-between"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
 | 
					        <StyledCheckboxBase
 | 
				
			||||||
 | 
					          checked={false}
 | 
				
			||||||
 | 
					          key="feature.canexceedmaximumvalue"
 | 
				
			||||||
 | 
					          label="Can exceed max value"
 | 
				
			||||||
 | 
					          onChange={[Function]}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
        <StyledCheckboxBase
 | 
					        <StyledCheckboxBase
 | 
				
			||||||
          checked={false}
 | 
					          checked={false}
 | 
				
			||||||
          key="feature.enablefixedcollectionwithsharedthroughput"
 | 
					          key="feature.enablefixedcollectionwithsharedthroughput"
 | 
				
			||||||
 | 
				
			|||||||
@ -1118,6 +1118,14 @@ exports[`SettingsComponent renders 1`] = `
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
                "selectedDatabaseId": [Function],
 | 
					                "selectedDatabaseId": [Function],
 | 
				
			||||||
                "selectedNode": [Function],
 | 
					                "selectedNode": [Function],
 | 
				
			||||||
 | 
					                "selfServeComponentAdapter": SelfServeComponentAdapter {
 | 
				
			||||||
 | 
					                  "container": [Circular],
 | 
				
			||||||
 | 
					                  "parameters": [Function],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
 | 
				
			||||||
 | 
					                  "parameters": [Function],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "selfServeType": [Function],
 | 
				
			||||||
                "serverId": [Function],
 | 
					                "serverId": [Function],
 | 
				
			||||||
                "settingsPane": SettingsPane {
 | 
					                "settingsPane": SettingsPane {
 | 
				
			||||||
                  "container": [Circular],
 | 
					                  "container": [Circular],
 | 
				
			||||||
@ -2393,6 +2401,14 @@ exports[`SettingsComponent renders 1`] = `
 | 
				
			|||||||
              },
 | 
					              },
 | 
				
			||||||
              "selectedDatabaseId": [Function],
 | 
					              "selectedDatabaseId": [Function],
 | 
				
			||||||
              "selectedNode": [Function],
 | 
					              "selectedNode": [Function],
 | 
				
			||||||
 | 
					              "selfServeComponentAdapter": SelfServeComponentAdapter {
 | 
				
			||||||
 | 
					                "container": [Circular],
 | 
				
			||||||
 | 
					                "parameters": [Function],
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
 | 
				
			||||||
 | 
					                "parameters": [Function],
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "selfServeType": [Function],
 | 
				
			||||||
              "serverId": [Function],
 | 
					              "serverId": [Function],
 | 
				
			||||||
              "settingsPane": SettingsPane {
 | 
					              "settingsPane": SettingsPane {
 | 
				
			||||||
                "container": [Circular],
 | 
					                "container": [Circular],
 | 
				
			||||||
@ -3681,6 +3697,14 @@ exports[`SettingsComponent renders 1`] = `
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
                "selectedDatabaseId": [Function],
 | 
					                "selectedDatabaseId": [Function],
 | 
				
			||||||
                "selectedNode": [Function],
 | 
					                "selectedNode": [Function],
 | 
				
			||||||
 | 
					                "selfServeComponentAdapter": SelfServeComponentAdapter {
 | 
				
			||||||
 | 
					                  "container": [Circular],
 | 
				
			||||||
 | 
					                  "parameters": [Function],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
 | 
				
			||||||
 | 
					                  "parameters": [Function],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "selfServeType": [Function],
 | 
				
			||||||
                "serverId": [Function],
 | 
					                "serverId": [Function],
 | 
				
			||||||
                "settingsPane": SettingsPane {
 | 
					                "settingsPane": SettingsPane {
 | 
				
			||||||
                  "container": [Circular],
 | 
					                  "container": [Circular],
 | 
				
			||||||
@ -4956,6 +4980,14 @@ exports[`SettingsComponent renders 1`] = `
 | 
				
			|||||||
              },
 | 
					              },
 | 
				
			||||||
              "selectedDatabaseId": [Function],
 | 
					              "selectedDatabaseId": [Function],
 | 
				
			||||||
              "selectedNode": [Function],
 | 
					              "selectedNode": [Function],
 | 
				
			||||||
 | 
					              "selfServeComponentAdapter": SelfServeComponentAdapter {
 | 
				
			||||||
 | 
					                "container": [Circular],
 | 
				
			||||||
 | 
					                "parameters": [Function],
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
 | 
				
			||||||
 | 
					                "parameters": [Function],
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "selfServeType": [Function],
 | 
				
			||||||
              "serverId": [Function],
 | 
					              "serverId": [Function],
 | 
				
			||||||
              "settingsPane": SettingsPane {
 | 
					              "settingsPane": SettingsPane {
 | 
				
			||||||
                "container": [Circular],
 | 
					                "container": [Circular],
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,25 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { shallow } from "enzyme";
 | 
					import { shallow } from "enzyme";
 | 
				
			||||||
import { SmartUiComponent, Descriptor } from "./SmartUiComponent";
 | 
					import { SmartUiComponent, Descriptor, UiType } from "./SmartUiComponent";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("SmartUiComponent", () => {
 | 
					describe("SmartUiComponent", () => {
 | 
				
			||||||
 | 
					  let initializeCalled = false;
 | 
				
			||||||
 | 
					  let fetchMaxCalled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const initializeMock = async () => {
 | 
				
			||||||
 | 
					    initializeCalled = true;
 | 
				
			||||||
 | 
					    return new Map();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const fetchMaxvalue = async () => {
 | 
				
			||||||
 | 
					    fetchMaxCalled = true;
 | 
				
			||||||
 | 
					    return 500;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const exampleData: Descriptor = {
 | 
					  const exampleData: Descriptor = {
 | 
				
			||||||
    onSubmit: async () => {
 | 
					    onSubmit: async () => {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: async () => {
 | 
					    initialize: initializeMock,
 | 
				
			||||||
      return undefined;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    root: {
 | 
					    root: {
 | 
				
			||||||
      id: "root",
 | 
					      id: "root",
 | 
				
			||||||
      info: {
 | 
					      info: {
 | 
				
			||||||
@ -27,11 +37,10 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
            dataFieldName: "throughput",
 | 
					            dataFieldName: "throughput",
 | 
				
			||||||
            type: "number",
 | 
					            type: "number",
 | 
				
			||||||
            min: 400,
 | 
					            min: 400,
 | 
				
			||||||
            max: 500,
 | 
					            max: fetchMaxvalue,
 | 
				
			||||||
            step: 10,
 | 
					            step: 10,
 | 
				
			||||||
            defaultValue: 400,
 | 
					            defaultValue: 400,
 | 
				
			||||||
            inputType: "spinner",
 | 
					            uiType: UiType.Spinner
 | 
				
			||||||
            onChange: undefined
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -44,8 +53,21 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
            max: 500,
 | 
					            max: 500,
 | 
				
			||||||
            step: 10,
 | 
					            step: 10,
 | 
				
			||||||
            defaultValue: 400,
 | 
					            defaultValue: 400,
 | 
				
			||||||
            inputType: "slider",
 | 
					            uiType: UiType.Slider
 | 
				
			||||||
            onChange: undefined
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "throughput3",
 | 
				
			||||||
 | 
					          input: {
 | 
				
			||||||
 | 
					            label: "Throughput (invalid)",
 | 
				
			||||||
 | 
					            dataFieldName: "throughput3",
 | 
				
			||||||
 | 
					            type: "boolean",
 | 
				
			||||||
 | 
					            min: 400,
 | 
				
			||||||
 | 
					            max: 500,
 | 
				
			||||||
 | 
					            step: 10,
 | 
				
			||||||
 | 
					            defaultValue: 400,
 | 
				
			||||||
 | 
					            uiType: UiType.Spinner,
 | 
				
			||||||
 | 
					            errorMessage: "label, truelabel and falselabel are required for boolean input 'throughput3'"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -53,8 +75,7 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
          input: {
 | 
					          input: {
 | 
				
			||||||
            label: "Container id",
 | 
					            label: "Container id",
 | 
				
			||||||
            dataFieldName: "containerId",
 | 
					            dataFieldName: "containerId",
 | 
				
			||||||
            type: "string",
 | 
					            type: "string"
 | 
				
			||||||
            onChange: undefined
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -65,8 +86,7 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
            falseLabel: "Disabled",
 | 
					            falseLabel: "Disabled",
 | 
				
			||||||
            defaultValue: true,
 | 
					            defaultValue: true,
 | 
				
			||||||
            dataFieldName: "analyticalStore",
 | 
					            dataFieldName: "analyticalStore",
 | 
				
			||||||
            type: "boolean",
 | 
					            type: "boolean"
 | 
				
			||||||
            onChange: undefined
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -80,7 +100,6 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
              { label: "Database 2", key: "db2" },
 | 
					              { label: "Database 2", key: "db2" },
 | 
				
			||||||
              { label: "Database 3", key: "db3" }
 | 
					              { label: "Database 3", key: "db3" }
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            onChange: undefined,
 | 
					 | 
				
			||||||
            defaultKey: "db2"
 | 
					            defaultKey: "db2"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -88,8 +107,17 @@ describe("SmartUiComponent", () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it("should render", () => {
 | 
					  it("should render", done => {
 | 
				
			||||||
    const wrapper = shallow(<SmartUiComponent descriptor={exampleData} />);
 | 
					    const wrapper = shallow(<SmartUiComponent descriptor={exampleData} />);
 | 
				
			||||||
 | 
					    setImmediate(() => {
 | 
				
			||||||
      expect(wrapper).toMatchSnapshot();
 | 
					      expect(wrapper).toMatchSnapshot();
 | 
				
			||||||
 | 
					      expect(initializeCalled).toBeTruthy();
 | 
				
			||||||
 | 
					      expect(fetchMaxCalled).toBeTruthy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      wrapper.setState({ isRefreshing: true });
 | 
				
			||||||
 | 
					      wrapper.update();
 | 
				
			||||||
 | 
					      expect(wrapper).toMatchSnapshot();
 | 
				
			||||||
 | 
					      done();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -22,12 +22,15 @@ import "./SmartUiComponent.less";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type InputTypeValue = "number" | "string" | "boolean" | "object";
 | 
					export type InputTypeValue = "number" | "string" | "boolean" | "object";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type NumberInputType = "spinner" | "slider";
 | 
					export enum UiType {
 | 
				
			||||||
 | 
					  Spinner = "Spinner",
 | 
				
			||||||
 | 
					  Slider = "Slider"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
 | 
					/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
 | 
				
			||||||
export type ChoiceItem = { label: string; key: string };
 | 
					export type DropdownItem = { label: string; key: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InputType = number | string | boolean | ChoiceItem | JSX.Element;
 | 
					export type InputType = number | string | boolean | DropdownItem | JSX.Element;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BaseInput {
 | 
					export interface BaseInput {
 | 
				
			||||||
  label: (() => Promise<string>) | string;
 | 
					  label: (() => Promise<string>) | string;
 | 
				
			||||||
@ -35,7 +38,7 @@ export interface BaseInput {
 | 
				
			|||||||
  type: InputTypeValue;
 | 
					  type: InputTypeValue;
 | 
				
			||||||
  onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
 | 
					  onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
 | 
				
			||||||
  placeholder?: (() => Promise<string>) | string;
 | 
					  placeholder?: (() => Promise<string>) | string;
 | 
				
			||||||
  customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element;
 | 
					  errorMessage?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -46,7 +49,7 @@ export interface NumberInput extends BaseInput {
 | 
				
			|||||||
  max: (() => Promise<number>) | number;
 | 
					  max: (() => Promise<number>) | number;
 | 
				
			||||||
  step: (() => Promise<number>) | number;
 | 
					  step: (() => Promise<number>) | number;
 | 
				
			||||||
  defaultValue?: number;
 | 
					  defaultValue?: number;
 | 
				
			||||||
  inputType: NumberInputType;
 | 
					  uiType: UiType;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BooleanInput extends BaseInput {
 | 
					export interface BooleanInput extends BaseInput {
 | 
				
			||||||
@ -59,8 +62,8 @@ export interface StringInput extends BaseInput {
 | 
				
			|||||||
  defaultValue?: string;
 | 
					  defaultValue?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ChoiceInput extends BaseInput {
 | 
					export interface DropdownInput extends BaseInput {
 | 
				
			||||||
  choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
 | 
					  choices: (() => Promise<DropdownItem[]>) | DropdownItem[];
 | 
				
			||||||
  defaultKey?: string;
 | 
					  defaultKey?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -72,7 +75,7 @@ export interface Info {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AnyInput = NumberInput | BooleanInput | StringInput | ChoiceInput;
 | 
					export type AnyInput = NumberInput | BooleanInput | StringInput | DropdownInput;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Node {
 | 
					export interface Node {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
@ -83,8 +86,9 @@ export interface Node {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface Descriptor {
 | 
					export interface Descriptor {
 | 
				
			||||||
  root: Node;
 | 
					  root: Node;
 | 
				
			||||||
  initialize: () => Promise<Map<string, InputType>>;
 | 
					  initialize?: () => Promise<Map<string, InputType>>;
 | 
				
			||||||
  onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
 | 
					  onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
 | 
				
			||||||
 | 
					  inputNames?: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/************************** Component implementation starts here ************************************* */
 | 
					/************************** Component implementation starts here ************************************* */
 | 
				
			||||||
@ -97,56 +101,30 @@ interface SmartUiComponentState {
 | 
				
			|||||||
  currentValues: Map<string, InputType>;
 | 
					  currentValues: Map<string, InputType>;
 | 
				
			||||||
  baselineValues: Map<string, InputType>;
 | 
					  baselineValues: Map<string, InputType>;
 | 
				
			||||||
  errors: Map<string, string>;
 | 
					  errors: Map<string, string>;
 | 
				
			||||||
  customInputIndex: number;
 | 
					 | 
				
			||||||
  isRefreshing: boolean;
 | 
					  isRefreshing: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
 | 
					export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
 | 
				
			||||||
  private customInputs: AnyInput[] = [];
 | 
					 | 
				
			||||||
  private shouldRenderCustomComponents = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static readonly labelStyle = {
 | 
					  private static readonly labelStyle = {
 | 
				
			||||||
    color: "#393939",
 | 
					    color: "#393939",
 | 
				
			||||||
    fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
 | 
					    fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
 | 
				
			||||||
    fontSize: 12
 | 
					    fontSize: 12
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount(): void {
 | 
				
			||||||
 | 
					    this.setDefaultValues();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(props: SmartUiComponentProps) {
 | 
					  constructor(props: SmartUiComponentProps) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.state = {
 | 
					    this.state = {
 | 
				
			||||||
      baselineValues: new Map(),
 | 
					      baselineValues: new Map(),
 | 
				
			||||||
      currentValues: new Map(),
 | 
					      currentValues: new Map(),
 | 
				
			||||||
      errors: new Map(),
 | 
					      errors: new Map(),
 | 
				
			||||||
      customInputIndex: 0,
 | 
					 | 
				
			||||||
      isRefreshing: false
 | 
					      isRefreshing: false
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.setDefaultValues();
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidUpdate = async (): Promise<void> => {
 | 
					 | 
				
			||||||
    if (!this.customInputs.length) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!this.shouldRenderCustomComponents) {
 | 
					 | 
				
			||||||
      this.shouldRenderCustomComponents = true;
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.state.customInputIndex === this.customInputs.length) {
 | 
					 | 
				
			||||||
      this.shouldRenderCustomComponents = false;
 | 
					 | 
				
			||||||
      this.setState({ customInputIndex: 0 });
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const input = this.customInputs[this.state.customInputIndex];
 | 
					 | 
				
			||||||
    const dataFieldName = input.dataFieldName;
 | 
					 | 
				
			||||||
    const element = await (input.customElement as Function)(this.state.currentValues);
 | 
					 | 
				
			||||||
    const { currentValues } = this.state;
 | 
					 | 
				
			||||||
    currentValues.set(dataFieldName, element);
 | 
					 | 
				
			||||||
    this.setState({ currentValues: currentValues, customInputIndex: this.state.customInputIndex + 1 });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setDefaultValues = async (): Promise<void> => {
 | 
					  private setDefaultValues = async (): Promise<void> => {
 | 
				
			||||||
    this.setState({ isRefreshing: true });
 | 
					    this.setState({ isRefreshing: true });
 | 
				
			||||||
    await this.setDefaults(this.props.descriptor.root);
 | 
					    await this.setDefaults(this.props.descriptor.root);
 | 
				
			||||||
@ -159,6 +137,11 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
    let { currentValues, baselineValues } = this.state;
 | 
					    let { currentValues, baselineValues } = this.state;
 | 
				
			||||||
    const initialValues = await this.props.descriptor.initialize();
 | 
					    const initialValues = await this.props.descriptor.initialize();
 | 
				
			||||||
    for (const key of initialValues.keys()) {
 | 
					    for (const key of initialValues.keys()) {
 | 
				
			||||||
 | 
					      if (this.props.descriptor.inputNames.indexOf(key) === -1) {
 | 
				
			||||||
 | 
					        console.log(this.props.descriptor.inputNames);
 | 
				
			||||||
 | 
					        this.setState({ isRefreshing: false });
 | 
				
			||||||
 | 
					        throw new Error(`${key} is not an input property of this class.`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      currentValues = currentValues.set(key, initialValues.get(key));
 | 
					      currentValues = currentValues.set(key, initialValues.get(key));
 | 
				
			||||||
      baselineValues = baselineValues.set(key, initialValues.get(key));
 | 
					      baselineValues = baselineValues.set(key, initialValues.get(key));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -182,8 +165,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
    if (currentNode.input) {
 | 
					    if (currentNode.input) {
 | 
				
			||||||
      currentNode.input = await this.getModifiedInput(currentNode.input);
 | 
					      currentNode.input = await this.getModifiedInput(currentNode.input);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const promises = currentNode.children?.map(async (child: Node) => await this.setDefaults(child));
 | 
				
			||||||
    await Promise.all(currentNode.children?.map(async (child: Node) => await this.setDefaults(child)));
 | 
					    if (promises) {
 | 
				
			||||||
 | 
					      await Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => {
 | 
					  private getModifiedInput = async (input: AnyInput): Promise<AnyInput> => {
 | 
				
			||||||
@ -195,13 +180,6 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
      input.placeholder = await (input.placeholder as Function)();
 | 
					      input.placeholder = await (input.placeholder as Function)();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input.customElement) {
 | 
					 | 
				
			||||||
      if (input.customElement instanceof Function) {
 | 
					 | 
				
			||||||
        this.customInputs.push(input);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return input;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (input.type) {
 | 
					    switch (input.type) {
 | 
				
			||||||
      case "string": {
 | 
					      case "string": {
 | 
				
			||||||
        return input as StringInput;
 | 
					        return input as StringInput;
 | 
				
			||||||
@ -230,7 +208,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
        return booleanInput;
 | 
					        return booleanInput;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      default: {
 | 
					      default: {
 | 
				
			||||||
        const enumInput = input as ChoiceInput;
 | 
					        const enumInput = input as DropdownInput;
 | 
				
			||||||
        if (enumInput.choices instanceof Function) {
 | 
					        if (enumInput.choices instanceof Function) {
 | 
				
			||||||
          enumInput.choices = await (enumInput.choices as Function)();
 | 
					          enumInput.choices = await (enumInput.choices as Function)();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -252,7 +230,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
        return (input as BooleanInput).defaultValue as boolean;
 | 
					        return (input as BooleanInput).defaultValue as boolean;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      default: {
 | 
					      default: {
 | 
				
			||||||
        return (input as ChoiceInput).defaultKey as string;
 | 
					        return (input as DropdownInput).defaultKey as string;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@ -364,7 +342,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const value = this.state.currentValues.get(dataFieldName) as number;
 | 
					    const value = this.state.currentValues.get(dataFieldName) as number;
 | 
				
			||||||
    if (input.inputType === "spinner") {
 | 
					    if (input.uiType === UiType.Spinner) {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
          <SpinButton
 | 
					          <SpinButton
 | 
				
			||||||
@ -386,7 +364,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else if (input.inputType === "slider") {
 | 
					    } else if (input.uiType === UiType.Slider) {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <Slider
 | 
					        <Slider
 | 
				
			||||||
          {...props}
 | 
					          {...props}
 | 
				
			||||||
@ -402,7 +380,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return <>Unsupported number input type {input.inputType}</>;
 | 
					      return <>Unsupported number UI type {input.uiType}</>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -440,7 +418,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderEnumInput(input: ChoiceInput): JSX.Element {
 | 
					  private renderEnumInput(input: DropdownInput): JSX.Element {
 | 
				
			||||||
    const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
 | 
					    const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Dropdown
 | 
					      <Dropdown
 | 
				
			||||||
@ -452,7 +430,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
 | 
					        onChange={(_, item: IDropdownOption) => this.onInputChange(input, item.key.toString())}
 | 
				
			||||||
        placeholder={placeholder as string}
 | 
					        placeholder={placeholder as string}
 | 
				
			||||||
        options={(choices as ChoiceItem[]).map(c => ({
 | 
					        options={(choices as DropdownItem[]).map(c => ({
 | 
				
			||||||
          key: c.key,
 | 
					          key: c.key,
 | 
				
			||||||
          text: c.label
 | 
					          text: c.label
 | 
				
			||||||
        }))}
 | 
					        }))}
 | 
				
			||||||
@ -467,19 +445,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderCustomInput(input: AnyInput): JSX.Element {
 | 
					  private renderError(input: AnyInput): JSX.Element {
 | 
				
			||||||
    if (input.customElement instanceof Function) {
 | 
					    return <MessageBar messageBarType={MessageBarType.error}>Error: {input.errorMessage}</MessageBar>;
 | 
				
			||||||
      const dataFieldName = input.dataFieldName;
 | 
					 | 
				
			||||||
      const element = this.state.currentValues.get(dataFieldName) as JSX.Element;
 | 
					 | 
				
			||||||
      return element ? element : <></>;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return input.customElement as JSX.Element;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderInput(input: AnyInput): JSX.Element {
 | 
					  private renderInput(input: AnyInput): JSX.Element {
 | 
				
			||||||
    if (input.customElement) {
 | 
					    if (input.errorMessage) {
 | 
				
			||||||
      return this.renderCustomInput(input);
 | 
					      return this.renderError(input);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    switch (input.type) {
 | 
					    switch (input.type) {
 | 
				
			||||||
      case "string":
 | 
					      case "string":
 | 
				
			||||||
@ -489,7 +461,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
      case "boolean":
 | 
					      case "boolean":
 | 
				
			||||||
        return this.renderBooleanInput(input as BooleanInput);
 | 
					        return this.renderBooleanInput(input as BooleanInput);
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        return this.renderEnumInput(input as ChoiceInput);
 | 
					        return this.renderEnumInput(input as DropdownInput);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -509,7 +481,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  render(): JSX.Element {
 | 
					  render(): JSX.Element {
 | 
				
			||||||
    const containerStackTokens: IStackTokens = { childrenGap: 20 };
 | 
					    const containerStackTokens: IStackTokens = { childrenGap: 20 };
 | 
				
			||||||
    return this.state.currentValues && this.state.currentValues.size && !this.state.isRefreshing ? (
 | 
					    return !this.state.isRefreshing ? (
 | 
				
			||||||
      <div style={{ overflowX: "auto" }}>
 | 
					      <div style={{ overflowX: "auto" }}>
 | 
				
			||||||
        <Stack tokens={containerStackTokens} styles={{ root: { width: 400, padding: 10 } }}>
 | 
					        <Stack tokens={containerStackTokens} styles={{ root: { width: 400, padding: 10 } }}>
 | 
				
			||||||
          {this.renderNode(this.props.descriptor.root)}
 | 
					          {this.renderNode(this.props.descriptor.root)}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,37 @@
 | 
				
			|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
					// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports[`SmartUiComponent should render 1`] = `
 | 
					exports[`SmartUiComponent should render 1`] = `
 | 
				
			||||||
<Fragment>
 | 
					<div
 | 
				
			||||||
 | 
					  style={
 | 
				
			||||||
 | 
					    Object {
 | 
				
			||||||
 | 
					      "overflowX": "auto",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  <Stack
 | 
				
			||||||
 | 
					    styles={
 | 
				
			||||||
 | 
					      Object {
 | 
				
			||||||
 | 
					        "root": Object {
 | 
				
			||||||
 | 
					          "padding": 10,
 | 
				
			||||||
 | 
					          "width": 400,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    tokens={
 | 
				
			||||||
 | 
					      Object {
 | 
				
			||||||
 | 
					        "childrenGap": 20,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
    <Stack
 | 
					    <Stack
 | 
				
			||||||
      className="widgetRendererContainer"
 | 
					      className="widgetRendererContainer"
 | 
				
			||||||
      tokens={
 | 
					      tokens={
 | 
				
			||||||
        Object {
 | 
					        Object {
 | 
				
			||||||
        "childrenGap": 10,
 | 
					          "childrenGap": 15,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
					      <StackItem>
 | 
				
			||||||
        <StyledMessageBarBase>
 | 
					        <StyledMessageBarBase>
 | 
				
			||||||
          Start at $24/mo per database
 | 
					          Start at $24/mo per database
 | 
				
			||||||
          <StyledLinkBase
 | 
					          <StyledLinkBase
 | 
				
			||||||
@ -19,6 +41,7 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
            More Details
 | 
					            More Details
 | 
				
			||||||
          </StyledLinkBase>
 | 
					          </StyledLinkBase>
 | 
				
			||||||
        </StyledMessageBarBase>
 | 
					        </StyledMessageBarBase>
 | 
				
			||||||
 | 
					      </StackItem>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        key="throughput"
 | 
					        key="throughput"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
@ -26,10 +49,11 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
          className="widgetRendererContainer"
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
          tokens={
 | 
					          tokens={
 | 
				
			||||||
            Object {
 | 
					            Object {
 | 
				
			||||||
            "childrenGap": 10,
 | 
					              "childrenGap": 15,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
              <CustomizedSpinButton
 | 
					              <CustomizedSpinButton
 | 
				
			||||||
                ariaLabel="Throughput (input)"
 | 
					                ariaLabel="Throughput (input)"
 | 
				
			||||||
@ -38,7 +62,6 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                    "iconName": "ChevronDownSmall",
 | 
					                    "iconName": "ChevronDownSmall",
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            defaultValue="400"
 | 
					 | 
				
			||||||
                disabled={false}
 | 
					                disabled={false}
 | 
				
			||||||
                incrementButtonIcon={
 | 
					                incrementButtonIcon={
 | 
				
			||||||
                  Object {
 | 
					                  Object {
 | 
				
			||||||
@ -65,6 +88,7 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
        </Stack>
 | 
					        </Stack>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
@ -74,13 +98,13 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
          className="widgetRendererContainer"
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
          tokens={
 | 
					          tokens={
 | 
				
			||||||
            Object {
 | 
					            Object {
 | 
				
			||||||
            "childrenGap": 10,
 | 
					              "childrenGap": 15,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
            <StyledSliderBase
 | 
					            <StyledSliderBase
 | 
				
			||||||
              ariaLabel="Throughput (Slider)"
 | 
					              ariaLabel="Throughput (Slider)"
 | 
				
			||||||
          defaultValue={400}
 | 
					 | 
				
			||||||
              label="Throughput (Slider)"
 | 
					              label="Throughput (Slider)"
 | 
				
			||||||
              max={500}
 | 
					              max={500}
 | 
				
			||||||
              min={400}
 | 
					              min={400}
 | 
				
			||||||
@ -102,6 +126,28 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
 | 
					        </Stack>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        key="throughput3"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Stack
 | 
				
			||||||
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
 | 
					          tokens={
 | 
				
			||||||
 | 
					            Object {
 | 
				
			||||||
 | 
					              "childrenGap": 15,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
 | 
					            <StyledMessageBarBase
 | 
				
			||||||
 | 
					              messageBarType={1}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              Error: 
 | 
				
			||||||
 | 
					              label, truelabel and falselabel are required for boolean input 'throughput3'
 | 
				
			||||||
 | 
					            </StyledMessageBarBase>
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
        </Stack>
 | 
					        </Stack>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
@ -111,10 +157,11 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
          className="widgetRendererContainer"
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
          tokens={
 | 
					          tokens={
 | 
				
			||||||
            Object {
 | 
					            Object {
 | 
				
			||||||
            "childrenGap": 10,
 | 
					              "childrenGap": 15,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className="stringInputContainer"
 | 
					              className="stringInputContainer"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@ -141,6 +188,7 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                />
 | 
					                />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
        </Stack>
 | 
					        </Stack>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
@ -150,10 +198,11 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
          className="widgetRendererContainer"
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
          tokens={
 | 
					          tokens={
 | 
				
			||||||
            Object {
 | 
					            Object {
 | 
				
			||||||
            "childrenGap": 10,
 | 
					              "childrenGap": 15,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
              <div
 | 
					              <div
 | 
				
			||||||
                className="inputLabelContainer"
 | 
					                className="inputLabelContainer"
 | 
				
			||||||
@ -184,6 +233,7 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                selectedKey="true"
 | 
					                selectedKey="true"
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
        </Stack>
 | 
					        </Stack>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
@ -193,10 +243,11 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
          className="widgetRendererContainer"
 | 
					          className="widgetRendererContainer"
 | 
				
			||||||
          tokens={
 | 
					          tokens={
 | 
				
			||||||
            Object {
 | 
					            Object {
 | 
				
			||||||
            "childrenGap": 10,
 | 
					              "childrenGap": 15,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <StackItem>
 | 
				
			||||||
            <StyledWithResponsiveMode
 | 
					            <StyledWithResponsiveMode
 | 
				
			||||||
              label="Database"
 | 
					              label="Database"
 | 
				
			||||||
              onChange={[Function]}
 | 
					              onChange={[Function]}
 | 
				
			||||||
@ -204,15 +255,15 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                Array [
 | 
					                Array [
 | 
				
			||||||
                  Object {
 | 
					                  Object {
 | 
				
			||||||
                    "key": "db1",
 | 
					                    "key": "db1",
 | 
				
			||||||
                "text": "database1",
 | 
					                    "text": "Database 1",
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  Object {
 | 
					                  Object {
 | 
				
			||||||
                    "key": "db2",
 | 
					                    "key": "db2",
 | 
				
			||||||
                "text": "database2",
 | 
					                    "text": "Database 2",
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  Object {
 | 
					                  Object {
 | 
				
			||||||
                    "key": "db3",
 | 
					                    "key": "db3",
 | 
				
			||||||
                "text": "database3",
 | 
					                    "text": "Database 3",
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
@ -233,8 +284,57 @@ exports[`SmartUiComponent should render 1`] = `
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					          </StackItem>
 | 
				
			||||||
        </Stack>
 | 
					        </Stack>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </Stack>
 | 
					    </Stack>
 | 
				
			||||||
</Fragment>
 | 
					    <Stack
 | 
				
			||||||
 | 
					      horizontal={true}
 | 
				
			||||||
 | 
					      tokens={
 | 
				
			||||||
 | 
					        Object {
 | 
				
			||||||
 | 
					          "childrenGap": 10,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <CustomizedPrimaryButton
 | 
				
			||||||
 | 
					        onClick={[Function]}
 | 
				
			||||||
 | 
					        styles={
 | 
				
			||||||
 | 
					          Object {
 | 
				
			||||||
 | 
					            "root": Object {
 | 
				
			||||||
 | 
					              "width": 100,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        text="submit"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <CustomizedPrimaryButton
 | 
				
			||||||
 | 
					        onClick={[Function]}
 | 
				
			||||||
 | 
					        styles={
 | 
				
			||||||
 | 
					          Object {
 | 
				
			||||||
 | 
					            "root": Object {
 | 
				
			||||||
 | 
					              "width": 100,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        text="discard"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </Stack>
 | 
				
			||||||
 | 
					  </Stack>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`SmartUiComponent should render 2`] = `
 | 
				
			||||||
 | 
					<StyledSpinnerBase
 | 
				
			||||||
 | 
					  size={3}
 | 
				
			||||||
 | 
					  styles={
 | 
				
			||||||
 | 
					    Object {
 | 
				
			||||||
 | 
					      "root": Object {
 | 
				
			||||||
 | 
					        "height": "100%",
 | 
				
			||||||
 | 
					        "justifyContent": "center",
 | 
				
			||||||
 | 
					        "textAlign": "center",
 | 
				
			||||||
 | 
					        "width": "100%",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					/>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
				
			|||||||
@ -1857,8 +1857,6 @@ export default class Explorer {
 | 
				
			|||||||
      this.selfServeType(inputs.selfServeType);
 | 
					      this.selfServeType(inputs.selfServeType);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.selfServeType(SelfServeTypes.none);
 | 
					      this.selfServeType(SelfServeTypes.none);
 | 
				
			||||||
      this._setLoadingStatusText("Connecting...", "Welcome to Azure Cosmos DB");
 | 
					 | 
				
			||||||
      this._setConnectingImage();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -2994,11 +2992,6 @@ export default class Explorer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _setConnectingImage() {
 | 
					 | 
				
			||||||
    const connectingImage = document.getElementById("explorerConnectingImage");
 | 
					 | 
				
			||||||
    connectingImage.innerHTML = '<img src="../images/HdeConnectCosmosDB.svg" >';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _openSetupNotebooksPaneForQuickstart(): void {
 | 
					  private _openSetupNotebooksPaneForQuickstart(): void {
 | 
				
			||||||
    const title = "Enable Notebooks (Preview)";
 | 
					    const title = "Enable Notebooks (Preview)";
 | 
				
			||||||
    const description =
 | 
					    const description =
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								src/Main.tsx
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								src/Main.tsx
									
									
									
									
									
								
							@ -78,6 +78,7 @@ import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
 | 
				
			|||||||
import refreshImg from "../images/refresh-cosmos.svg";
 | 
					import refreshImg from "../images/refresh-cosmos.svg";
 | 
				
			||||||
import arrowLeftImg from "../images/imgarrowlefticon.svg";
 | 
					import arrowLeftImg from "../images/imgarrowlefticon.svg";
 | 
				
			||||||
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
 | 
					import { KOCommentEnd, KOCommentIfStart } from "./koComment";
 | 
				
			||||||
 | 
					import { Spinner, SpinnerSize } from "office-ui-fabric-react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Encapsulate and reuse all global variables as environment variables
 | 
					// TODO: Encapsulate and reuse all global variables as environment variables
 | 
				
			||||||
window.authType = AuthType.AAD;
 | 
					window.authType = AuthType.AAD;
 | 
				
			||||||
@ -131,9 +132,14 @@ const App: React.FunctionComponent = () => {
 | 
				
			|||||||
        className="flexContainer"
 | 
					        className="flexContainer"
 | 
				
			||||||
        data-bind="visible: selfServeType() && selfServeType() !== 'none', react: selfServeComponentAdapter"
 | 
					        data-bind="visible: selfServeType() && selfServeType() !== 'none', react: selfServeComponentAdapter"
 | 
				
			||||||
      ></div>
 | 
					      ></div>
 | 
				
			||||||
      <div id="divExplorer" className="flexContainer hideOverflows" style={{ display: "none" }}>
 | 
					      <div
 | 
				
			||||||
 | 
					        id="divExplorer"
 | 
				
			||||||
 | 
					        data-bind="if: selfServeType() === 'none'"
 | 
				
			||||||
 | 
					        className="flexContainer hideOverflows"
 | 
				
			||||||
 | 
					        style={{ display: "none" }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
        {/* Main Command Bar - Start */}
 | 
					        {/* Main Command Bar - Start */}
 | 
				
			||||||
        <div data-bind="visible: selfServeType() === 'none', react: commandBarComponentAdapter" />
 | 
					        <div data-bind="react: commandBarComponentAdapter" />
 | 
				
			||||||
        {/* Main Command Bar - End */}
 | 
					        {/* Main Command Bar - End */}
 | 
				
			||||||
        {/* Share url flyout - Start */}
 | 
					        {/* Share url flyout - Start */}
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
@ -201,7 +207,7 @@ const App: React.FunctionComponent = () => {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        {/* Share url flyout - End */}
 | 
					        {/* Share url flyout - End */}
 | 
				
			||||||
        {/* Collections Tree and Tabs - Begin */}
 | 
					        {/* Collections Tree and Tabs - Begin */}
 | 
				
			||||||
        <div className="resourceTreeAndTabs" data-bind="visible: selfServeType() === 'none'">
 | 
					        <div className="resourceTreeAndTabs">
 | 
				
			||||||
          {/* Collections Tree - Start */}
 | 
					          {/* Collections Tree - Start */}
 | 
				
			||||||
          <div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
 | 
					          <div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
 | 
				
			||||||
            <div className="collectionsTreeWithSplitter">
 | 
					            <div className="collectionsTreeWithSplitter">
 | 
				
			||||||
@ -308,10 +314,7 @@ const App: React.FunctionComponent = () => {
 | 
				
			|||||||
            data-bind="visible: !isRefreshingExplorer() && tabsManager.openedTabs().length === 0"
 | 
					            data-bind="visible: !isRefreshingExplorer() && tabsManager.openedTabs().length === 0"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <form className="connectExplorerFormContainer">
 | 
					            <form className="connectExplorerFormContainer">
 | 
				
			||||||
              <div
 | 
					              <div className="connectExplorer" data-bind="react: splashScreenAdapter" />
 | 
				
			||||||
                className="connectExplorer"
 | 
					 | 
				
			||||||
                data-bind="visible: selfServeType() === 'none', react: splashScreenAdapter"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </form>
 | 
					            </form>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
@ -325,7 +328,7 @@ const App: React.FunctionComponent = () => {
 | 
				
			|||||||
          role="contentinfo"
 | 
					          role="contentinfo"
 | 
				
			||||||
          aria-label="Notification console"
 | 
					          aria-label="Notification console"
 | 
				
			||||||
          id="explorerNotificationConsole"
 | 
					          id="explorerNotificationConsole"
 | 
				
			||||||
          data-bind="react: notificationConsoleComponentAdapter, visible: selfServeType() === 'none'"
 | 
					          data-bind="react: notificationConsoleComponentAdapter"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {/* Explorer Connection - Encryption Token / AAD - Start */}
 | 
					      {/* Explorer Connection - Encryption Token / AAD - Start */}
 | 
				
			||||||
@ -379,25 +382,20 @@ const App: React.FunctionComponent = () => {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {/* Explorer Connection - Encryption Token / AAD - End */}
 | 
					      {/* Explorer Connection - Encryption Token / AAD - End */}
 | 
				
			||||||
      {/* Global loader - Start */}
 | 
					      {/* Global loader - Start */}
 | 
				
			||||||
 | 
					      {window.dataExplorer && <Spinner size={SpinnerSize.large} />}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="splashLoaderContainer" data-bind="visible: isRefreshingExplorer">
 | 
					      <div className="splashLoaderContainer" data-bind="visible: isRefreshingExplorer">
 | 
				
			||||||
        <div className="splashLoaderContentContainer">
 | 
					        <div className="splashLoaderContentContainer">
 | 
				
			||||||
          <div data-bind="visible: selfServeType() === undefined, react: selfServeLoadingComponentAdapter"></div>
 | 
					          <div data-bind="visible: selfServeType() === undefined, react: selfServeLoadingComponentAdapter"></div>
 | 
				
			||||||
          <p
 | 
					          <div data-bind="if: selfServeType() === 'none'" style={{ display: "none" }}>
 | 
				
			||||||
            className="connectExplorerContent"
 | 
					            <p className="connectExplorerContent">
 | 
				
			||||||
            id="explorerConnectingImage"
 | 
					              <img src={hdeConnectImage} alt="Azure Cosmos DB" />
 | 
				
			||||||
            data-bind="visible: selfServeType() === 'none'"
 | 
					            </p>
 | 
				
			||||||
          ></p>
 | 
					            <p className="splashLoaderTitle">Welcome to Azure Cosmos DB</p>
 | 
				
			||||||
          <p
 | 
					            <p className="splashLoaderText" role="alert">
 | 
				
			||||||
            className="splashLoaderTitle"
 | 
					              Connecting...
 | 
				
			||||||
            id="explorerLoadingStatusTitle"
 | 
					            </p>
 | 
				
			||||||
            data-bind="visible: selfServeType() === 'none'"
 | 
					          </div>
 | 
				
			||||||
          ></p>
 | 
					 | 
				
			||||||
          <p
 | 
					 | 
				
			||||||
            className="splashLoaderText"
 | 
					 | 
				
			||||||
            id="explorerLoadingStatusText"
 | 
					 | 
				
			||||||
            role="alert"
 | 
					 | 
				
			||||||
            data-bind="visible: selfServeType() === 'none'"
 | 
					 | 
				
			||||||
          ></p>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {/* Global loader - End */}
 | 
					      {/* Global loader - End */}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/SelfServe/ClassDecorators.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/SelfServe/ClassDecorators.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { Info } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
 | 
					import { addPropertyToMap, buildSmartUiDescriptor } from "./SelfServeUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const IsDisplayable = (): ClassDecorator => {
 | 
				
			||||||
 | 
					  return (target: Function) => {
 | 
				
			||||||
 | 
					    buildSmartUiDescriptor(target.name, target.prototype);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ClassInfo = (info: (() => Promise<Info>) | Info): ClassDecorator => {
 | 
				
			||||||
 | 
					  return (target: Function) => {
 | 
				
			||||||
 | 
					    addPropertyToMap(target.prototype, "root", target.name, "info", info);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,26 +0,0 @@
 | 
				
			|||||||
import { Info, InputType } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					 | 
				
			||||||
import { addPropertyToMap, toSmartUiDescriptor } from "./SelfServeUtils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const SmartUi = (): ClassDecorator => {
 | 
					 | 
				
			||||||
  return (target: Function) => {
 | 
					 | 
				
			||||||
    toSmartUiDescriptor(target.name, target);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ClassInfo = (info: (() => Promise<Info>) | Info): ClassDecorator => {
 | 
					 | 
				
			||||||
  return (target: Function) => {
 | 
					 | 
				
			||||||
    addPropertyToMap(target, "root", target.name, "info", info);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const OnSubmit = (onSubmit: (currentValues: Map<string, InputType>) => Promise<void>): ClassDecorator => {
 | 
					 | 
				
			||||||
  return (target: Function) => {
 | 
					 | 
				
			||||||
    addPropertyToMap(target, "root", target.name, "onSubmit", onSubmit);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Initialize = (initialize: () => Promise<Map<string, InputType>>): ClassDecorator => {
 | 
					 | 
				
			||||||
  return (target: Function) => {
 | 
					 | 
				
			||||||
    addPropertyToMap(target, "root", target.name, "initialize", initialize);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,30 +0,0 @@
 | 
				
			|||||||
import React from "react";
 | 
					 | 
				
			||||||
import { HoverCard, HoverCardType, Stack, Text } from "office-ui-fabric-react";
 | 
					 | 
				
			||||||
import { InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface TextComponentProps {
 | 
					 | 
				
			||||||
  text: string;
 | 
					 | 
				
			||||||
  currentValues: Map<string, InputType>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class TextComponent extends React.Component<TextComponentProps> {
 | 
					 | 
				
			||||||
  private onHover = (): JSX.Element => {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <Stack tokens={{ childrenGap: 5, padding: 15 }}>
 | 
					 | 
				
			||||||
        <Text>Choice: {this.props.currentValues.get("choiceInput")?.toString()}</Text>
 | 
					 | 
				
			||||||
        <Text>Boolean: {this.props.currentValues.get("booleanInput")?.toString()}</Text>
 | 
					 | 
				
			||||||
        <Text>String: {this.props.currentValues.get("stringInput")?.toString()}</Text>
 | 
					 | 
				
			||||||
        <Text>Slider: {this.props.currentValues.get("numberSliderInput")?.toString()}</Text>
 | 
					 | 
				
			||||||
        <Text>Spinner: {this.props.currentValues.get("numberSpinnerInput")?.toString()}</Text>
 | 
					 | 
				
			||||||
      </Stack>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public render(): JSX.Element {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <HoverCard plainCardProps={{ onRenderPlainCard: this.onHover }} instantOpenOnClick type={HoverCardType.plain}>
 | 
					 | 
				
			||||||
        <Text styles={{ root: { fontWeight: 600 } }}>{this.props.text}</Text>
 | 
					 | 
				
			||||||
      </HoverCard>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,189 +0,0 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
  Label,
 | 
					 | 
				
			||||||
  ParentOf,
 | 
					 | 
				
			||||||
  PropertyInfo,
 | 
					 | 
				
			||||||
  OnChange,
 | 
					 | 
				
			||||||
  Placeholder,
 | 
					 | 
				
			||||||
  CustomElement,
 | 
					 | 
				
			||||||
  ChoiceInput,
 | 
					 | 
				
			||||||
  BooleanInput,
 | 
					 | 
				
			||||||
  NumberInput
 | 
					 | 
				
			||||||
} from "../PropertyDescriptors";
 | 
					 | 
				
			||||||
import { SmartUi, ClassInfo, OnSubmit, Initialize } from "../ClassDescriptors";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  initializeSelfServeExample,
 | 
					 | 
				
			||||||
  choiceInfo,
 | 
					 | 
				
			||||||
  choiceOptions,
 | 
					 | 
				
			||||||
  onSliderChange,
 | 
					 | 
				
			||||||
  onSubmit,
 | 
					 | 
				
			||||||
  renderText,
 | 
					 | 
				
			||||||
  selfServeExampleInfo,
 | 
					 | 
				
			||||||
  descriptionElement,
 | 
					 | 
				
			||||||
  initializeNumberMaxValue
 | 
					 | 
				
			||||||
} from "./ExampleApis";
 | 
					 | 
				
			||||||
import { SelfServeBase } from "../SelfServeUtils";
 | 
					 | 
				
			||||||
import { ChoiceItem } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
This is an example self serve class that auto generates UI components for your feature.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Each self serve class
 | 
					 | 
				
			||||||
  - Needs to extends the SelfServeBase class.
 | 
					 | 
				
			||||||
  - Needs to have the @SmartUi() descriptor to tell the compiler that UI needs to be generated from this class.
 | 
					 | 
				
			||||||
  - Needs to have an @OnSubmit() descriptor, a callback for when the submit button is clicked.
 | 
					 | 
				
			||||||
  - Needs to have an @Initialize() descriptor, to set default values for the inputs.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You can test this self serve UI by using the featureflag '?feature.selfServeTypeForTest=example'
 | 
					 | 
				
			||||||
and plumb in similar feature flags for your own self serve class.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The default values and functions used for this class can be found in ExampleApis.tsx
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
@SmartUi()
 | 
					 | 
				
			||||||
  - role: Generated the JSON required to convert this class into the required UI. This is done during compile time.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@SmartUi()
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
@OnSubmit() 
 | 
					 | 
				
			||||||
  - input: (currentValues: Map<string, InputType>) => Promise<void>
 | 
					 | 
				
			||||||
  - role: Callback that is triggerred when the submit button is clicked. You should perform your rest API 
 | 
					 | 
				
			||||||
          calls here using the data from the different inputs passed as a Map to this callback function.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          In this example, the onSubmit callback simply sets the value for keys corresponding to the field name
 | 
					 | 
				
			||||||
          in the SessionStorage.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@OnSubmit(onSubmit)
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
@ClassInfo()
 | 
					 | 
				
			||||||
  - input: Info | () => Promise<Info>
 | 
					 | 
				
			||||||
  - role: Display an Info bar as the first element of the UI.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@ClassInfo(selfServeExampleInfo)
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
@Initialize() 
 | 
					 | 
				
			||||||
  - input: () => Promise<Map<string, InputType>>
 | 
					 | 
				
			||||||
  - role: Set default values for the properties of this class.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          The static properties of this class (namely choiceInput, booleanInput, stringInput, numberSliderInput, numberSpinnerInput)
 | 
					 | 
				
			||||||
          will each correspond to an UI element. Their values can be of 'InputType'. Their defaults can be set by setting 
 | 
					 | 
				
			||||||
          values in a Map corresponding to the field's name. 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          Typically, you can make rest calls in the async function passed to @Initialize() to fetch the initial values for 
 | 
					 | 
				
			||||||
          these fields. This is called after the onSubmit callback, to reinitialize the defaults.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          In this example, the initializeSelfServeExample function simply reads the SessionStorage to fetch the default values
 | 
					 | 
				
			||||||
          for these fields. These are then set when the changes are submitted.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@Initialize(initializeSelfServeExample)
 | 
					 | 
				
			||||||
export class SelfServeExample extends SelfServeBase {
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @CustomElement()
 | 
					 | 
				
			||||||
    - input: JSX.Element | (currentValues: Map<string, InputType> => Promise<JSX.Element>)
 | 
					 | 
				
			||||||
    - role: Display a custom element by either passing the element itself, or by passing a function that takes the current values
 | 
					 | 
				
			||||||
            and renders a Component / JSX.Element.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            In this example, we first use a static JSX.Element to show a description text. We also declare a CustomComponent, that 
 | 
					 | 
				
			||||||
            takes a Map of propertyName -> value, as input. It uses this to display a Hoverable Card which shows a snapshot of 
 | 
					 | 
				
			||||||
            the current values.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @CustomElement(descriptionElement)
 | 
					 | 
				
			||||||
  static description: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @ParentOf()
 | 
					 | 
				
			||||||
    - input: string[]
 | 
					 | 
				
			||||||
    - role: Determines which UI elements are the children of which UI element. An array containing the names of the child properties 
 | 
					 | 
				
			||||||
            is passsed. You need to make sure these children are declared in this Class as proeprties.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @ParentOf(["choiceInput", "booleanInput", "stringInput", "numberSliderInput", "numberSpinnerInput"])
 | 
					 | 
				
			||||||
  @CustomElement(renderText("Hover to see current values..."))
 | 
					 | 
				
			||||||
  static currentValues: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @Label()
 | 
					 | 
				
			||||||
    - input: string | () => Promise<string>
 | 
					 | 
				
			||||||
    - role: Adds a label for the UI element. This is ignored for a custom element but is required for all other properties.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @Label("Choice")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @PropertyInfo()
 | 
					 | 
				
			||||||
    - input: Info | () => Promise<Info>
 | 
					 | 
				
			||||||
    - role: Display an Info bar above the UI element for this property.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @PropertyInfo(choiceInfo)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @ChoiceInput()
 | 
					 | 
				
			||||||
    - input: ChoiceItem[] | () => Promise<ChoiceItem[]>
 | 
					 | 
				
			||||||
    - role: Display a dropdown with choices.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @ChoiceInput(choiceOptions)
 | 
					 | 
				
			||||||
  static choiceInput: ChoiceItem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Label("Boolean")
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @BooleanInput()
 | 
					 | 
				
			||||||
    - input: 
 | 
					 | 
				
			||||||
        trueLabel : string | () => Promise<string>
 | 
					 | 
				
			||||||
        falseLabel : string | () => Promise<string>
 | 
					 | 
				
			||||||
    - role: Add a boolean input eith radio buttons for true and false values.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @BooleanInput({
 | 
					 | 
				
			||||||
    trueLabel: "allowed",
 | 
					 | 
				
			||||||
    falseLabel: "not allowed"
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  static booleanInput: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Label("String")
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @PlaceHolder()
 | 
					 | 
				
			||||||
    - input: string | () => Promise<string>
 | 
					 | 
				
			||||||
    - role: Adds a placeholder for the string input
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @Placeholder("instance name")
 | 
					 | 
				
			||||||
  static stringInput: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Label("Slider")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @OnChange()
 | 
					 | 
				
			||||||
    - input: (currentValues: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
 | 
					 | 
				
			||||||
    - role: Takes a Map of current values and the newValue for this property as inputs. This is called when a property 
 | 
					 | 
				
			||||||
            changes its value in the UI. This can be used to change other input values based on some other input.
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            The new Map of propertyName -> value is returned.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            In this example, the onSliderChange function sets the spinner input to the same value as the slider input
 | 
					 | 
				
			||||||
            when the slider in moved in the UI.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @OnChange(onSliderChange)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  @NumberInput()
 | 
					 | 
				
			||||||
    - input: 
 | 
					 | 
				
			||||||
        min : number | () => Promise<number>
 | 
					 | 
				
			||||||
        max : number | () => Promise<number>
 | 
					 | 
				
			||||||
        step : number | () => Promise<number>
 | 
					 | 
				
			||||||
        numberInputType : NumberInputType
 | 
					 | 
				
			||||||
    - role: Display a numeric input as slider or a spinner. The Min, Max and step to increase by need to be provided as well.
 | 
					 | 
				
			||||||
            In this example, the Max value is fetched via an async function. This is resolved every time the UI is reloaded.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  @NumberInput({
 | 
					 | 
				
			||||||
    min: 1,
 | 
					 | 
				
			||||||
    max: initializeNumberMaxValue,
 | 
					 | 
				
			||||||
    step: 1,
 | 
					 | 
				
			||||||
    numberInputType: "slider"
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  static numberSliderInput: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Label("Spinner")
 | 
					 | 
				
			||||||
  @NumberInput({
 | 
					 | 
				
			||||||
    min: 1,
 | 
					 | 
				
			||||||
    max: initializeNumberMaxValue,
 | 
					 | 
				
			||||||
    step: 1,
 | 
					 | 
				
			||||||
    numberInputType: "spinner"
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  static numberSpinnerInput: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,70 +0,0 @@
 | 
				
			|||||||
import React from "react";
 | 
					 | 
				
			||||||
import { ChoiceItem, Info, InputType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					 | 
				
			||||||
import { TextComponent } from "./CustomComponent";
 | 
					 | 
				
			||||||
import { SessionStorageUtility } from "../../Shared/StorageUtility";
 | 
					 | 
				
			||||||
import { Text } from "office-ui-fabric-react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export enum Choices {
 | 
					 | 
				
			||||||
  Choice1 = "Choice1",
 | 
					 | 
				
			||||||
  Choice2 = "Choice2",
 | 
					 | 
				
			||||||
  Choice3 = "Choice3"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const choiceOptions: ChoiceItem[] = [
 | 
					 | 
				
			||||||
  { label: "Choice 1", key: Choices.Choice1 },
 | 
					 | 
				
			||||||
  { label: "Choice 2", key: Choices.Choice2 },
 | 
					 | 
				
			||||||
  { label: "Choice 3", key: Choices.Choice3 }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const selfServeExampleInfo: Info = {
 | 
					 | 
				
			||||||
  message: "This is a self serve class"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const choiceInfo: Info = {
 | 
					 | 
				
			||||||
  message: "More choices can be added in the future."
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const onSliderChange = (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
 | 
					 | 
				
			||||||
  currentState.set("numberSliderInput", newValue);
 | 
					 | 
				
			||||||
  currentState.set("numberSpinnerInput", newValue);
 | 
					 | 
				
			||||||
  return currentState;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
 | 
					 | 
				
			||||||
  SessionStorageUtility.setEntry("choiceInput", currentValues.get("choiceInput")?.toString());
 | 
					 | 
				
			||||||
  SessionStorageUtility.setEntry("booleanInput", currentValues.get("booleanInput")?.toString());
 | 
					 | 
				
			||||||
  SessionStorageUtility.setEntry("stringInput", currentValues.get("stringInput")?.toString());
 | 
					 | 
				
			||||||
  SessionStorageUtility.setEntry("numberSliderInput", currentValues.get("numberSliderInput")?.toString());
 | 
					 | 
				
			||||||
  SessionStorageUtility.setEntry("numberSpinnerInput", currentValues.get("numberSpinnerInput")?.toString());
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const delay = (ms: number): Promise<void> => {
 | 
					 | 
				
			||||||
  return new Promise(resolve => setTimeout(resolve, ms));
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const initializeSelfServeExample = async (): Promise<Map<string, InputType>> => {
 | 
					 | 
				
			||||||
  await delay(1000);
 | 
					 | 
				
			||||||
  const defaults = new Map<string, InputType>();
 | 
					 | 
				
			||||||
  defaults.set("choiceInput", SessionStorageUtility.getEntry("choiceInput"));
 | 
					 | 
				
			||||||
  defaults.set("booleanInput", SessionStorageUtility.getEntry("booleanInput") === "true");
 | 
					 | 
				
			||||||
  defaults.set("stringInput", SessionStorageUtility.getEntry("stringInput"));
 | 
					 | 
				
			||||||
  const numberSliderInput = parseInt(SessionStorageUtility.getEntry("numberSliderInput"));
 | 
					 | 
				
			||||||
  defaults.set("numberSliderInput", !isNaN(numberSliderInput) ? numberSliderInput : 1);
 | 
					 | 
				
			||||||
  const numberSpinnerInput = parseInt(SessionStorageUtility.getEntry("numberSpinnerInput"));
 | 
					 | 
				
			||||||
  defaults.set("numberSpinnerInput", !isNaN(numberSpinnerInput) ? numberSpinnerInput : 1);
 | 
					 | 
				
			||||||
  return defaults;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const initializeNumberMaxValue = async (): Promise<number> => {
 | 
					 | 
				
			||||||
  await delay(2000);
 | 
					 | 
				
			||||||
  return 5;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const descriptionElement = <Text>This is an example of Self serve class.</Text>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const renderText = (text: string): ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) => {
 | 
					 | 
				
			||||||
  const elementPromiseFunction = async (currentValues: Map<string, InputType>): Promise<JSX.Element> => {
 | 
					 | 
				
			||||||
    return <TextComponent text={text} currentValues={currentValues} />;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  return elementPromiseFunction;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										175
									
								
								src/SelfServe/Example/SelfServeExample.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/SelfServe/Example/SelfServeExample.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,175 @@
 | 
				
			|||||||
 | 
					import { PropertyInfo, OnChange, Values } from "../PropertyDecorators";
 | 
				
			||||||
 | 
					import { ClassInfo, IsDisplayable } from "../ClassDecorators";
 | 
				
			||||||
 | 
					import { SelfServeBaseClass } from "../SelfServeUtils";
 | 
				
			||||||
 | 
					import { DropdownItem, Info, InputType, UiType } from "../../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
 | 
					import { SessionStorageUtility } from "../../Shared/StorageUtility";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum Regions {
 | 
				
			||||||
 | 
					  NorthCentralUS = "NCUS",
 | 
				
			||||||
 | 
					  WestUS = "WUS",
 | 
				
			||||||
 | 
					  EastUS2 = "EUS2"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const regionDropdownItems: DropdownItem[] = [
 | 
				
			||||||
 | 
					  { label: "North Central US", key: Regions.NorthCentralUS },
 | 
				
			||||||
 | 
					  { label: "West US", key: Regions.WestUS },
 | 
				
			||||||
 | 
					  { label: "East US 2", key: Regions.EastUS2 }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const selfServeExampleInfo: Info = {
 | 
				
			||||||
 | 
					  message: "This is a self serve class"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const regionDropdownInfo: Info = {
 | 
				
			||||||
 | 
					  message: "More regions can be added in the future."
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const delay = (ms: number): Promise<void> => {
 | 
				
			||||||
 | 
					  console.log("delay called");
 | 
				
			||||||
 | 
					  return new Promise(resolve => setTimeout(resolve, ms));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const onDbThroughputChange = (currentState: Map<string, InputType>, newValue: InputType): Map<string, InputType> => {
 | 
				
			||||||
 | 
					  currentState.set("dbThroughput", newValue);
 | 
				
			||||||
 | 
					  currentState.set("collectionThroughput", newValue);
 | 
				
			||||||
 | 
					  return currentState;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initializeMaxThroughput = async (): Promise<number> => {
 | 
				
			||||||
 | 
					  await delay(2000);
 | 
				
			||||||
 | 
					  return 10000;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					  This is an example self serve class that auto generates UI components for your feature.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Each self serve class
 | 
				
			||||||
 | 
					    - Needs to extends the SelfServeBase class.
 | 
				
			||||||
 | 
					    - Needs to have the @IsDisplayable() decorator to tell the compiler that UI needs to be generated from this class.
 | 
				
			||||||
 | 
					    - Needs to define an onSubmit() function, a callback for when the submit button is clicked.
 | 
				
			||||||
 | 
					    - Needs to define an initialize() function, to set default values for the inputs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  You can test this self serve UI by using the featureflag '?feature.selfServeTypeForTest=example'
 | 
				
			||||||
 | 
					  and plumb in similar feature flags for your own self serve class.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					  @IsDisplayable()
 | 
				
			||||||
 | 
					    - role: Generated the JSON required to convert this class into the required UI. This is done during compile time.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					@IsDisplayable()
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					  @ClassInfo()
 | 
				
			||||||
 | 
					    - optional
 | 
				
			||||||
 | 
					    - input: Info | () => Promise<Info>
 | 
				
			||||||
 | 
					    - role: Display an Info bar as the first element of the UI.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					@ClassInfo(selfServeExampleInfo)
 | 
				
			||||||
 | 
					export default class SelfServeExample extends SelfServeBaseClass {
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  onSubmit()
 | 
				
			||||||
 | 
					    - input: (currentValues: Map<string, InputType>) => Promise<void>
 | 
				
			||||||
 | 
					    - role: Callback that is triggerred when the submit button is clicked. You should perform your rest API
 | 
				
			||||||
 | 
					            calls here using the data from the different inputs passed as a Map to this callback function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            In this example, the onSubmit callback simply sets the value for keys corresponding to the field name
 | 
				
			||||||
 | 
					            in the SessionStorage.
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  public onSubmit = async (currentValues: Map<string, InputType>): Promise<void> => {
 | 
				
			||||||
 | 
					    await delay(1000);
 | 
				
			||||||
 | 
					    SessionStorageUtility.setEntry("regions", currentValues.get("regions")?.toString());
 | 
				
			||||||
 | 
					    SessionStorageUtility.setEntry("enableLogging", currentValues.get("enableLogging")?.toString());
 | 
				
			||||||
 | 
					    SessionStorageUtility.setEntry("accountName", currentValues.get("accountName")?.toString());
 | 
				
			||||||
 | 
					    SessionStorageUtility.setEntry("dbThroughput", currentValues.get("dbThroughput")?.toString());
 | 
				
			||||||
 | 
					    SessionStorageUtility.setEntry("collectionThroughput", currentValues.get("collectionThroughput")?.toString());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  initialize()
 | 
				
			||||||
 | 
					    - input: () => Promise<Map<string, InputType>>
 | 
				
			||||||
 | 
					    - role: Set default values for the properties of this class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            The properties of this class (namely regions, enableLogging, accountName, dbThroughput, collectionThroughput),
 | 
				
			||||||
 | 
					            having the @Values decorator, will each correspond to an UI element. Their values can be of 'InputType'. Their 
 | 
				
			||||||
 | 
					            defaults can be set by setting values in a Map corresponding to the field's name.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Typically, you can make rest calls in the async initialize function, to fetch the initial values for
 | 
				
			||||||
 | 
					            these fields. This is called after the onSubmit callback, to reinitialize the defaults.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            In this example, the initialize function simply reads the SessionStorage to fetch the default values
 | 
				
			||||||
 | 
					            for these fields. These are then set when the changes are submitted.
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  public initialize = async (): Promise<Map<string, InputType>> => {
 | 
				
			||||||
 | 
					    await delay(1000);
 | 
				
			||||||
 | 
					    const defaults = new Map<string, InputType>();
 | 
				
			||||||
 | 
					    defaults.set("regions", SessionStorageUtility.getEntry("regions"));
 | 
				
			||||||
 | 
					    defaults.set("enableLogging", SessionStorageUtility.getEntry("enableLogging") === "true");
 | 
				
			||||||
 | 
					    const stringInput = SessionStorageUtility.getEntry("accountName");
 | 
				
			||||||
 | 
					    defaults.set("accountName", stringInput ? stringInput : "");
 | 
				
			||||||
 | 
					    const numberSliderInput = parseInt(SessionStorageUtility.getEntry("dbThroughput"));
 | 
				
			||||||
 | 
					    defaults.set("dbThroughput", isNaN(numberSliderInput) ? 1 : numberSliderInput);
 | 
				
			||||||
 | 
					    const numberSpinnerInput = parseInt(SessionStorageUtility.getEntry("collectionThroughput"));
 | 
				
			||||||
 | 
					    defaults.set("collectionThroughput", isNaN(numberSpinnerInput) ? 1 : numberSpinnerInput);
 | 
				
			||||||
 | 
					    return defaults;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  @PropertyInfo()
 | 
				
			||||||
 | 
					    - optional
 | 
				
			||||||
 | 
					    - input: Info | () => Promise<Info>
 | 
				
			||||||
 | 
					    - role: Display an Info bar above the UI element for this property.
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  @PropertyInfo(regionDropdownInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  @Values() :
 | 
				
			||||||
 | 
					    - input: NumberInputOptions | StringInputOptions | BooleanInputOptions | DropdownInputOptions
 | 
				
			||||||
 | 
					    - role: Specifies the required options to display the property as TextBox, Number Spinner/Slider, Radio buton or Dropdown.
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  @Values({ label: "Regions", choices: regionDropdownItems })
 | 
				
			||||||
 | 
					  regions: DropdownItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Values({
 | 
				
			||||||
 | 
					    label: "Enable Logging",
 | 
				
			||||||
 | 
					    trueLabel: "Enable",
 | 
				
			||||||
 | 
					    falseLabel: "Disable"
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  enableLogging: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Values({
 | 
				
			||||||
 | 
					    label: "Account Name",
 | 
				
			||||||
 | 
					    placeholder: "Enter the account name"
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  accountName: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  @OnChange()
 | 
				
			||||||
 | 
					    - optional
 | 
				
			||||||
 | 
					    - input: (currentValues: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
 | 
				
			||||||
 | 
					    - role: Takes a Map of current values and the newValue for this property as inputs. This is called when a property
 | 
				
			||||||
 | 
					            changes its value in the UI. This can be used to change other input values based on some other input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            The new Map of propertyName -> value is returned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            In this example, the onDbThroughputChange function sets the collectionThroughput to the same value as the dbThroughput
 | 
				
			||||||
 | 
					            when the slider in moved in the UI.
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  @OnChange(onDbThroughputChange)
 | 
				
			||||||
 | 
					  @Values({
 | 
				
			||||||
 | 
					    label: "Database Throughput",
 | 
				
			||||||
 | 
					    min: 400,
 | 
				
			||||||
 | 
					    max: initializeMaxThroughput,
 | 
				
			||||||
 | 
					    step: 100,
 | 
				
			||||||
 | 
					    uiType: UiType.Slider
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  dbThroughput: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Values({
 | 
				
			||||||
 | 
					    label: "Collection Throughput",
 | 
				
			||||||
 | 
					    min: 400,
 | 
				
			||||||
 | 
					    max: initializeMaxThroughput,
 | 
				
			||||||
 | 
					    step: 100,
 | 
				
			||||||
 | 
					    uiType: UiType.Spinner
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  collectionThroughput: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								src/SelfServe/PropertyDecorators.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/SelfServe/PropertyDecorators.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					import { DropdownItem, Info, InputType, UiType } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
 | 
					import { addPropertyToMap } from "./SelfServeUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Decorator {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					  value: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface InputOptionsBase {
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NumberInputOptions extends InputOptionsBase {
 | 
				
			||||||
 | 
					  min: (() => Promise<number>) | number;
 | 
				
			||||||
 | 
					  max: (() => Promise<number>) | number;
 | 
				
			||||||
 | 
					  step: (() => Promise<number>) | number;
 | 
				
			||||||
 | 
					  uiType: UiType;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StringInputOptions extends InputOptionsBase {
 | 
				
			||||||
 | 
					  placeholder?: (() => Promise<string>) | string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface BooleanInputOptions extends InputOptionsBase {
 | 
				
			||||||
 | 
					  trueLabel: (() => Promise<string>) | string;
 | 
				
			||||||
 | 
					  falseLabel: (() => Promise<string>) | string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DropdownInputOptions extends InputOptionsBase {
 | 
				
			||||||
 | 
					  choices: (() => Promise<DropdownItem[]>) | DropdownItem[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type InputOptions = NumberInputOptions | StringInputOptions | BooleanInputOptions | DropdownInputOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isNumberInputOptions(inputOptions: InputOptions): inputOptions is NumberInputOptions {
 | 
				
			||||||
 | 
					  return !!(inputOptions as NumberInputOptions).min;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isBooleanInputOptions(inputOptions: InputOptions): inputOptions is BooleanInputOptions {
 | 
				
			||||||
 | 
					  return !!(inputOptions as BooleanInputOptions).trueLabel;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isDropdownInputOptions(inputOptions: InputOptions): inputOptions is DropdownInputOptions {
 | 
				
			||||||
 | 
					  return !!(inputOptions as DropdownInputOptions).choices;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addToMap = (...decorators: Decorator[]): PropertyDecorator => {
 | 
				
			||||||
 | 
					  return (target, property) => {
 | 
				
			||||||
 | 
					    let className = target.constructor.name;
 | 
				
			||||||
 | 
					    const propertyName = property.toString();
 | 
				
			||||||
 | 
					    if (className === "Function") {
 | 
				
			||||||
 | 
					      className = (target as Function).name;
 | 
				
			||||||
 | 
					      throw new Error(`Property '${propertyName}' in class '${className}'should be not be static.`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const propertyType = (Reflect.getMetadata("design:type", target, property)?.name as string)?.toLowerCase();
 | 
				
			||||||
 | 
					    addPropertyToMap(target, propertyName, className, "type", propertyType);
 | 
				
			||||||
 | 
					    addPropertyToMap(target, propertyName, className, "dataFieldName", propertyName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    decorators.map((decorator: Decorator) =>
 | 
				
			||||||
 | 
					      addPropertyToMap(target, propertyName, className, decorator.name, decorator.value)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const OnChange = (
 | 
				
			||||||
 | 
					  onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
 | 
				
			||||||
 | 
					): PropertyDecorator => {
 | 
				
			||||||
 | 
					  return addToMap({ name: "onChange", value: onChange });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
 | 
				
			||||||
 | 
					  return addToMap({ name: "info", value: info });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Values = (inputOptions: InputOptions): PropertyDecorator => {
 | 
				
			||||||
 | 
					  if (isNumberInputOptions(inputOptions)) {
 | 
				
			||||||
 | 
					    const numberInputOptions = inputOptions as NumberInputOptions;
 | 
				
			||||||
 | 
					    return addToMap(
 | 
				
			||||||
 | 
					      { name: "label", value: numberInputOptions.label },
 | 
				
			||||||
 | 
					      { name: "min", value: numberInputOptions.min },
 | 
				
			||||||
 | 
					      { name: "max", value: numberInputOptions.max },
 | 
				
			||||||
 | 
					      { name: "step", value: numberInputOptions.step },
 | 
				
			||||||
 | 
					      { name: "uiType", value: numberInputOptions.uiType }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } else if (isBooleanInputOptions(inputOptions)) {
 | 
				
			||||||
 | 
					    const booleanInputOptions = inputOptions as BooleanInputOptions;
 | 
				
			||||||
 | 
					    return addToMap(
 | 
				
			||||||
 | 
					      { name: "label", value: booleanInputOptions.label },
 | 
				
			||||||
 | 
					      { name: "trueLabel", value: booleanInputOptions.trueLabel },
 | 
				
			||||||
 | 
					      { name: "falseLabel", value: booleanInputOptions.falseLabel }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } else if (isDropdownInputOptions(inputOptions)) {
 | 
				
			||||||
 | 
					    const dropdownInputOptions = inputOptions as DropdownInputOptions;
 | 
				
			||||||
 | 
					    return addToMap(
 | 
				
			||||||
 | 
					      { name: "label", value: dropdownInputOptions.label },
 | 
				
			||||||
 | 
					      { name: "choices", value: dropdownInputOptions.choices }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const stringInputOptions = inputOptions as StringInputOptions;
 | 
				
			||||||
 | 
					    return addToMap(
 | 
				
			||||||
 | 
					      { name: "label", value: stringInputOptions.label },
 | 
				
			||||||
 | 
					      { name: "placeholder", value: stringInputOptions.placeholder }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,84 +0,0 @@
 | 
				
			|||||||
import { ChoiceItem, Info, InputType, NumberInputType } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					 | 
				
			||||||
import { addPropertyToMap } from "./SelfServeUtils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Decorator {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  value: unknown;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const addToMap = (...decorators: Decorator[]): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return (target, property) => {
 | 
					 | 
				
			||||||
    const className = (target as Function).name;
 | 
					 | 
				
			||||||
    const propertyType = (Reflect.getMetadata("design:type", target, property).name as string).toLowerCase();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    addPropertyToMap(target, property.toString(), className, "type", propertyType);
 | 
					 | 
				
			||||||
    addPropertyToMap(target, property.toString(), className, "dataFieldName", property.toString());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!className) {
 | 
					 | 
				
			||||||
      throw new Error("property descriptor applied to non static field!");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    decorators.map((decorator: Decorator) =>
 | 
					 | 
				
			||||||
      addPropertyToMap(target, property.toString(), className, decorator.name, decorator.value)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const OnChange = (
 | 
					 | 
				
			||||||
  onChange: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>
 | 
					 | 
				
			||||||
): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "onChange", value: onChange });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const CustomElement = (
 | 
					 | 
				
			||||||
  customElement: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element
 | 
					 | 
				
			||||||
): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "customElement", value: customElement });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const PropertyInfo = (info: (() => Promise<Info>) | Info): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "info", value: info });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Placeholder = (placeholder: (() => Promise<string>) | string): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "placeholder", value: placeholder });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ParentOf = (children: string[]): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "parentOf", value: children });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Label = (label: (() => Promise<string>) | string): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "label", value: label });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface NumberInputOptions {
 | 
					 | 
				
			||||||
  min: (() => Promise<number>) | number;
 | 
					 | 
				
			||||||
  max: (() => Promise<number>) | number;
 | 
					 | 
				
			||||||
  step: (() => Promise<number>) | number;
 | 
					 | 
				
			||||||
  numberInputType: NumberInputType;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const NumberInput = (numberInputOptions: NumberInputOptions): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap(
 | 
					 | 
				
			||||||
    { name: "min", value: numberInputOptions.min },
 | 
					 | 
				
			||||||
    { name: "max", value: numberInputOptions.max },
 | 
					 | 
				
			||||||
    { name: "step", value: numberInputOptions.step },
 | 
					 | 
				
			||||||
    { name: "inputType", value: numberInputOptions.numberInputType }
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface BooleanInputOptions {
 | 
					 | 
				
			||||||
  trueLabel: (() => Promise<string>) | string;
 | 
					 | 
				
			||||||
  falseLabel: (() => Promise<string>) | string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const BooleanInput = (booleanInputOptions: BooleanInputOptions): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap(
 | 
					 | 
				
			||||||
    { name: "trueLabel", value: booleanInputOptions.trueLabel },
 | 
					 | 
				
			||||||
    { name: "falseLabel", value: booleanInputOptions.falseLabel }
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ChoiceInput = (choices: (() => Promise<ChoiceItem[]>) | ChoiceItem[]): PropertyDecorator => {
 | 
					 | 
				
			||||||
  return addToMap({ name: "choices", value: choices });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -9,7 +9,6 @@ import { ReactAdapter } from "../Bindings/ReactBindingHandler";
 | 
				
			|||||||
import Explorer from "../Explorer/Explorer";
 | 
					import Explorer from "../Explorer/Explorer";
 | 
				
			||||||
import { Descriptor, SmartUiComponent } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					import { Descriptor, SmartUiComponent } from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
import { SelfServeTypes } from "./SelfServeUtils";
 | 
					import { SelfServeTypes } from "./SelfServeUtils";
 | 
				
			||||||
import { SelfServeExample } from "./Example/Example";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SelfServeComponentAdapter implements ReactAdapter {
 | 
					export class SelfServeComponentAdapter implements ReactAdapter {
 | 
				
			||||||
  public parameters: ko.Observable<number>;
 | 
					  public parameters: ko.Observable<number>;
 | 
				
			||||||
@ -23,18 +22,20 @@ export class SelfServeComponentAdapter implements ReactAdapter {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getDescriptor = (selfServeType: SelfServeTypes): Descriptor => {
 | 
					  public static getDescriptor = async (selfServeType: SelfServeTypes): Promise<Descriptor> => {
 | 
				
			||||||
    switch (selfServeType) {
 | 
					    switch (selfServeType) {
 | 
				
			||||||
      case SelfServeTypes.example:
 | 
					      case SelfServeTypes.example: {
 | 
				
			||||||
        return SelfServeExample.toSmartUiDescriptor();
 | 
					        const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
 | 
				
			||||||
 | 
					        return new SelfServeExample.default().toSmartUiDescriptor();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        return undefined;
 | 
					        return undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public renderComponent(): JSX.Element {
 | 
					  public async renderComponent(): Promise<JSX.Element> {
 | 
				
			||||||
    const selfServeType = this.container.selfServeType();
 | 
					    const selfServeType = this.container.selfServeType();
 | 
				
			||||||
    const smartUiDescriptor = this.getDescriptor(selfServeType);
 | 
					    const smartUiDescriptor = await SelfServeComponentAdapter.getDescriptor(selfServeType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const element = smartUiDescriptor ? (
 | 
					    const element = smartUiDescriptor ? (
 | 
				
			||||||
      <SmartUiComponent descriptor={smartUiDescriptor} />
 | 
					      <SmartUiComponent descriptor={smartUiDescriptor} />
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										275
									
								
								src/SelfServe/SelfServeUtils.test.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/SelfServe/SelfServeUtils.test.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,275 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CommonInputTypes,
 | 
				
			||||||
 | 
					  mapToSmartUiDescriptor,
 | 
				
			||||||
 | 
					  SelfServeBaseClass,
 | 
				
			||||||
 | 
					  updateContextWithDecorator
 | 
				
			||||||
 | 
					} from "./SelfServeUtils";
 | 
				
			||||||
 | 
					import { InputType, UiType } from "./../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("SelfServeUtils", () => {
 | 
				
			||||||
 | 
					  it("initialize should be declared for self serve classes", () => {
 | 
				
			||||||
 | 
					    class Test extends SelfServeBaseClass {
 | 
				
			||||||
 | 
					      public onSubmit = async (): Promise<void> => {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      public initialize: () => Promise<Map<string, InputType>>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(() => new Test().toSmartUiDescriptor()).toThrow("initialize() was not declared for the class 'Test'");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("onSubmit should be declared for self serve classes", () => {
 | 
				
			||||||
 | 
					    class Test extends SelfServeBaseClass {
 | 
				
			||||||
 | 
					      public onSubmit: () => Promise<void>;
 | 
				
			||||||
 | 
					      public initialize = async (): Promise<Map<string, InputType>> => {
 | 
				
			||||||
 | 
					        return undefined;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(() => new Test().toSmartUiDescriptor()).toThrow("onSubmit() was not declared for the class 'Test'");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("@SmartUi decorator must be present for self serve classes", () => {
 | 
				
			||||||
 | 
					    class Test extends SelfServeBaseClass {
 | 
				
			||||||
 | 
					      public onSubmit = async (): Promise<void> => {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      public initialize = async (): Promise<Map<string, InputType>> => {
 | 
				
			||||||
 | 
					        return undefined;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(() => new Test().toSmartUiDescriptor()).toThrow("@SmartUi decorator was not declared for the class 'Test'");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("updateContextWithDecorator", () => {
 | 
				
			||||||
 | 
					    const context = new Map<string, CommonInputTypes>();
 | 
				
			||||||
 | 
					    updateContextWithDecorator(context, "dbThroughput", "testClass", "max", 1);
 | 
				
			||||||
 | 
					    updateContextWithDecorator(context, "dbThroughput", "testClass", "min", 2);
 | 
				
			||||||
 | 
					    updateContextWithDecorator(context, "collThroughput", "testClass", "max", 5);
 | 
				
			||||||
 | 
					    expect(context.size).toEqual(2);
 | 
				
			||||||
 | 
					    expect(context.get("dbThroughput")).toEqual({ id: "dbThroughput", max: 1, min: 2 });
 | 
				
			||||||
 | 
					    expect(context.get("collThroughput")).toEqual({ id: "collThroughput", max: 5 });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("mapToSmartUiDescriptor", () => {
 | 
				
			||||||
 | 
					    const context: Map<string, CommonInputTypes> = new Map([
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "dbThroughput",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "dbThroughput",
 | 
				
			||||||
 | 
					          dataFieldName: "dbThroughput",
 | 
				
			||||||
 | 
					          type: "number",
 | 
				
			||||||
 | 
					          label: "Database Throughput",
 | 
				
			||||||
 | 
					          min: 1,
 | 
				
			||||||
 | 
					          max: 5,
 | 
				
			||||||
 | 
					          step: 1,
 | 
				
			||||||
 | 
					          uiType: UiType.Slider
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "collThroughput",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "collThroughput",
 | 
				
			||||||
 | 
					          dataFieldName: "collThroughput",
 | 
				
			||||||
 | 
					          type: "number",
 | 
				
			||||||
 | 
					          label: "Coll Throughput",
 | 
				
			||||||
 | 
					          min: 1,
 | 
				
			||||||
 | 
					          max: 5,
 | 
				
			||||||
 | 
					          step: 1,
 | 
				
			||||||
 | 
					          uiType: UiType.Spinner
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "invalidThroughput",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "invalidThroughput",
 | 
				
			||||||
 | 
					          dataFieldName: "invalidThroughput",
 | 
				
			||||||
 | 
					          type: "boolean",
 | 
				
			||||||
 | 
					          label: "Invalid Coll Throughput",
 | 
				
			||||||
 | 
					          min: 1,
 | 
				
			||||||
 | 
					          max: 5,
 | 
				
			||||||
 | 
					          step: 1,
 | 
				
			||||||
 | 
					          uiType: UiType.Spinner,
 | 
				
			||||||
 | 
					          errorMessage: "label, truelabel and falselabel are required for boolean input"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "collName",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "collName",
 | 
				
			||||||
 | 
					          dataFieldName: "collName",
 | 
				
			||||||
 | 
					          type: "string",
 | 
				
			||||||
 | 
					          label: "Coll Name",
 | 
				
			||||||
 | 
					          placeholder: "placeholder text"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "enableLogging",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "enableLogging",
 | 
				
			||||||
 | 
					          dataFieldName: "enableLogging",
 | 
				
			||||||
 | 
					          type: "boolean",
 | 
				
			||||||
 | 
					          label: "Enable Logging",
 | 
				
			||||||
 | 
					          trueLabel: "Enable",
 | 
				
			||||||
 | 
					          falseLabel: "Disable"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "invalidEnableLogging",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "invalidEnableLogging",
 | 
				
			||||||
 | 
					          dataFieldName: "invalidEnableLogging",
 | 
				
			||||||
 | 
					          type: "boolean",
 | 
				
			||||||
 | 
					          label: "Invalid Enable Logging",
 | 
				
			||||||
 | 
					          placeholder: "placeholder text"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "regions",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "regions",
 | 
				
			||||||
 | 
					          dataFieldName: "regions",
 | 
				
			||||||
 | 
					          type: "object",
 | 
				
			||||||
 | 
					          label: "Regions",
 | 
				
			||||||
 | 
					          choices: [
 | 
				
			||||||
 | 
					            { label: "South West US", key: "SWUS" },
 | 
				
			||||||
 | 
					            { label: "North Central US", key: "NCUS" },
 | 
				
			||||||
 | 
					            { label: "East US 2", key: "EUS2" }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      [
 | 
				
			||||||
 | 
					        "invalidRegions",
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: "invalidRegions",
 | 
				
			||||||
 | 
					          dataFieldName: "invalidRegions",
 | 
				
			||||||
 | 
					          type: "object",
 | 
				
			||||||
 | 
					          label: "Invalid Regions",
 | 
				
			||||||
 | 
					          placeholder: "placeholder text"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    const expectedDescriptor = {
 | 
				
			||||||
 | 
					      root: {
 | 
				
			||||||
 | 
					        id: "root",
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "dbThroughput",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "dbThroughput",
 | 
				
			||||||
 | 
					              dataFieldName: "dbThroughput",
 | 
				
			||||||
 | 
					              type: "number",
 | 
				
			||||||
 | 
					              label: "Database Throughput",
 | 
				
			||||||
 | 
					              min: 1,
 | 
				
			||||||
 | 
					              max: 5,
 | 
				
			||||||
 | 
					              step: 1,
 | 
				
			||||||
 | 
					              uiType: "Slider"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "collThroughput",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "collThroughput",
 | 
				
			||||||
 | 
					              dataFieldName: "collThroughput",
 | 
				
			||||||
 | 
					              type: "number",
 | 
				
			||||||
 | 
					              label: "Coll Throughput",
 | 
				
			||||||
 | 
					              min: 1,
 | 
				
			||||||
 | 
					              max: 5,
 | 
				
			||||||
 | 
					              step: 1,
 | 
				
			||||||
 | 
					              uiType: "Spinner"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "invalidThroughput",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "invalidThroughput",
 | 
				
			||||||
 | 
					              dataFieldName: "invalidThroughput",
 | 
				
			||||||
 | 
					              type: "boolean",
 | 
				
			||||||
 | 
					              label: "Invalid Coll Throughput",
 | 
				
			||||||
 | 
					              min: 1,
 | 
				
			||||||
 | 
					              max: 5,
 | 
				
			||||||
 | 
					              step: 1,
 | 
				
			||||||
 | 
					              uiType: "Spinner",
 | 
				
			||||||
 | 
					              errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidThroughput'."
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "collName",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "collName",
 | 
				
			||||||
 | 
					              dataFieldName: "collName",
 | 
				
			||||||
 | 
					              type: "string",
 | 
				
			||||||
 | 
					              label: "Coll Name",
 | 
				
			||||||
 | 
					              placeholder: "placeholder text"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "enableLogging",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "enableLogging",
 | 
				
			||||||
 | 
					              dataFieldName: "enableLogging",
 | 
				
			||||||
 | 
					              type: "boolean",
 | 
				
			||||||
 | 
					              label: "Enable Logging",
 | 
				
			||||||
 | 
					              trueLabel: "Enable",
 | 
				
			||||||
 | 
					              falseLabel: "Disable"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "invalidEnableLogging",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "invalidEnableLogging",
 | 
				
			||||||
 | 
					              dataFieldName: "invalidEnableLogging",
 | 
				
			||||||
 | 
					              type: "boolean",
 | 
				
			||||||
 | 
					              label: "Invalid Enable Logging",
 | 
				
			||||||
 | 
					              placeholder: "placeholder text",
 | 
				
			||||||
 | 
					              errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidEnableLogging'."
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "regions",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "regions",
 | 
				
			||||||
 | 
					              dataFieldName: "regions",
 | 
				
			||||||
 | 
					              type: "object",
 | 
				
			||||||
 | 
					              label: "Regions",
 | 
				
			||||||
 | 
					              choices: [
 | 
				
			||||||
 | 
					                { label: "South West US", key: "SWUS" },
 | 
				
			||||||
 | 
					                { label: "North Central US", key: "NCUS" },
 | 
				
			||||||
 | 
					                { label: "East US 2", key: "EUS2" }
 | 
				
			||||||
 | 
					              ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: "invalidRegions",
 | 
				
			||||||
 | 
					            input: {
 | 
				
			||||||
 | 
					              id: "invalidRegions",
 | 
				
			||||||
 | 
					              dataFieldName: "invalidRegions",
 | 
				
			||||||
 | 
					              type: "object",
 | 
				
			||||||
 | 
					              label: "Invalid Regions",
 | 
				
			||||||
 | 
					              placeholder: "placeholder text",
 | 
				
			||||||
 | 
					              errorMessage: "label and choices are required for Dropdown input 'invalidRegions'."
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            children: [] as Node[]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      inputNames: [
 | 
				
			||||||
 | 
					        "dbThroughput",
 | 
				
			||||||
 | 
					        "collThroughput",
 | 
				
			||||||
 | 
					        "invalidThroughput",
 | 
				
			||||||
 | 
					        "collName",
 | 
				
			||||||
 | 
					        "enableLogging",
 | 
				
			||||||
 | 
					        "invalidEnableLogging",
 | 
				
			||||||
 | 
					        "regions",
 | 
				
			||||||
 | 
					        "invalidRegions"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const descriptor = mapToSmartUiDescriptor(context);
 | 
				
			||||||
 | 
					    expect(descriptor).toEqual(expectedDescriptor);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import "reflect-metadata";
 | 
					import "reflect-metadata";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ChoiceItem,
 | 
					  DropdownItem,
 | 
				
			||||||
  Node,
 | 
					  Node,
 | 
				
			||||||
  Info,
 | 
					  Info,
 | 
				
			||||||
  InputTypeValue,
 | 
					  InputTypeValue,
 | 
				
			||||||
@ -9,22 +9,43 @@ import {
 | 
				
			|||||||
  NumberInput,
 | 
					  NumberInput,
 | 
				
			||||||
  StringInput,
 | 
					  StringInput,
 | 
				
			||||||
  BooleanInput,
 | 
					  BooleanInput,
 | 
				
			||||||
  ChoiceInput,
 | 
					  DropdownInput,
 | 
				
			||||||
  InputType
 | 
					  InputType
 | 
				
			||||||
} from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
					} from "../Explorer/Controls/SmartUi/SmartUiComponent";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SelfServeType = "selfServeType";
 | 
					export enum SelfServeTypes {
 | 
				
			||||||
 | 
					  none = "none",
 | 
				
			||||||
 | 
					  invalid = "invalid",
 | 
				
			||||||
 | 
					  example = "example"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SelfServeBase {
 | 
					export abstract class SelfServeBaseClass {
 | 
				
			||||||
  public static toSmartUiDescriptor(): Descriptor {
 | 
					  public abstract onSubmit: (currentValues: Map<string, InputType>) => Promise<void>;
 | 
				
			||||||
    return Reflect.getMetadata(this.name, this) as Descriptor;
 | 
					  public abstract initialize: () => Promise<Map<string, InputType>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public toSmartUiDescriptor(): Descriptor {
 | 
				
			||||||
 | 
					    const className = this.constructor.name;
 | 
				
			||||||
 | 
					    const smartUiDescriptor = Reflect.getMetadata(className, this) as Descriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.initialize) {
 | 
				
			||||||
 | 
					      throw new Error(`initialize() was not declared for the class '${className}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!this.onSubmit) {
 | 
				
			||||||
 | 
					      throw new Error(`onSubmit() was not declared for the class '${className}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!smartUiDescriptor?.root) {
 | 
				
			||||||
 | 
					      throw new Error(`@SmartUi decorator was not declared for the class '${className}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    smartUiDescriptor.initialize = this.initialize;
 | 
				
			||||||
 | 
					    smartUiDescriptor.onSubmit = this.onSubmit;
 | 
				
			||||||
 | 
					    return smartUiDescriptor;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CommonInputTypes {
 | 
					export interface CommonInputTypes {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  info?: (() => Promise<Info>) | Info;
 | 
					  info?: (() => Promise<Info>) | Info;
 | 
				
			||||||
  parentOf?: string[];
 | 
					 | 
				
			||||||
  type?: InputTypeValue;
 | 
					  type?: InputTypeValue;
 | 
				
			||||||
  label?: (() => Promise<string>) | string;
 | 
					  label?: (() => Promise<string>) | string;
 | 
				
			||||||
  placeholder?: (() => Promise<string>) | string;
 | 
					  placeholder?: (() => Promise<string>) | string;
 | 
				
			||||||
@ -34,12 +55,12 @@ export interface CommonInputTypes {
 | 
				
			|||||||
  step?: (() => Promise<number>) | number;
 | 
					  step?: (() => Promise<number>) | number;
 | 
				
			||||||
  trueLabel?: (() => Promise<string>) | string;
 | 
					  trueLabel?: (() => Promise<string>) | string;
 | 
				
			||||||
  falseLabel?: (() => Promise<string>) | string;
 | 
					  falseLabel?: (() => Promise<string>) | string;
 | 
				
			||||||
  choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
 | 
					  choices?: (() => Promise<DropdownItem[]>) | DropdownItem[];
 | 
				
			||||||
  inputType?: string;
 | 
					  uiType?: string;
 | 
				
			||||||
 | 
					  errorMessage?: string;
 | 
				
			||||||
  onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
 | 
					  onChange?: (currentState: Map<string, InputType>, newValue: InputType) => Map<string, InputType>;
 | 
				
			||||||
  onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
 | 
					  onSubmit?: (currentValues: Map<string, InputType>) => Promise<void>;
 | 
				
			||||||
  initialize?: () => Promise<Map<string, InputType>>;
 | 
					  initialize?: () => Promise<Map<string, InputType>>;
 | 
				
			||||||
  customElement?: ((currentValues: Map<string, InputType>) => Promise<JSX.Element>) | JSX.Element;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
 | 
					const setValue = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
 | 
				
			||||||
@ -54,95 +75,86 @@ const getValue = <T extends keyof CommonInputTypes>(name: T, fieldObject: Common
 | 
				
			|||||||
  return fieldObject[name];
 | 
					  return fieldObject[name];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addPropertyToMap = (
 | 
					export const addPropertyToMap = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
 | 
				
			||||||
  target: Object,
 | 
					  target: unknown,
 | 
				
			||||||
  propertyKey: string,
 | 
					  propertyName: string,
 | 
				
			||||||
  metadataKey: string,
 | 
					  className: string,
 | 
				
			||||||
  descriptorName: string,
 | 
					  descriptorName: string,
 | 
				
			||||||
  descriptorValue: any
 | 
					  descriptorValue: K
 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
  const descriptorKey = descriptorName.toString() as keyof CommonInputTypes;
 | 
					  let context = Reflect.getMetadata(className, target) as Map<string, CommonInputTypes>;
 | 
				
			||||||
  let context = Reflect.getMetadata(metadataKey, target) as Map<string, CommonInputTypes>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!context) {
 | 
					  if (!context) {
 | 
				
			||||||
    context = new Map<string, CommonInputTypes>();
 | 
					    context = new Map<string, CommonInputTypes>();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  updateContextWithDecorator(context, propertyName, className, descriptorName, descriptorValue);
 | 
				
			||||||
 | 
					  Reflect.defineMetadata(className, context, target);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const updateContextWithDecorator = <T extends keyof CommonInputTypes, K extends CommonInputTypes[T]>(
 | 
				
			||||||
 | 
					  context: Map<string, CommonInputTypes>,
 | 
				
			||||||
 | 
					  propertyName: string,
 | 
				
			||||||
 | 
					  className: string,
 | 
				
			||||||
 | 
					  descriptorName: string,
 | 
				
			||||||
 | 
					  descriptorValue: K
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					  const descriptorKey = descriptorName as keyof CommonInputTypes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!(context instanceof Map)) {
 | 
					  if (!(context instanceof Map)) {
 | 
				
			||||||
    throw new Error("@SmartUi should be the first decorator for the class.");
 | 
					    console.log(context);
 | 
				
			||||||
 | 
					    throw new Error(`@SmartUi should be the first decorator for the class '${className}'.`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let propertyObject = context.get(propertyKey);
 | 
					  let propertyObject = context.get(propertyName);
 | 
				
			||||||
  if (!propertyObject) {
 | 
					  if (!propertyObject) {
 | 
				
			||||||
    propertyObject = { id: propertyKey };
 | 
					    propertyObject = { id: propertyName };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type" && descriptorKey !== "dataFieldName") {
 | 
					  if (getValue(descriptorKey, propertyObject) && descriptorKey !== "type" && descriptorKey !== "dataFieldName") {
 | 
				
			||||||
    throw new Error("duplicate descriptor");
 | 
					    throw new Error(
 | 
				
			||||||
 | 
					      `Duplicate value passed for '${descriptorKey}' on property '${propertyName}' of class '${className}'`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setValue(descriptorKey, descriptorValue, propertyObject);
 | 
					  setValue(descriptorKey, descriptorValue, propertyObject);
 | 
				
			||||||
  context.set(propertyKey, propertyObject);
 | 
					  context.set(propertyName, propertyObject);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  Reflect.defineMetadata(metadataKey, context, target);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const toSmartUiDescriptor = (metadataKey: string, target: Object): void => {
 | 
					export const buildSmartUiDescriptor = (className: string, target: unknown): void => {
 | 
				
			||||||
  const context = Reflect.getMetadata(metadataKey, target) as Map<string, CommonInputTypes>;
 | 
					  const context = Reflect.getMetadata(className, target) as Map<string, CommonInputTypes>;
 | 
				
			||||||
  Reflect.defineMetadata(metadataKey, context, target);
 | 
					  const smartUiDescriptor = mapToSmartUiDescriptor(context);
 | 
				
			||||||
 | 
					  Reflect.defineMetadata(className, smartUiDescriptor, target);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const mapToSmartUiDescriptor = (context: Map<string, CommonInputTypes>): Descriptor => {
 | 
				
			||||||
  const root = context.get("root");
 | 
					  const root = context.get("root");
 | 
				
			||||||
  context.delete("root");
 | 
					  context.delete("root");
 | 
				
			||||||
 | 
					  const inputNames: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!root?.onSubmit) {
 | 
					  const smartUiDescriptor: Descriptor = {
 | 
				
			||||||
    throw new Error(
 | 
					 | 
				
			||||||
      "@OnSubmit decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!root?.initialize) {
 | 
					 | 
				
			||||||
    throw new Error(
 | 
					 | 
				
			||||||
      "@Initialize decorator not declared for the class. Please ensure @SmartUi is the first decorator used for the class."
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const smartUiDescriptor = {
 | 
					 | 
				
			||||||
    onSubmit: root.onSubmit,
 | 
					 | 
				
			||||||
    initialize: root.initialize,
 | 
					 | 
				
			||||||
    root: {
 | 
					    root: {
 | 
				
			||||||
      id: "root",
 | 
					      id: "root",
 | 
				
			||||||
      info: root.info,
 | 
					      info: root?.info,
 | 
				
			||||||
      children: []
 | 
					      children: []
 | 
				
			||||||
    } as Node
 | 
					    }
 | 
				
			||||||
  } as Descriptor;
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (context.size > 0) {
 | 
					  while (context.size > 0) {
 | 
				
			||||||
    const key = context.keys().next().value;
 | 
					    const key = context.keys().next().value;
 | 
				
			||||||
    addToDescriptor(context, smartUiDescriptor, smartUiDescriptor.root, key);
 | 
					    addToDescriptor(context, smartUiDescriptor.root, key, inputNames);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  smartUiDescriptor.inputNames = inputNames;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Reflect.defineMetadata(metadataKey, smartUiDescriptor, target);
 | 
					  return smartUiDescriptor;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const addToDescriptor = (
 | 
					const addToDescriptor = (
 | 
				
			||||||
  context: Map<string, CommonInputTypes>,
 | 
					  context: Map<string, CommonInputTypes>,
 | 
				
			||||||
  smartUiDescriptor: Descriptor,
 | 
					 | 
				
			||||||
  root: Node,
 | 
					  root: Node,
 | 
				
			||||||
  key: string
 | 
					  key: string,
 | 
				
			||||||
 | 
					  inputNames: string[]
 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
  const value = context.get(key);
 | 
					  const value = context.get(key);
 | 
				
			||||||
  if (!value) {
 | 
					  inputNames.push(value.id);
 | 
				
			||||||
    // should already be added to root
 | 
					 | 
				
			||||||
    const childNode = getChildFromRoot(key, smartUiDescriptor);
 | 
					 | 
				
			||||||
    if (!childNode) {
 | 
					 | 
				
			||||||
      // 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(childNode);
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const childrenKeys = value.parentOf;
 | 
					 | 
				
			||||||
  const element = {
 | 
					  const element = {
 | 
				
			||||||
    id: value.id,
 | 
					    id: value.id,
 | 
				
			||||||
    info: value.info,
 | 
					    info: value.info,
 | 
				
			||||||
@ -150,61 +162,30 @@ const addToDescriptor = (
 | 
				
			|||||||
    children: []
 | 
					    children: []
 | 
				
			||||||
  } as Node;
 | 
					  } as Node;
 | 
				
			||||||
  context.delete(key);
 | 
					  context.delete(key);
 | 
				
			||||||
  for (const childKey in childrenKeys) {
 | 
					 | 
				
			||||||
    addToDescriptor(context, smartUiDescriptor, element, childrenKeys[childKey]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  root.children.push(element);
 | 
					  root.children.push(element);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getChildFromRoot = (key: string, smartUiDescriptor: Descriptor): Node => {
 | 
					 | 
				
			||||||
  let i = 0;
 | 
					 | 
				
			||||||
  const children = smartUiDescriptor.root.children;
 | 
					 | 
				
			||||||
  while (i < children.length) {
 | 
					 | 
				
			||||||
    if (children[i]?.id === key) {
 | 
					 | 
				
			||||||
      const value = children[i];
 | 
					 | 
				
			||||||
      delete children[i];
 | 
					 | 
				
			||||||
      return value;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      i++;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return undefined;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getInput = (value: CommonInputTypes): AnyInput => {
 | 
					const getInput = (value: CommonInputTypes): AnyInput => {
 | 
				
			||||||
  if (!value.label && !value.customElement) {
 | 
					 | 
				
			||||||
    throw new Error("label is required.");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  switch (value.type) {
 | 
					  switch (value.type) {
 | 
				
			||||||
    case "number":
 | 
					    case "number":
 | 
				
			||||||
      if (!value.step || !value.inputType || !value.min || !value.max) {
 | 
					      if (!value.label || !value.step || !value.uiType || !value.min || !value.max) {
 | 
				
			||||||
        throw new Error("step, min, miax and inputType are needed for number type");
 | 
					        value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return value as NumberInput;
 | 
					      return value as NumberInput;
 | 
				
			||||||
    case "string":
 | 
					    case "string":
 | 
				
			||||||
 | 
					      if (!value.label) {
 | 
				
			||||||
 | 
					        value.errorMessage = `label is required for string input '${value.id}'.`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      return value as StringInput;
 | 
					      return value as StringInput;
 | 
				
			||||||
    case "boolean":
 | 
					    case "boolean":
 | 
				
			||||||
      if (!value.trueLabel || !value.falseLabel) {
 | 
					      if (!value.label || !value.trueLabel || !value.falseLabel) {
 | 
				
			||||||
        throw new Error("truelabel and falselabel are needed for boolean type");
 | 
					        value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return value as BooleanInput;
 | 
					      return value as BooleanInput;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      if (!value.choices) {
 | 
					      if (!value.label || !value.choices) {
 | 
				
			||||||
        throw new Error("choices are needed for enum type");
 | 
					        value.errorMessage = `label and choices are required for Dropdown input '${value.id}'.`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return value as ChoiceInput;
 | 
					      return value as DropdownInput;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export enum SelfServeTypes {
 | 
					 | 
				
			||||||
  none = "none",
 | 
					 | 
				
			||||||
  invalid = "invalid",
 | 
					 | 
				
			||||||
  example = "example"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const getSelfServeType = (search: string): SelfServeTypes => {
 | 
					 | 
				
			||||||
  const params = new URLSearchParams(search);
 | 
					 | 
				
			||||||
  const selfServeTypeParam = params.get(SelfServeType)?.toLowerCase();
 | 
					 | 
				
			||||||
  return SelfServeTypes[selfServeTypeParam as keyof typeof SelfServeTypes];
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,59 +1,9 @@
 | 
				
			|||||||
import { ElementHandle, Frame } from "puppeteer";
 | 
					import { ElementHandle, Frame } from "puppeteer";
 | 
				
			||||||
import { TestExplorerParams } from "./testExplorer/TestExplorerParams";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					import * as path from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTEBOOK_OPERATION_DELAY = 5000;
 | 
					export const NOTEBOOK_OPERATION_DELAY = 5000;
 | 
				
			||||||
export const RENDER_DELAY = 2500;
 | 
					export const RENDER_DELAY = 2500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let testExplorerFrame: Frame;
 | 
					 | 
				
			||||||
export const getTestExplorerFrame = async (): Promise<Frame> => {
 | 
					 | 
				
			||||||
  if (testExplorerFrame) {
 | 
					 | 
				
			||||||
    return testExplorerFrame;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const notebooksTestRunnerTenantId = process.env.NOTEBOOKS_TEST_RUNNER_TENANT_ID;
 | 
					 | 
				
			||||||
  const notebooksTestRunnerClientId = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_ID;
 | 
					 | 
				
			||||||
  const notebooksTestRunnerClientSecret = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET;
 | 
					 | 
				
			||||||
  const portalRunnerDatabaseAccount = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT;
 | 
					 | 
				
			||||||
  const portalRunnerDatabaseAccountKey = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY;
 | 
					 | 
				
			||||||
  const portalRunnerSubscripton = process.env.PORTAL_RUNNER_SUBSCRIPTION;
 | 
					 | 
				
			||||||
  const portalRunnerResourceGroup = process.env.PORTAL_RUNNER_RESOURCE_GROUP;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const testExplorerUrl = new URL("testExplorer.html", "https://localhost:1234");
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.notebooksTestRunnerTenantId,
 | 
					 | 
				
			||||||
    encodeURI(notebooksTestRunnerTenantId)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.notebooksTestRunnerClientId,
 | 
					 | 
				
			||||||
    encodeURI(notebooksTestRunnerClientId)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.notebooksTestRunnerClientSecret,
 | 
					 | 
				
			||||||
    encodeURI(notebooksTestRunnerClientSecret)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.portalRunnerDatabaseAccount,
 | 
					 | 
				
			||||||
    encodeURI(portalRunnerDatabaseAccount)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.portalRunnerDatabaseAccountKey,
 | 
					 | 
				
			||||||
    encodeURI(portalRunnerDatabaseAccountKey)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(TestExplorerParams.portalRunnerSubscripton, encodeURI(portalRunnerSubscripton));
 | 
					 | 
				
			||||||
  testExplorerUrl.searchParams.append(
 | 
					 | 
				
			||||||
    TestExplorerParams.portalRunnerResourceGroup,
 | 
					 | 
				
			||||||
    encodeURI(portalRunnerResourceGroup)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  await page.goto(testExplorerUrl.toString());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handle = await page.waitForSelector("iframe");
 | 
					 | 
				
			||||||
  testExplorerFrame = await handle.contentFrame();
 | 
					 | 
				
			||||||
  await testExplorerFrame.waitForSelector(".galleryHeader");
 | 
					 | 
				
			||||||
  return testExplorerFrame;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const uploadNotebookIfNotExist = async (frame: Frame, notebookName: string): Promise<ElementHandle<Element>> => {
 | 
					export const uploadNotebookIfNotExist = async (frame: Frame, notebookName: string): Promise<ElementHandle<Element>> => {
 | 
				
			||||||
  const notebookNode = await getNotebookNode(frame, notebookName);
 | 
					  const notebookNode = await getNotebookNode(frame, notebookName);
 | 
				
			||||||
  if (notebookNode) {
 | 
					  if (notebookNode) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import "expect-puppeteer";
 | 
					import { uploadNotebookIfNotExist } from "./notebookTestUtils";
 | 
				
			||||||
import { getTestExplorerFrame, uploadNotebookIfNotExist } from "./notebookTestUtils";
 | 
					 | 
				
			||||||
import { ElementHandle, Frame } from "puppeteer";
 | 
					import { ElementHandle, Frame } from "puppeteer";
 | 
				
			||||||
 | 
					import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jest.setTimeout(300000);
 | 
					jest.setTimeout(300000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -12,6 +12,7 @@ describe("Notebook UI tests", () => {
 | 
				
			|||||||
  it("Upload, Open and Delete Notebook", async () => {
 | 
					  it("Upload, Open and Delete Notebook", async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      frame = await getTestExplorerFrame();
 | 
					      frame = await getTestExplorerFrame();
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".galleryHeader");
 | 
				
			||||||
      uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
 | 
					      uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
 | 
				
			||||||
      await uploadedNotebookNode.click();
 | 
					      await uploadedNotebookNode.click();
 | 
				
			||||||
      await frame.waitForSelector(".tabNavText");
 | 
					      await frame.waitForSelector(".tabNavText");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										29
									
								
								test/selfServe/selfServeExample.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								test/selfServe/selfServeExample.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					import { Frame } from "puppeteer";
 | 
				
			||||||
 | 
					import { TestExplorerParams } from "../testExplorer/TestExplorerParams";
 | 
				
			||||||
 | 
					import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
 | 
				
			||||||
 | 
					import { SelfServeTypes } from "../../src/SelfServe/SelfServeUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jest.setTimeout(300000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let frame: Frame;
 | 
				
			||||||
 | 
					describe("Notebook UI tests", () => {
 | 
				
			||||||
 | 
					  it("Upload, Open and Delete Notebook", async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      frame = await getTestExplorerFrame(
 | 
				
			||||||
 | 
					        new Map<string, string>([[TestExplorerParams.selfServeType, SelfServeTypes.example]])
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".ms-Dropdown");
 | 
				
			||||||
 | 
					      const dropdownLabel = await frame.$eval(".ms-Dropdown-label", element => element.textContent);
 | 
				
			||||||
 | 
					      expect(dropdownLabel).toEqual("Regions");
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".radioSwitchComponent");
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".ms-TextField");
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".ms-Slider ");
 | 
				
			||||||
 | 
					      await frame.waitForSelector(".ms-spinButton-input");
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					      const testName = (expect as any).getState().currentTestName;
 | 
				
			||||||
 | 
					      await page.screenshot({ path: `Test Failed ${testName}.jpg` });
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
import { MessageTypes } from "../../../src/Contracts/ExplorerContracts";
 | 
					import { MessageTypes } from "../../src/Contracts/ExplorerContracts";
 | 
				
			||||||
import "../../../less/hostedexplorer.less";
 | 
					import "../../less/hostedexplorer.less";
 | 
				
			||||||
import { TestExplorerParams } from "./TestExplorerParams";
 | 
					import { TestExplorerParams } from "./TestExplorerParams";
 | 
				
			||||||
import { ClientSecretCredential } from "@azure/identity";
 | 
					import { ClientSecretCredential } from "@azure/identity";
 | 
				
			||||||
import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models";
 | 
					import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models";
 | 
				
			||||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
 | 
					import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
 | 
				
			||||||
import * as msRest from "@azure/ms-rest-js";
 | 
					import * as msRest from "@azure/ms-rest-js";
 | 
				
			||||||
import * as ViewModels from "../../../src/Contracts/ViewModels";
 | 
					import * as ViewModels from "../../src/Contracts/ViewModels";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomSigner implements msRest.ServiceClientCredentials {
 | 
					class CustomSigner implements msRest.ServiceClientCredentials {
 | 
				
			||||||
  private token: string;
 | 
					  private token: string;
 | 
				
			||||||
@ -87,6 +87,7 @@ const initTestExplorer = async (): Promise<void> => {
 | 
				
			|||||||
  const portalRunnerResourceGroup = decodeURIComponent(
 | 
					  const portalRunnerResourceGroup = decodeURIComponent(
 | 
				
			||||||
    urlSearchParams.get(TestExplorerParams.portalRunnerResourceGroup)
 | 
					    urlSearchParams.get(TestExplorerParams.portalRunnerResourceGroup)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					  const selfServeType = urlSearchParams.get(TestExplorerParams.selfServeType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const token = await AADLogin(
 | 
					  const token = await AADLogin(
 | 
				
			||||||
    notebooksTestRunnerTenantId,
 | 
					    notebooksTestRunnerTenantId,
 | 
				
			||||||
@ -128,7 +129,8 @@ const initTestExplorer = async (): Promise<void> => {
 | 
				
			|||||||
        throughput: { fixed: 400, unlimited: 400, unlimitedmax: 100000, unlimitedmin: 400, shared: 400 }
 | 
					        throughput: { fixed: 400, unlimited: 400, unlimitedmax: 100000, unlimitedmin: 400, shared: 400 }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      // add UI test only when feature is not dependent on flights anymore
 | 
					      // add UI test only when feature is not dependent on flights anymore
 | 
				
			||||||
      flights: []
 | 
					      flights: [],
 | 
				
			||||||
 | 
					      selfServeType: selfServeType
 | 
				
			||||||
    } as ViewModels.DataExplorerInputsFrame
 | 
					    } as ViewModels.DataExplorerInputsFrame
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5,5 +5,6 @@ export enum TestExplorerParams {
 | 
				
			|||||||
  portalRunnerDatabaseAccount = "portalRunnerDatabaseAccount",
 | 
					  portalRunnerDatabaseAccount = "portalRunnerDatabaseAccount",
 | 
				
			||||||
  portalRunnerDatabaseAccountKey = "portalRunnerDatabaseAccountKey",
 | 
					  portalRunnerDatabaseAccountKey = "portalRunnerDatabaseAccountKey",
 | 
				
			||||||
  portalRunnerSubscripton = "portalRunnerSubscripton",
 | 
					  portalRunnerSubscripton = "portalRunnerSubscripton",
 | 
				
			||||||
  portalRunnerResourceGroup = "portalRunnerResourceGroup"
 | 
					  portalRunnerResourceGroup = "portalRunnerResourceGroup",
 | 
				
			||||||
 | 
					  selfServeType = "selfServeType"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								test/testExplorer/TestExplorerUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								test/testExplorer/TestExplorerUtils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import { Frame } from "puppeteer";
 | 
				
			||||||
 | 
					import { TestExplorerParams } from "./TestExplorerParams";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let testExplorerFrame: Frame;
 | 
				
			||||||
 | 
					export const getTestExplorerFrame = async (params?: Map<string, string>): Promise<Frame> => {
 | 
				
			||||||
 | 
					  if (testExplorerFrame) {
 | 
				
			||||||
 | 
					    return testExplorerFrame;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const notebooksTestRunnerTenantId = process.env.NOTEBOOKS_TEST_RUNNER_TENANT_ID;
 | 
				
			||||||
 | 
					  const notebooksTestRunnerClientId = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_ID;
 | 
				
			||||||
 | 
					  const notebooksTestRunnerClientSecret = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET;
 | 
				
			||||||
 | 
					  const portalRunnerDatabaseAccount = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT;
 | 
				
			||||||
 | 
					  const portalRunnerDatabaseAccountKey = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY;
 | 
				
			||||||
 | 
					  const portalRunnerSubscripton = process.env.PORTAL_RUNNER_SUBSCRIPTION;
 | 
				
			||||||
 | 
					  const portalRunnerResourceGroup = process.env.PORTAL_RUNNER_RESOURCE_GROUP;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const testExplorerUrl = new URL("testExplorer.html", "https://localhost:1234");
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.notebooksTestRunnerTenantId,
 | 
				
			||||||
 | 
					    encodeURI(notebooksTestRunnerTenantId)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.notebooksTestRunnerClientId,
 | 
				
			||||||
 | 
					    encodeURI(notebooksTestRunnerClientId)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.notebooksTestRunnerClientSecret,
 | 
				
			||||||
 | 
					    encodeURI(notebooksTestRunnerClientSecret)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.portalRunnerDatabaseAccount,
 | 
				
			||||||
 | 
					    encodeURI(portalRunnerDatabaseAccount)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.portalRunnerDatabaseAccountKey,
 | 
				
			||||||
 | 
					    encodeURI(portalRunnerDatabaseAccountKey)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(TestExplorerParams.portalRunnerSubscripton, encodeURI(portalRunnerSubscripton));
 | 
				
			||||||
 | 
					  testExplorerUrl.searchParams.append(
 | 
				
			||||||
 | 
					    TestExplorerParams.portalRunnerResourceGroup,
 | 
				
			||||||
 | 
					    encodeURI(portalRunnerResourceGroup)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (params) {
 | 
				
			||||||
 | 
					    for (const key of params.keys()) {
 | 
				
			||||||
 | 
					      testExplorerUrl.searchParams.append(key, encodeURI(params.get(key)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await page.goto(testExplorerUrl.toString());
 | 
				
			||||||
 | 
					  const handle = await page.waitForSelector("iframe");
 | 
				
			||||||
 | 
					  return await handle.contentFrame();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -21,6 +21,6 @@
 | 
				
			|||||||
    "noEmit": true,
 | 
					    "noEmit": true,
 | 
				
			||||||
    "types": ["jest"]
 | 
					    "types": ["jest"]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": ["./src/**/*", "./test/notebooks/testExplorer/**/*"],
 | 
					  "include": ["./src/**/*", "test/testExplorer/**/*"],
 | 
				
			||||||
  "exclude": ["./src/**/__mocks__/**/*"]
 | 
					  "exclude": ["./src/**/__mocks__/**/*"]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -142,7 +142,7 @@ module.exports = function(env = {}, argv = {}) {
 | 
				
			|||||||
    }),
 | 
					    }),
 | 
				
			||||||
    new HtmlWebpackPlugin({
 | 
					    new HtmlWebpackPlugin({
 | 
				
			||||||
      filename: "testExplorer.html",
 | 
					      filename: "testExplorer.html",
 | 
				
			||||||
      template: "test/notebooks/testExplorer/testExplorer.html",
 | 
					      template: "test/testExplorer/testExplorer.html",
 | 
				
			||||||
      chunks: ["testExplorer"]
 | 
					      chunks: ["testExplorer"]
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    new HtmlWebpackPlugin({
 | 
					    new HtmlWebpackPlugin({
 | 
				
			||||||
@ -183,7 +183,7 @@ module.exports = function(env = {}, argv = {}) {
 | 
				
			|||||||
      index: "./src/Index.ts",
 | 
					      index: "./src/Index.ts",
 | 
				
			||||||
      quickstart: "./src/quickstart.ts",
 | 
					      quickstart: "./src/quickstart.ts",
 | 
				
			||||||
      hostedExplorer: "./src/HostedExplorer.ts",
 | 
					      hostedExplorer: "./src/HostedExplorer.ts",
 | 
				
			||||||
      testExplorer: "./test/notebooks/testExplorer/TestExplorer.ts",
 | 
					      testExplorer: "./test/testExplorer/TestExplorer.ts",
 | 
				
			||||||
      heatmap: "./src/Controls/Heatmap/Heatmap.ts",
 | 
					      heatmap: "./src/Controls/Heatmap/Heatmap.ts",
 | 
				
			||||||
      terminal: "./src/Terminal/index.ts",
 | 
					      terminal: "./src/Terminal/index.ts",
 | 
				
			||||||
      notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx",
 | 
					      notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user