diff --git a/src/Bindings/ReactBindingHandler.ts b/src/Bindings/ReactBindingHandler.ts index 95b05a97f..128c16dd4 100644 --- a/src/Bindings/ReactBindingHandler.ts +++ b/src/Bindings/ReactBindingHandler.ts @@ -15,7 +15,7 @@ import * as ReactDOM from "react-dom"; export interface ReactAdapter { parameters: any; - renderComponent: (() => Promise) | (() => JSX.Element); + renderComponent: () => JSX.Element; setElement?: (elt: Element) => void; } @@ -36,12 +36,12 @@ export class Registerer { } // If any of the ko observable change inside parameters, trigger a new render. - ko.computed(() => ko.toJSON(adapter.parameters)).subscribe(async () => - ReactDOM.render(await adapter.renderComponent(), element) + ko.computed(() => ko.toJSON(adapter.parameters)).subscribe(() => + ReactDOM.render(adapter.renderComponent(), element) ); // Initial rendering at mount point - Promise.resolve(adapter.renderComponent()).then(component => ReactDOM.render(component, element)); + ReactDOM.render(adapter.renderComponent(), element); } } as ko.BindingHandler; } diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 9d17f96ae..d9a8c8a1f 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -126,7 +126,7 @@ export class Features { public static readonly enableSchema = "enableschema"; public static readonly enableSDKoperations = "enablesdkoperations"; public static readonly showMinRUSurvey = "showminrusurvey"; - public static readonly selfServeTypeForTest = "selfservetypefortest"; + public static readonly selfServeType = "selfservetype"; } // flight names returned from the portal are always lowercase diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 704ae0906..604b7af11 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -15,7 +15,7 @@ import DocumentId from "../Explorer/Tree/DocumentId"; import StoredProcedure from "../Explorer/Tree/StoredProcedure"; import Trigger from "../Explorer/Tree/Trigger"; import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction"; -import { SelfServeTypes } from "../SelfServe/SelfServeUtils"; +import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { UploadDetails } from "../workers/upload/definitions"; import * as DataModels from "./DataModels"; import { SubscriptionType } from "./SubscriptionType"; @@ -396,7 +396,7 @@ export interface DataExplorerInputsFrame { isAuthWithresourceToken?: boolean; defaultCollectionThroughput?: CollectionCreationDefaults; flights?: readonly string[]; - selfServeType?: SelfServeTypes; + selfServeType?: SelfServeType; } export interface CollectionCreationDefaults { diff --git a/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx b/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx index c70f730d3..41f4ff001 100644 --- a/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx +++ b/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx @@ -48,7 +48,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => { { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" }, { key: "feature.enablettl", label: "Enable TTL", 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.selfServeType", label: "Self serve feature", value: "sample" }, { key: "feature.enableLinkInjection", label: "Enable Injecting Notebook Viewer Link into the first cell", diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx index ddc61e919..0512a1cfb 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx @@ -5,7 +5,6 @@ import { SpinButton } from "office-ui-fabric-react/lib/SpinButton"; import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; import { TextField } from "office-ui-fabric-react/lib/TextField"; import { Text } from "office-ui-fabric-react/lib/Text"; -import { InputType } from "../../Tables/Constants"; import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent"; import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack"; import { Link, MessageBar, MessageBarType, PrimaryButton, Spinner, SpinnerSize } from "office-ui-fabric-react"; @@ -35,7 +34,7 @@ type infoPromise = () => Promise; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export type DropdownItem = { label: string; key: string }; -export type InputType = number | string | boolean | DropdownItem | JSX.Element; +export type InputType = number | string | boolean | DropdownItem; export interface BaseInput { label: (() => Promise) | string; @@ -117,7 +116,7 @@ export class SmartUiComponent extends React.Component => { + private initializeSmartUiComponent = async (): Promise => { this.setState({ isRefreshing: true }); - await this.setDefaults(this.props.descriptor.root); + await this.initializeNode(this.props.descriptor.root); + await this.setDefaults(); this.setState({ isRefreshing: false }); - await this.initialize(); }; - private initialize = async (): Promise => { + private setDefaults = async (): Promise => { this.setState({ isRefreshing: true }); let { currentValues, baselineValues } = this.state; + const initialValues = await this.props.descriptor.initialize(); 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)); baselineValues = baselineValues.set(key, initialValues.get(key)); } @@ -162,7 +163,7 @@ export class SmartUiComponent extends React.Component => { + private initializeNode = async (currentNode: Node): Promise => { if (currentNode.info && currentNode.info instanceof Function) { currentNode.info = await (currentNode.info as infoPromise)(); } @@ -170,7 +171,7 @@ export class SmartUiComponent extends React.Component await this.setDefaults(child)); + const promises = currentNode.children?.map(async (child: Node) => await this.initializeNode(child)); if (promises) { await Promise.all(promises); } @@ -222,24 +223,6 @@ export class SmartUiComponent extends React.Component { - switch (input.type) { - case "string": { - const stringInput = input as StringInput; - return stringInput.defaultValue ? (stringInput.defaultValue as string) : ""; - } - case "number": { - return (input as NumberInput).defaultValue as number; - } - case "boolean": { - return (input as BooleanInput).defaultValue as boolean; - } - default: { - return (input as DropdownInput).defaultKey as string; - } - } - }; - private renderInfo(info: Info): JSX.Element { return ( @@ -265,7 +248,7 @@ export class SmartUiComponent extends React.Component @@ -423,7 +406,7 @@ export class SmartUiComponent extends React.Component { await this.props.descriptor.onSubmit(this.state.currentValues); - this.initialize(); + this.setDefaults(); }} /> this.discard()} /> diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 542cf12f5..647414900 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -89,7 +89,7 @@ import { IChoiceGroupProps } from "office-ui-fabric-react"; import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils"; import { SubscriptionType } from "../Contracts/SubscriptionType"; import { SelfServeLoadingComponentAdapter } from "../SelfServe/SelfServeLoadingComponentAdapter"; -import { SelfServeTypes } from "../SelfServe/SelfServeUtils"; +import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { SelfServeComponentAdapter } from "../SelfServe/SelfServeComponentAdapter"; BindingHandlersRegisterer.registerBindingHandlers(); @@ -134,7 +134,7 @@ export default class Explorer { public isEnableMongoCapabilityPresent: ko.Computed; public isServerlessEnabled: ko.Computed; public isAccountReady: ko.Observable; - public selfServeType: ko.Observable; + public selfServeType: ko.Observable; public canSaveQueries: ko.Computed; public features: ko.Observable; public serverId: ko.Observable; @@ -299,7 +299,7 @@ export default class Explorer { } }); this.isAccountReady = ko.observable(false); - this.selfServeType = ko.observable(undefined); + this.selfServeType = ko.observable(undefined); this._isInitializingNotebooks = false; this._isInitializingSparkConnectionInfo = false; this.arcadiaToken = ko.observable(); @@ -1851,14 +1851,16 @@ export default class Explorer { } public setSelfServeType(inputs: ViewModels.DataExplorerInputsFrame): void { - const selfServeTypeForTest = inputs.features[Constants.Features.selfServeTypeForTest]; - if (selfServeTypeForTest) { - const selfServeType = SelfServeTypes[selfServeTypeForTest?.toLowerCase() as keyof typeof SelfServeTypes]; - this.selfServeType(selfServeType ? selfServeType : SelfServeTypes.invalid); + const selfServeFeature = inputs.features[Constants.Features.selfServeType]; + if (selfServeFeature) { + // self serve type received from query string + const selfServeType = SelfServeType[selfServeFeature?.toLowerCase() as keyof typeof SelfServeType]; + this.selfServeType(selfServeType ? selfServeType : SelfServeType.invalid); } else if (inputs.selfServeType) { + // self serve type received from portal this.selfServeType(inputs.selfServeType); } else { - this.selfServeType(SelfServeTypes.none); + this.selfServeType(SelfServeType.none); } } diff --git a/src/Main.tsx b/src/Main.tsx index 8609b6e03..f5aabafcc 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -79,6 +79,7 @@ import refreshImg from "../images/refresh-cosmos.svg"; import arrowLeftImg from "../images/imgarrowlefticon.svg"; import { KOCommentEnd, KOCommentIfStart } from "./koComment"; import { Spinner, SpinnerSize } from "office-ui-fabric-react"; +import { SelfServeType } from "./SelfServe/SelfServeUtils"; // TODO: Encapsulate and reuse all global variables as environment variables window.authType = AuthType.AAD; @@ -382,7 +383,6 @@ const App: React.FunctionComponent = () => { {/* Explorer Connection - Encryption Token / AAD - End */} {/* Global loader - Start */} - {window.dataExplorer && }
@@ -391,8 +391,10 @@ const App: React.FunctionComponent = () => {

Azure Cosmos DB

-

Welcome to Azure Cosmos DB

-

+

+ Welcome to Azure Cosmos DB +

+
diff --git a/src/SelfServe/Example/SelfServeExample.tsx b/src/SelfServe/Example/SelfServeExample.tsx index 3bca37549..473677ef0 100644 --- a/src/SelfServe/Example/SelfServeExample.tsx +++ b/src/SelfServe/Example/SelfServeExample.tsx @@ -49,13 +49,13 @@ const initializeMaxThroughput = async (): Promise => { - 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' + You can test this self serve UI by using the featureflag '?feature.selfServeType=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. + - role: Indicates to the compiler that UI should be generated from this class. */ @IsDisplayable() /* diff --git a/src/SelfServe/SelfServeComponentAdapter.tsx b/src/SelfServe/SelfServeComponentAdapter.tsx index eb83aebed..711f23c72 100644 --- a/src/SelfServe/SelfServeComponentAdapter.tsx +++ b/src/SelfServe/SelfServeComponentAdapter.tsx @@ -8,23 +8,23 @@ import * as React from "react"; import { ReactAdapter } from "../Bindings/ReactBindingHandler"; import Explorer from "../Explorer/Explorer"; import { Descriptor, SmartUiComponent } from "../Explorer/Controls/SmartUi/SmartUiComponent"; -import { SelfServeTypes } from "./SelfServeUtils"; +import { SelfServeType } from "./SelfServeUtils"; export class SelfServeComponentAdapter implements ReactAdapter { - public parameters: ko.Observable; + public parameters: ko.Observable; public container: Explorer; constructor(container: Explorer) { this.container = container; - this.parameters = ko.observable(Date.now()); + this.parameters = ko.observable(undefined) this.container.selfServeType.subscribe(() => { this.triggerRender(); }); } - public static getDescriptor = async (selfServeType: SelfServeTypes): Promise => { + public static getDescriptor = async (selfServeType: SelfServeType): Promise => { switch (selfServeType) { - case SelfServeTypes.example: { + case SelfServeType.example: { const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample"); return new SelfServeExample.default().toSmartUiDescriptor(); } @@ -33,20 +33,23 @@ export class SelfServeComponentAdapter implements ReactAdapter { } }; - public async renderComponent(): Promise { - const selfServeType = this.container.selfServeType(); - const smartUiDescriptor = await SelfServeComponentAdapter.getDescriptor(selfServeType); - - const element = smartUiDescriptor ? ( + public renderComponent(): JSX.Element { + if (this.container.selfServeType() === SelfServeType.invalid) { + return

Invalid self serve type!

+ } + const smartUiDescriptor = this.parameters() + return smartUiDescriptor ? ( ) : ( -

Invalid self serve type!

+ <> ); - - return element; } private triggerRender() { - window.requestAnimationFrame(() => this.parameters(Date.now())); + window.requestAnimationFrame(async () => { + const selfServeType = this.container.selfServeType(); + const smartUiDescriptor = await SelfServeComponentAdapter.getDescriptor(selfServeType); + this.parameters(smartUiDescriptor) + }); } } diff --git a/src/SelfServe/SelfServeUtils.tsx b/src/SelfServe/SelfServeUtils.tsx index b58701f88..246d6e3c7 100644 --- a/src/SelfServe/SelfServeUtils.tsx +++ b/src/SelfServe/SelfServeUtils.tsx @@ -13,9 +13,12 @@ import { InputType } from "../Explorer/Controls/SmartUi/SmartUiComponent"; -export enum SelfServeTypes { +export enum SelfServeType { + // No self serve type passed, launch explorer none = "none", + // Unsupported self serve type passed as feature flag invalid = "invalid", + // Add your self serve types here example = "example" } diff --git a/test/selfServe/selfServeExample.spec.ts b/test/selfServe/selfServeExample.spec.ts index d4266d7f9..de2e0ebf3 100644 --- a/test/selfServe/selfServeExample.spec.ts +++ b/test/selfServe/selfServeExample.spec.ts @@ -1,16 +1,16 @@ import { Frame } from "puppeteer"; import { TestExplorerParams } from "../testExplorer/TestExplorerParams"; import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils"; -import { SelfServeTypes } from "../../src/SelfServe/SelfServeUtils"; +import { SelfServeType } from "../../src/SelfServe/SelfServeUtils"; jest.setTimeout(300000); let frame: Frame; -describe("Notebook UI tests", () => { - it("Upload, Open and Delete Notebook", async () => { +describe("Self Serve", () => { + it("Launch Self Serve Example", async () => { try { frame = await getTestExplorerFrame( - new Map([[TestExplorerParams.selfServeType, SelfServeTypes.example]]) + new Map([[TestExplorerParams.selfServeType, SelfServeType.example]]) ); await frame.waitForSelector(".ms-Dropdown"); const dropdownLabel = await frame.$eval(".ms-Dropdown-label", element => element.textContent);