From 1d98c83be574875df20d787c8323cb57f4672fb0 Mon Sep 17 00:00:00 2001 From: Srinath Narayanan Date: Wed, 10 Mar 2021 13:55:05 -0800 Subject: [PATCH] Created selfServe landing page (#444) * Portal changes for DedicatedGateway Changes to support creation and deletion of DedicatedGateway resource. Tested locally with various scenarios. * Portal changes for DedicatedGateway. CR feedback * Stylecop changes * created selfServe.html landing page * Removing TODO comments * exposed baselineValues * added getOnSaveNotification * disable UI when onSave is taking place * minro edits * made polling optional * added optional polling * added default * Added portal notifications * merged more changes * minor edits * added label for description * Added correlationids and polling of refresh * Added correlationids and polling of refresh * minor edit * added label tooltip * removed ClassInfo decorator * Added dynamic decription * added info and warninf types for description * more changes to promise retry * promise retry changes * added spinner on selfserve load * compile errors fixed * New changes * added operationstatus link * merged sqlxEdits * undid sqlx changes * added completed notification * passed retryInterval in notif options * more retry changes * more changes * added polling on landing on the page * edits for error display * added keys blade link * added link generation * added link to blade * Modified info and description * fixed format errors * added selfserve contract to output files * addressed PR comments Co-authored-by: Balaji Sridharan Co-authored-by: fnbalaji <75445927+fnbalaji@users.noreply.github.com> --- src/Contracts/ViewModels.ts | 11 ++- .../SettingsComponent.test.tsx.snap | 32 ------- .../Controls/SmartUi/SmartUiComponent.tsx | 6 +- src/Explorer/Explorer.tsx | 24 ----- src/Main.tsx | 35 +++---- src/SelfServe/SelfServe.less | 16 ++++ src/SelfServe/SelfServe.tsx | 92 +++++++++++++++++++ src/SelfServe/SelfServeComponentAdapter.tsx | 56 ----------- .../SelfServeLoadingComponentAdapter.tsx | 25 ----- src/SelfServe/selfServe.html | 13 +++ src/hooks/useKnockoutExplorer.ts | 3 - test/testExplorer/TestExplorer.ts | 10 +- tsconfig.contracts.json | 1 + webpack.config.js | 6 ++ 14 files changed, 160 insertions(+), 170 deletions(-) create mode 100644 src/SelfServe/SelfServe.less create mode 100644 src/SelfServe/SelfServe.tsx delete mode 100644 src/SelfServe/SelfServeComponentAdapter.tsx delete mode 100644 src/SelfServe/SelfServeLoadingComponentAdapter.tsx create mode 100644 src/SelfServe/selfServe.html diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index bdb5116fd..45bf16fc1 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -393,7 +393,16 @@ export interface DataExplorerInputsFrame { isAuthWithresourceToken?: boolean; defaultCollectionThroughput?: CollectionCreationDefaults; flights?: readonly string[]; - selfServeType?: SelfServeType; +} + +export interface SelfServeFrameInputs { + selfServeType: SelfServeType; + databaseAccount: any; + subscriptionId: string; + resourceGroup: string; + authorizationToken: string; + csmEndpoint: string; + flights?: readonly string[]; } export interface CollectionCreationDefaults { diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 794d2beaf..9384cab74 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -1060,14 +1060,6 @@ exports[`SettingsComponent renders 1`] = ` }, "selectedDatabaseId": [Function], "selectedNode": [Function], - "selfServeComponentAdapter": SelfServeComponentAdapter { - "container": [Circular], - "parameters": [Function], - }, - "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter { - "parameters": [Function], - }, - "selfServeType": [Function], "serverId": [Function], "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, @@ -2269,14 +2261,6 @@ exports[`SettingsComponent renders 1`] = ` }, "selectedDatabaseId": [Function], "selectedNode": [Function], - "selfServeComponentAdapter": SelfServeComponentAdapter { - "container": [Circular], - "parameters": [Function], - }, - "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter { - "parameters": [Function], - }, - "selfServeType": [Function], "serverId": [Function], "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, @@ -3491,14 +3475,6 @@ exports[`SettingsComponent renders 1`] = ` }, "selectedDatabaseId": [Function], "selectedNode": [Function], - "selfServeComponentAdapter": SelfServeComponentAdapter { - "container": [Circular], - "parameters": [Function], - }, - "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter { - "parameters": [Function], - }, - "selfServeType": [Function], "serverId": [Function], "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, @@ -4700,14 +4676,6 @@ exports[`SettingsComponent renders 1`] = ` }, "selectedDatabaseId": [Function], "selectedNode": [Function], - "selfServeComponentAdapter": SelfServeComponentAdapter { - "container": [Circular], - "parameters": [Function], - }, - "selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter { - "parameters": [Function], - }, - "selfServeType": [Function], "serverId": [Function], "setInProgressConsoleDataIdToBeDeleted": undefined, "setIsNotificationConsoleExpanded": undefined, diff --git a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx index fbedd7529..1f80464b5 100644 --- a/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx +++ b/src/Explorer/Controls/SmartUi/SmartUiComponent.tsx @@ -127,8 +127,7 @@ export class SmartUiComponent extends React.Component - {this.props.getTranslation(info.messageTKey)} - {` `} + {this.props.getTranslation(info.messageTKey)}{" "} {info.link && ( {this.props.getTranslation(info.link.textTKey)} @@ -168,8 +167,7 @@ export class SmartUiComponent extends React.Component - {this.props.getTranslation(description.textTKey)} - {` `} + {this.props.getTranslation(description.textTKey)}{" "} {description.link && ( {this.props.getTranslation(description.link.textTKey)} diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 41566fe63..7c888358d 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -83,9 +83,6 @@ import { IChoiceGroupProps } from "office-ui-fabric-react"; import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils"; import { SubscriptionType } from "../Contracts/SubscriptionType"; import { appInsights } from "../Shared/appInsights"; -import { SelfServeLoadingComponentAdapter } from "../SelfServe/SelfServeLoadingComponentAdapter"; -import { SelfServeType } from "../SelfServe/SelfServeUtils"; -import { SelfServeComponentAdapter } from "../SelfServe/SelfServeComponentAdapter"; import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel"; @@ -131,7 +128,6 @@ export default class Explorer { public isEnableMongoCapabilityPresent: ko.Computed; public isServerlessEnabled: ko.Computed; public isAccountReady: ko.Observable; - public selfServeType: ko.Observable; public canSaveQueries: ko.Computed; public features: ko.Observable; public serverId: ko.Observable; @@ -159,7 +155,6 @@ export default class Explorer { public selectedNode: ko.Observable; public isRefreshingExplorer: ko.Observable; private resourceTree: ResourceTreeAdapter; - private selfServeComponentAdapter: SelfServeComponentAdapter; // Resource Token public resourceTokenDatabaseId: ko.Observable; @@ -243,7 +238,6 @@ export default class Explorer { // React adapters private commandBarComponentAdapter: CommandBarComponentAdapter; - private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter; private static readonly MaxNbDatabasesToAutoExpand = 5; @@ -287,7 +281,6 @@ export default class Explorer { } }); this.isAccountReady = ko.observable(false); - this.selfServeType = ko.observable(undefined); this._isInitializingNotebooks = false; this.arcadiaToken = ko.observable(); this.arcadiaToken.subscribe((token: string) => { @@ -662,7 +655,6 @@ export default class Explorer { }); this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this); - this.selfServeComponentAdapter = new SelfServeComponentAdapter(this); this.loadQueryPane = new LoadQueryPane({ id: "loadquerypane", @@ -837,7 +829,6 @@ export default class Explorer { }); this.commandBarComponentAdapter = new CommandBarComponentAdapter(this); - this.selfServeLoadingComponentAdapter = new SelfServeLoadingComponentAdapter(); this._initSettings(); @@ -1407,20 +1398,6 @@ export default class Explorer { return false; } - public setSelfServeType(inputs: ViewModels.DataExplorerInputsFrame): void { - 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(SelfServeType.none); - } - } - public configure(inputs: ViewModels.DataExplorerInputsFrame): void { if (inputs != null) { // In development mode, save the iframe message from the portal in session storage. @@ -1446,7 +1423,6 @@ export default class Explorer { this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription ?? false); this.isAuthWithResourceToken(inputs.isAuthWithresourceToken ?? false); this.setFeatureFlagsFromFlights(inputs.flights); - this.setSelfServeType(inputs); updateConfigContext({ BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT, diff --git a/src/Main.tsx b/src/Main.tsx index 1a00f2a09..1d594fa2a 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -105,17 +105,8 @@ const App: React.FunctionComponent = () => { return (
-
-
+
+ {/* Main Command Bar - Start */}
{/* Collections Tree and Tabs - Begin */}
@@ -246,21 +237,17 @@ const App: React.FunctionComponent = () => {
{/* Global loader - Start */} -
-
-
-

- Azure Cosmos DB -

-

- Welcome to Azure Cosmos DB -

- -
+

+ Azure Cosmos DB +

+

+ Welcome to Azure Cosmos DB +

+
{/* Global loader - End */} diff --git a/src/SelfServe/SelfServe.less b/src/SelfServe/SelfServe.less new file mode 100644 index 000000000..b3b2a9f57 --- /dev/null +++ b/src/SelfServe/SelfServe.less @@ -0,0 +1,16 @@ +.selfServeComponentContainer { + text-transform: none; + line-height: 1.28581; + letter-spacing: 0; + font-size: 14px; + font-weight: 400; + color: #182026; + height: 100%; + min-height: 100vh; + width: 100%; + background-color: #FFFFFF; +} + +body { + margin: 0; +} \ No newline at end of file diff --git a/src/SelfServe/SelfServe.tsx b/src/SelfServe/SelfServe.tsx new file mode 100644 index 000000000..143795009 --- /dev/null +++ b/src/SelfServe/SelfServe.tsx @@ -0,0 +1,92 @@ +import * as React from "react"; +import ReactDOM from "react-dom"; +import { sendMessage } from "../Common/MessageHandler"; +import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation"; +import { SelfServeComponent } from "./SelfServeComponent"; +import { SelfServeDescriptor } from "./SelfServeTypes"; +import { SelfServeType } from "./SelfServeUtils"; +import { SelfServeFrameInputs } from "../Contracts/ViewModels"; +import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; +import { configContext, updateConfigContext } from "../ConfigContext"; +import { normalizeArmEndpoint } from "../Common/EnvironmentUtility"; +import { updateUserContext } from "../UserContext"; +import "./SelfServe.less"; +import { Spinner, SpinnerSize } from "office-ui-fabric-react"; +initializeIcons(); + +const getDescriptor = async (selfServeType: SelfServeType): Promise => { + switch (selfServeType) { + case SelfServeType.example: { + const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample"); + return new SelfServeExample.default().toSelfServeDescriptor(); + } + case SelfServeType.sqlx: { + const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX"); + return new SqlX.default().toSelfServeDescriptor(); + } + default: + return undefined; + } +}; + +const renderComponent = (selfServeDescriptor: SelfServeDescriptor): JSX.Element => { + if (!selfServeDescriptor) { + return

Invalid self serve type!

; + } + return ; +}; + +const renderSpinner = (): JSX.Element => { + return ; +}; + +const handleMessage = async (event: MessageEvent): Promise => { + if (isInvalidParentFrameOrigin(event)) { + return; + } + + if (event.data["signature"] !== "pcIframe") { + return; + } + + if (typeof event.data !== "object") { + return; + } + + const inputs = event.data.data.inputs as SelfServeFrameInputs; + if (!inputs) { + return; + } + + const urlSearchParams = new URLSearchParams(window.location.search); + const selfServeTypeText = inputs.selfServeType || urlSearchParams.get("selfServeType"); + const selfServeType = SelfServeType[selfServeTypeText?.toLowerCase() as keyof typeof SelfServeType]; + if ( + !inputs.subscriptionId || + !inputs.resourceGroup || + !inputs.databaseAccount || + !inputs.authorizationToken || + !inputs.csmEndpoint || + !selfServeType + ) { + return; + } + + updateConfigContext({ + ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT), + }); + + updateUserContext({ + authorizationToken: inputs.authorizationToken, + databaseAccount: inputs.databaseAccount, + resourceGroup: inputs.resourceGroup, + subscriptionId: inputs.subscriptionId, + }); + + const descriptor = await getDescriptor(selfServeType); + ReactDOM.render(renderComponent(descriptor), document.getElementById("selfServeContent")); +}; + +ReactDOM.render(renderSpinner(), document.getElementById("selfServeContent")); +window.addEventListener("message", handleMessage, false); +sendMessage("ready"); diff --git a/src/SelfServe/SelfServeComponentAdapter.tsx b/src/SelfServe/SelfServeComponentAdapter.tsx deleted file mode 100644 index 967eea72b..000000000 --- a/src/SelfServe/SelfServeComponentAdapter.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This adapter is responsible to render the React component - * If the component signals a change through the callback passed in the properties, it must render the React component when appropriate - * and update any knockout observables passed from the parent. - */ -import * as ko from "knockout"; -import * as React from "react"; -import { ReactAdapter } from "../Bindings/ReactBindingHandler"; -import Explorer from "../Explorer/Explorer"; -import { SelfServeComponent } from "./SelfServeComponent"; -import { SelfServeDescriptor } from "./SelfServeTypes"; -import { SelfServeType } from "./SelfServeUtils"; - -export class SelfServeComponentAdapter implements ReactAdapter { - public parameters: ko.Observable; - public container: Explorer; - - constructor(container: Explorer) { - this.container = container; - this.parameters = ko.observable(undefined); - this.container.selfServeType.subscribe(() => { - this.triggerRender(); - }); - } - - public static getDescriptor = async (selfServeType: SelfServeType): Promise => { - switch (selfServeType) { - case SelfServeType.example: { - const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample"); - return new SelfServeExample.default().toSelfServeDescriptor(); - } - case SelfServeType.sqlx: { - const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX"); - return new SqlX.default().toSelfServeDescriptor(); - } - default: - return undefined; - } - }; - - public renderComponent(): JSX.Element { - if (this.container.selfServeType() === SelfServeType.invalid) { - return

Invalid self serve type!

; - } - const smartUiDescriptor = this.parameters(); - return smartUiDescriptor ? : <>; - } - - private triggerRender() { - window.requestAnimationFrame(async () => { - const selfServeType = this.container.selfServeType(); - const smartUiDescriptor = await SelfServeComponentAdapter.getDescriptor(selfServeType); - this.parameters(smartUiDescriptor); - }); - } -} diff --git a/src/SelfServe/SelfServeLoadingComponentAdapter.tsx b/src/SelfServe/SelfServeLoadingComponentAdapter.tsx deleted file mode 100644 index a27ae1ece..000000000 --- a/src/SelfServe/SelfServeLoadingComponentAdapter.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/** - * This adapter is responsible to render the React component - * If the component signals a change through the callback passed in the properties, it must render the React component when appropriate - * and update any knockout observables passed from the parent. - */ -import * as ko from "knockout"; -import { Spinner, SpinnerSize } from "office-ui-fabric-react"; -import * as React from "react"; -import { ReactAdapter } from "../Bindings/ReactBindingHandler"; - -export class SelfServeLoadingComponentAdapter implements ReactAdapter { - public parameters: ko.Observable; - - constructor() { - this.parameters = ko.observable(Date.now()); - } - - public renderComponent(): JSX.Element { - return ; - } - - private triggerRender() { - window.requestAnimationFrame(() => this.renderComponent()); - } -} diff --git a/src/SelfServe/selfServe.html b/src/SelfServe/selfServe.html new file mode 100644 index 000000000..4c45d43c1 --- /dev/null +++ b/src/SelfServe/selfServe.html @@ -0,0 +1,13 @@ + + + + + + Self Serve + + + + +
+ + diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 79980ad9c..a3a4b18b8 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -23,7 +23,6 @@ import { getDatabaseAccountKindFromExperience, getDatabaseAccountPropertiesFromMetadata, } from "../Platform/Hosted/HostedUtils"; -import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { updateUserContext } from "../UserContext"; import { listKeys } from "../Utils/arm/generatedClients/2020-04-01/databaseAccounts"; @@ -57,7 +56,6 @@ export function useKnockoutExplorer(platform: Platform, explorerParams: Explorer async function configureHosted() { const win = (window as unknown) as HostedExplorerChildFrame; - explorer.selfServeType(SelfServeType.none); if (win.hostedConfig.authType === AuthType.EncryptedToken) { configureHostedWithEncryptedToken(win.hostedConfig); } else if (win.hostedConfig.authType === AuthType.ResourceToken) { @@ -161,7 +159,6 @@ function configureEmulator() { updateUserContext({ authType: AuthType.MasterKey, }); - explorer.selfServeType(SelfServeType.none); explorer.databaseAccount(emulatorAccount); explorer.isAccountReady(true); } diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts index 32afe8a44..088b84f56 100644 --- a/test/testExplorer/TestExplorer.ts +++ b/test/testExplorer/TestExplorer.ts @@ -127,8 +127,16 @@ const initTestExplorer = async (): Promise => { iframe.name = "explorer"; iframe.classList.add("iframe"); iframe.title = "explorer"; - iframe.src = "explorer.html?platform=Portal&disablePortalInitCache"; + iframe.src = getIframeSrc(selfServeType); document.body.appendChild(iframe); }; +const getIframeSrc = (selfServeType: string): string => { + let iframeSrc = "explorer.html?platform=Portal&disablePortalInitCache"; + if (selfServeType) { + iframeSrc = `selfServe.html?selfServeType=${selfServeType}`; + } + return iframeSrc; +}; + initTestExplorer(); diff --git a/tsconfig.contracts.json b/tsconfig.contracts.json index d3eeda639..58c47dc32 100644 --- a/tsconfig.contracts.json +++ b/tsconfig.contracts.json @@ -10,6 +10,7 @@ "./src/Contracts/ActionContracts.ts", "./src/Contracts/Diagnostics.ts", "./src/Contracts/ExplorerContracts.ts", + "./src/Contracts/SelfServeContracts.ts", "./src/Contracts/Versions.ts" ], } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a2e1c771b..1e4988934 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -166,6 +166,11 @@ module.exports = function (env = {}, argv = {}) { template: "src/connectToGitHub.html", chunks: ["connectToGitHub"], }), + new HtmlWebpackPlugin({ + filename: "selfServe.html", + template: "src/SelfServe/selfServe.html", + chunks: ["selfServe"], + }), new MonacoWebpackPlugin(), new CopyWebpackPlugin({ patterns: [{ from: "DataExplorer.nuspec" }, { from: "web.config" }, { from: "quickstart/*.zip" }], @@ -189,6 +194,7 @@ module.exports = function (env = {}, argv = {}) { terminal: "./src/Terminal/index.ts", notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx", galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx", + selfServe: "./src/SelfServe/SelfServe.tsx", connectToGitHub: "./src/GitHub/GitHubConnector.ts", }, node: {