diff --git a/.eslintignore b/.eslintignore index 052e75fa4..28aee9220 100644 --- a/.eslintignore +++ b/.eslintignore @@ -117,8 +117,6 @@ src/Explorer/OpenActions.ts src/Explorer/OpenActionsStubs.ts src/Explorer/Panes/AddCollectionPane.test.ts src/Explorer/Panes/AddCollectionPane.ts -src/Explorer/Panes/AddDatabasePane.test.ts -src/Explorer/Panes/AddDatabasePane.ts src/Explorer/Panes/BrowseQueriesPane.ts src/Explorer/Panes/CassandraAddCollectionPane.ts src/Explorer/Panes/ContextualPaneBase.ts diff --git a/package-lock.json b/package-lock.json index 5bb7967c1..81ae0372c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "clean-webpack-plugin": "0.1.19", "clipboard-copy": "4.0.1", "copy-webpack-plugin": "6.0.2", - "core-js": "3.5.0", + "core-js": "3.9.1", "crossroads": "0.12.2", "css-element-queries": "1.1.1", "d3": "6.1.1", @@ -8384,9 +8384,9 @@ } }, "node_modules/core-js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz", - "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.1.tgz", + "integrity": "sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -30916,9 +30916,9 @@ } }, "core-js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz", - "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==" + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.1.tgz", + "integrity": "sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==" }, "core-js-compat": { "version": "3.9.1", diff --git a/package.json b/package.json index d22b0c1bb..c34547bef 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "clean-webpack-plugin": "0.1.19", "clipboard-copy": "4.0.1", "copy-webpack-plugin": "6.0.2", - "core-js": "3.5.0", + "core-js": "3.9.1", "crossroads": "0.12.2", "css-element-queries": "1.1.1", "d3": "6.1.1", diff --git a/src/Explorer/Panes/AddDatabasePaneF/Tooltip/index.tsx b/src/Common/Tooltip/index.tsx similarity index 64% rename from src/Explorer/Panes/AddDatabasePaneF/Tooltip/index.tsx rename to src/Common/Tooltip/index.tsx index 8acf790b0..f4a9be6ee 100644 --- a/src/Explorer/Panes/AddDatabasePaneF/Tooltip/index.tsx +++ b/src/Common/Tooltip/index.tsx @@ -1,5 +1,5 @@ import { useId } from "@uifabric/react-hooks"; -import { Icon } from "office-ui-fabric-react"; +import { IButtonStyles, IconButton } from "office-ui-fabric-react"; import { ITooltipHostStyles, TooltipHost } from "office-ui-fabric-react/lib/Tooltip"; import * as React from "react"; @@ -9,13 +9,18 @@ const hostStyles: Partial = { root: { display: "inline-block export interface TooltipProps { children: string; } + +const iconButtonStyles: Partial = { root: { marginBottom: -3 } }; +const iconProps = { iconName: "Info" }; + export const Tooltip: React.FunctionComponent = ({ children }: TooltipProps) => { const tooltipId = useId("tooltip"); + const iconButtonId: string = useId("iconButton"); return ( - + ); diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index b67717c0e..d0210e4ec 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -1,31 +1,31 @@ import * as ko from "knockout"; -import * as PaneComponents from "./Panes/PaneComponents"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent"; -import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; -import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; - +import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; +import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; +import * as PaneComponents from "./Panes/PaneComponents"; +import ConflictsTab from "./Tabs/ConflictsTab"; +import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab"; import DocumentsTab from "./Tabs/DocumentsTab"; -import StoredProcedureTab from "./Tabs/StoredProcedureTab"; -import TriggerTab from "./Tabs/TriggerTab"; -import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab"; -import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2"; -import QueryTab from "./Tabs/QueryTab"; -import QueryTablesTab from "./Tabs/QueryTablesTab"; +import GalleryTab from "./Tabs/GalleryTab"; import GraphTab from "./Tabs/GraphTab"; import MongoShellTab from "./Tabs/MongoShellTab"; -import ConflictsTab from "./Tabs/ConflictsTab"; import NotebookTabV2 from "./Tabs/NotebookV2Tab"; -import TerminalTab from "./Tabs/TerminalTab"; -import GalleryTab from "./Tabs/GalleryTab"; import NotebookViewerTab from "./Tabs/NotebookViewerTab"; -import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab"; +import QueryTab from "./Tabs/QueryTab"; +import QueryTablesTab from "./Tabs/QueryTablesTab"; +import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2"; +import StoredProcedureTab from "./Tabs/StoredProcedureTab"; import TabsManagerTemplate from "./Tabs/TabsManager.html"; +import TerminalTab from "./Tabs/TerminalTab"; +import TriggerTab from "./Tabs/TriggerTab"; +import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab"; + ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("new-vertex-form", NewVertexComponent); @@ -59,7 +59,6 @@ ko.components.register("tabs-manager", { template: TabsManagerTemplate }); ].forEach(({ component: { name, template } }) => ko.components.register(name, { template })); // Panes -ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register( "delete-collection-confirmation-pane", diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 69b0adbfd..2b3da2ede 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -49,8 +49,7 @@ import { NotebookContentItem, NotebookContentItemType } from "./Notebook/Noteboo import { NotebookUtil } from "./Notebook/NotebookUtil"; import AddCollectionPane from "./Panes/AddCollectionPane"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; -import AddDatabasePane from "./Panes/AddDatabasePane"; -import { AddDatabasePaneF } from "./Panes/AddDatabasePaneF"; +import { AddDatabasePane } from "./Panes/AddDatabasePane"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; @@ -203,7 +202,6 @@ export default class Explorer { public tabsManager: TabsManager; // Contextual panes - public addDatabasePane: AddDatabasePane; public addCollectionPane: AddCollectionPane; public deleteCollectionConfirmationPane: DeleteCollectionConfirmationPane; public deleteDatabaseConfirmationPane: DeleteDatabaseConfirmationPane; @@ -565,13 +563,6 @@ export default class Explorer { return this.databases().filter((database: ViewModels.Database) => !this._isSystemDatabasePredicate(database)); }); - this.addDatabasePane = new AddDatabasePane({ - id: "adddatabasepane", - visible: ko.observable(false), - - container: this, - }); - this.addCollectionPane = new AddCollectionPane({ isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()), id: "addcollectionpane", @@ -711,7 +702,6 @@ export default class Explorer { this.tabsManager = new TabsManager(); this._panes = [ - this.addDatabasePane, this.addCollectionPane, this.deleteCollectionConfirmationPane, this.deleteDatabaseConfirmationPane, @@ -732,7 +722,7 @@ export default class Explorer { this.stringInputPane, this.setupNotebooksPane, ]; - this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); + //this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.isTabsContentExpanded = ko.observable(false); document.addEventListener( @@ -2495,7 +2485,7 @@ export default class Explorer { public openAddDatabasePane(): void { this.openSidePanel( "Add Database", - + ); } } diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 2ccc47285..c630f861e 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -286,7 +286,6 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { iconSrc: AddDatabaseIcon, iconAlt: label, onCommandClick: () => { - // container.addDatabasePane.open(); container.openAddDatabasePane(); document.getElementById("linkAddDatabase").focus(); }, diff --git a/src/Explorer/Panes/AddDatabasePane.html b/src/Explorer/Panes/AddDatabasePane.html deleted file mode 100644 index 0166ab7cb..000000000 --- a/src/Explorer/Panes/AddDatabasePane.html +++ /dev/null @@ -1,174 +0,0 @@ -
-
-
- -
-
- -
-
- - -
- -
- -
-
- - diff --git a/src/Explorer/Panes/AddDatabasePane.test.ts b/src/Explorer/Panes/AddDatabasePane.test.ts deleted file mode 100644 index d4bb6f19f..000000000 --- a/src/Explorer/Panes/AddDatabasePane.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as Constants from "../../Common/Constants"; -import { SubscriptionType } from "../../Contracts/SubscriptionType"; -import Explorer from "../Explorer"; -import AddDatabasePane from "./AddDatabasePane"; -import { DatabaseAccount } from "../../Contracts/DataModels"; - -describe("Add Database Pane", () => { - describe("getSharedThroughputDefault()", () => { - let explorer: Explorer; - const mockDatabaseAccount: DatabaseAccount = { - id: "mock", - kind: "DocumentDB", - location: "", - name: "mock", - properties: { - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "", - enableFreeTier: false, - }, - type: undefined, - tags: [], - }; - - const mockFreeTierDatabaseAccount: DatabaseAccount = { - id: "mock", - kind: "DocumentDB", - location: "", - name: "mock", - properties: { - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "", - enableFreeTier: true, - }, - type: undefined, - tags: [], - }; - - beforeEach(() => { - explorer = new Explorer(); - }); - - it("should be true if subscription type is Benefits", () => { - explorer.subscriptionType(SubscriptionType.Benefits); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); - }); - - it("should be false if subscription type is EA", () => { - explorer.subscriptionType(SubscriptionType.EA); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.getSharedThroughputDefault()).toBe(false); - }); - - it("should be true if subscription type is Free", () => { - explorer.subscriptionType(SubscriptionType.Free); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); - }); - - it("should be true if subscription type is Internal", () => { - explorer.subscriptionType(SubscriptionType.Internal); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); - }); - - it("should be true if subscription type is PAYG", () => { - explorer.subscriptionType(SubscriptionType.PAYG); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); - }); - - it("should display free tier text in upsell messaging", () => { - explorer.databaseAccount(mockFreeTierDatabaseAccount); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.isFreeTierAccount()).toBe(true); - expect(addDatabasePane.upsellMessage()).toContain("With free tier"); - expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation); - expect(addDatabasePane.upsellAnchorText()).toBe("Learn more"); - }); - - it("should display standard texr in upsell messaging", () => { - explorer.databaseAccount(mockDatabaseAccount); - const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; - expect(addDatabasePane.isFreeTierAccount()).toBe(false); - expect(addDatabasePane.upsellMessage()).toContain("Start at"); - expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing); - expect(addDatabasePane.upsellAnchorText()).toBe("More details"); - }); - }); -}); diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts deleted file mode 100644 index b8336b941..000000000 --- a/src/Explorer/Panes/AddDatabasePane.ts +++ /dev/null @@ -1,469 +0,0 @@ -import * as ko from "knockout"; -import * as Constants from "../../Common/Constants"; -import { createDatabase } from "../../Common/dataAccess/createDatabase"; -import editable from "../../Common/EditableUtility"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; -import { configContext, Platform } from "../../ConfigContext"; -import * as DataModels from "../../Contracts/DataModels"; -import { SubscriptionType } from "../../Contracts/SubscriptionType"; -import * as ViewModels from "../../Contracts/ViewModels"; -import * as SharedConstants from "../../Shared/Constants"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../UserContext"; -import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; -import * as PricingUtils from "../../Utils/PricingUtils"; -import { ContextualPaneBase } from "./ContextualPaneBase"; - -export default class AddDatabasePane extends ContextualPaneBase { - public defaultExperience: ko.Computed; - public databaseIdLabel: ko.Computed; - public databaseIdPlaceHolder: ko.Computed; - public databaseId: ko.Observable; - public databaseIdTooltipText: ko.Computed; - public databaseLevelThroughputTooltipText: ko.Computed; - public databaseCreateNewShared: ko.Observable; - public formErrorsDetails: ko.Observable; - public throughput: ViewModels.Editable; - public maxThroughputRU: ko.Observable; - public minThroughputRU: ko.Observable; - public maxThroughputRUText: ko.PureComputed; - public throughputRangeText: ko.Computed; - public throughputSpendAckText: ko.Observable; - public throughputSpendAck: ko.Observable; - public throughputSpendAckVisible: ko.Computed; - public requestUnitsUsageCost: ko.Computed; - public canRequestSupport: ko.PureComputed; - public costsVisible: ko.PureComputed; - public upsellMessage: ko.PureComputed; - public upsellMessageAriaLabel: ko.PureComputed; - public upsellAnchorUrl: ko.PureComputed; - public upsellAnchorText: ko.PureComputed; - public isAutoPilotSelected: ko.Observable; - public maxAutoPilotThroughputSet: ko.Observable; - public autoPilotUsageCost: ko.Computed; - public canExceedMaximumValue: ko.PureComputed; - public ruToolTipText: ko.Computed; - public freeTierExceedThroughputTooltip: ko.Computed; - public isFreeTierAccount: ko.Computed; - public canConfigureThroughput: ko.PureComputed; - public showUpsellMessage: ko.PureComputed; - - constructor(options: ViewModels.PaneOptions) { - super(options); - this.title((this.container && this.container.addDatabaseText()) || "New Database"); - this.databaseId = ko.observable(); - this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); - this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); - - this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); - - // TODO 388844: get defaults from parent frame - this.databaseCreateNewShared = ko.observable(this.getSharedThroughputDefault()); - - this.container.subscriptionType && - this.container.subscriptionType.subscribe((subscriptionType) => { - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - }); - - this.databaseIdLabel = ko.computed(() => - this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id" - ); - - this.databaseIdPlaceHolder = ko.computed(() => - this.container.isPreferredApiCassandra() ? "Type a new keyspace id" : "Type a new database id" - ); - - this.databaseIdTooltipText = ko.computed(() => { - const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); - return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${ - isCassandraAccount ? "tables" : "collections" - }`; - }); - this.databaseLevelThroughputTooltipText = ko.computed(() => { - const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); - const databaseLabel: string = isCassandraAccount ? "keyspace" : "database"; - const collectionsLabel: string = isCassandraAccount ? "tables" : "collections"; - return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`; - }); - - this.throughput = editable.observable(); - this.maxThroughputRU = ko.observable(); - this.minThroughputRU = ko.observable(); - this.throughputSpendAckText = ko.observable(); - this.throughputSpendAck = ko.observable(false); - this.isAutoPilotSelected = ko.observable(false); - this.maxAutoPilotThroughputSet = ko.observable(AutoPilotUtils.minAutoPilotThroughput); - this.autoPilotUsageCost = ko.pureComputed(() => { - const autoPilot = this._isAutoPilotSelectedAndWhatTier(); - if (!autoPilot) { - return ""; - } - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */); - }); - this.throughputRangeText = ko.pureComputed(() => { - if (this.isAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; - }); - - this.requestUnitsUsageCost = ko.computed(() => { - const offerThroughput: number = this.throughput(); - if ( - offerThroughput < this.minThroughputRU() || - (offerThroughput > this.maxThroughputRU() && !this.canExceedMaximumValue()) - ) { - return ""; - } - - const account = this.container.databaseAccount(); - if (!account) { - return ""; - } - - const regions = - (account && - account.properties && - account.properties.readLocations && - account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - - let estimatedSpendAcknowledge: string; - let estimatedSpend: string; - if (!this.isAutoPilotSelected()) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - offerThroughput, - userContext.portalEnv, - regions, - multimaster - ); - estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - offerThroughput, - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.maxAutoPilotThroughputSet(), - userContext.portalEnv, - regions, - multimaster - ); - estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - this.maxAutoPilotThroughputSet(), - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - } - // TODO: change throughputSpendAckText to be a computed value, instead of having this side effect - this.throughputSpendAckText(estimatedSpendAcknowledge); - return estimatedSpend; - }); - - this.canRequestSupport = ko.pureComputed(() => { - if ( - configContext.platform !== Platform.Emulator && - !userContext.isTryCosmosDBSubscription && - configContext.platform !== Platform.Portal - ) { - const offerThroughput: number = this.throughput(); - return offerThroughput <= 100000; - } - - return false; - }); - - this.isFreeTierAccount = ko.computed(() => { - const databaseAccount = this.container && this.container.databaseAccount && this.container.databaseAccount(); - const isFreeTierAccount = - databaseAccount && databaseAccount.properties && databaseAccount.properties.enableFreeTier; - return isFreeTierAccount; - }); - - this.showUpsellMessage = ko.pureComputed(() => { - if (this.container.isServerlessEnabled()) { - return false; - } - - if (this.isFreeTierAccount()) { - return this.databaseCreateNewShared(); - } - - return true; - }); - - this.maxThroughputRUText = ko.pureComputed(() => { - return this.maxThroughputRU().toLocaleString(); - }); - - this.costsVisible = ko.pureComputed(() => { - return configContext.platform !== Platform.Emulator; - }); - - this.throughputSpendAckVisible = ko.pureComputed(() => { - const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; - if (this.isAutoPilotSelected()) { - return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - } - - const selectedThroughput: number = this.throughput(); - const maxRU: number = this.maxThroughputRU && this.maxThroughputRU(); - - const isMaxRUGreaterThanDefault: boolean = maxRU > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - const isThroughputSetGreaterThanDefault: boolean = - selectedThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - - if (this.canExceedMaximumValue()) { - return isThroughputSetGreaterThanDefault; - } - - return isThroughputSetGreaterThanDefault && isMaxRUGreaterThanDefault; - }); - - this.databaseCreateNewShared.subscribe((useShared: boolean) => { - this._updateThroughputLimitByDatabase(); - }); - - this.resetData(); - this.container.flight.subscribe(() => { - this.resetData(); - }); - - this.freeTierExceedThroughputTooltip = ko.pureComputed(() => - this.isFreeTierAccount() && !this.container.isFirstResourceCreated() - ? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s." - : "" - ); - - this.upsellMessage = ko.pureComputed(() => { - return PricingUtils.getUpsellMessage( - userContext.portalEnv, - this.isFreeTierAccount(), - this.container.isFirstResourceCreated(), - this.container.defaultExperience(), - false - ); - }); - - this.upsellMessageAriaLabel = ko.pureComputed(() => { - return `${this.upsellMessage()}. Click ${this.isFreeTierAccount() ? "to learn more" : "for more details"}`; - }); - - this.upsellAnchorUrl = ko.pureComputed(() => { - return this.isFreeTierAccount() ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing; - }); - - this.upsellAnchorText = ko.pureComputed(() => { - return this.isFreeTierAccount() ? "Learn more" : "More details"; - }); - } - - public onMoreDetailsKeyPress = (source: any, event: KeyboardEvent): boolean => { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.showErrorDetails(); - return false; - } - return true; - }; - - public open() { - super.open(); - this.resetData(); - const addDatabasePaneOpenMessage = { - subscriptionType: SubscriptionType[this.container.subscriptionType()], - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - throughput: this.throughput(), - flight: this.container.flight(), - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - const focusElement = document.getElementById("database-id"); - focusElement && focusElement.focus(); - TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage); - } - - public submit() { - if (!this._isValid()) { - return; - } - - const offerThroughput: number = this._computeOfferThroughput(); - - const addDatabasePaneStartMessage = { - database: ko.toJS({ - id: this.databaseId(), - shared: this.databaseCreateNewShared(), - }), - offerThroughput, - subscriptionType: SubscriptionType[this.container.subscriptionType()], - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - flight: this.container.flight(), - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage); - this.formErrors(""); - this.isExecuting(true); - - const createDatabaseParams: DataModels.CreateDatabaseParams = { - databaseId: addDatabasePaneStartMessage.database.id, - databaseLevelThroughput: addDatabasePaneStartMessage.database.shared, - }; - - if (this.isAutoPilotSelected()) { - createDatabaseParams.autoPilotMaxThroughput = this.maxAutoPilotThroughputSet(); - } else { - createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput; - } - - createDatabase(createDatabaseParams).then( - (database: DataModels.Database) => { - this._onCreateDatabaseSuccess(offerThroughput, startKey); - }, - (error: any) => { - this._onCreateDatabaseFailure(error, offerThroughput, startKey); - } - ); - } - - public resetData() { - this.databaseId(""); - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput); - this._updateThroughputLimitByDatabase(); - this.throughputSpendAck(false); - super.resetData(); - } - - public getSharedThroughputDefault(): boolean { - const subscriptionType = this.container.subscriptionType && this.container.subscriptionType(); - - if (subscriptionType === SubscriptionType.EA || this.container.isServerlessEnabled()) { - return false; - } - - return true; - } - - private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void { - this.isExecuting(false); - this.close(); - this.container.refreshAllDatabases(); - const addDatabasePaneSuccessMessage = { - database: ko.toJS({ - id: this.databaseId(), - shared: this.databaseCreateNewShared(), - }), - offerThroughput: offerThroughput, - subscriptionType: SubscriptionType[this.container.subscriptionType()], - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - flight: this.container.flight(), - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey); - this.resetData(); - } - - private _onCreateDatabaseFailure(error: any, offerThroughput: number, startKey: number): void { - this.isExecuting(false); - const errorMessage = getErrorMessage(error); - this.formErrors(errorMessage); - this.formErrorsDetails(errorMessage); - const addDatabasePaneFailedMessage = { - database: ko.toJS({ - id: this.databaseId(), - shared: this.databaseCreateNewShared(), - }), - offerThroughput: offerThroughput, - subscriptionType: SubscriptionType[this.container.subscriptionType()], - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - flight: this.container.flight(), - }, - dataExplorerArea: Constants.Areas.ContextualPane, - error: errorMessage, - errorStack: getErrorStack(error), - }; - TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey); - } - - private _getThroughput(): number { - const throughput: number = this.throughput(); - return isNaN(throughput) ? 0 : Number(throughput); - } - - private _computeOfferThroughput(): number { - if (!this.canConfigureThroughput()) { - return undefined; - } - - if (this.isAutoPilotSelected()) { - return undefined; - } - - return this._getThroughput(); - } - - private _isValid(): boolean { - // TODO add feature flag that disables validation for customers with custom accounts - if (this.isAutoPilotSelected()) { - const autoPilot = this._isAutoPilotSelectedAndWhatTier(); - if ( - !autoPilot || - !autoPilot.maxThroughput || - !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) - ) { - this.formErrors( - `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` - ); - return false; - } - } - const throughput = this._getThroughput(); - - if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) { - this.formErrors(`Please acknowledge the estimated daily spend.`); - return false; - } - - const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; - - if ( - this.isAutoPilotSelected() && - autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !this.throughputSpendAck() - ) { - this.formErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - return true; - } - - private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings { - if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) { - return { - maxThroughput: this.maxAutoPilotThroughputSet() * 1, - }; - } - return undefined; - } - - private _updateThroughputLimitByDatabase() { - const throughputDefaults = this.container.collectionCreationDefaults.throughput; - this.throughput(throughputDefaults.shared); - this.maxThroughputRU(throughputDefaults.unlimitedmax); - this.minThroughputRU(throughputDefaults.unlimitedmin); - } -} diff --git a/src/Explorer/Panes/AddDatabasePaneF/index.tsx b/src/Explorer/Panes/AddDatabasePane/index.tsx similarity index 77% rename from src/Explorer/Panes/AddDatabasePaneF/index.tsx rename to src/Explorer/Panes/AddDatabasePane/index.tsx index 843745091..23f34a825 100644 --- a/src/Explorer/Panes/AddDatabasePaneF/index.tsx +++ b/src/Explorer/Panes/AddDatabasePane/index.tsx @@ -1,8 +1,9 @@ -import { Checkbox, TextField } from "office-ui-fabric-react"; +import { Checkbox, Text, TextField } from "office-ui-fabric-react"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as Constants from "../../../Common/Constants"; import { createDatabase } from "../../../Common/dataAccess/createDatabase"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; +import { Tooltip } from "../../../Common/Tooltip"; import { configContext, Platform } from "../../../ConfigContext"; import * as DataModels from "../../../Contracts/DataModels"; import { SubscriptionType } from "../../../Contracts/SubscriptionType"; @@ -16,7 +17,6 @@ import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput" import Explorer from "../../Explorer"; import { GenericRightPaneComponent, GenericRightPaneProps } from "../GenericRightPaneComponent"; import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; -import { Tooltip } from "./Tooltip"; export interface AddDatabasePaneProps { explorer: Explorer; @@ -24,7 +24,7 @@ export interface AddDatabasePaneProps { openNotificationConsole: () => void; } -export const AddDatabasePaneF: FunctionComponent = ({ +export const AddDatabasePane: FunctionComponent = ({ explorer: container, closePanel, openNotificationConsole, @@ -67,82 +67,11 @@ export const AddDatabasePaneF: FunctionComponent = ({ const [throughput, setThroughput] = useState(throughputDefaults.shared); const [maxThroughputRU, setMaxThroughputRU] = useState(throughputDefaults.unlimitedmax); - const [minThroughputRU, setMinThroughputRU] = useState(throughputDefaults.unlimitedmin); const maxThroughputRUText: string = maxThroughputRU?.toLocaleString(); const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(container.isAutoscaleDefaultEnabled()); - const [throughputRangeText, setThroughputRangeText] = useState( - isAutoPilotSelected - ? AutoPilotUtils.getAutoPilotHeaderText() - : `Throughput (${minThroughputRU?.toLocaleString()} - ${maxThroughputRU?.toLocaleString()} RU/s)` - ); - const [throughputSpendAckText, setThroughputSpendAckText] = useState(); + const [throughputSpendAck, setThroughputSpendAck] = useState(false); - const canExceedMaximumValue: boolean = container.canExceedMaximumValue(); - const [throughputSpendAckVisible, setThroughputSpendAckVisible] = useState(() => { - if (isAutoPilotSelected) { - const autoscaleThroughput = maxAutoPilotThroughputSet * 1; - return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - } - const selectedThroughput: number = throughput; - const maxRU: number = maxThroughputRU && maxThroughputRU; - - const isMaxRUGreaterThanDefault: boolean = maxRU > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - const isThroughputSetGreaterThanDefault: boolean = - selectedThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - - if (canExceedMaximumValue) { - return isThroughputSetGreaterThanDefault; - } - - return isThroughputSetGreaterThanDefault && isMaxRUGreaterThanDefault; - }); - const [requestUnitsUsageCost, setRequestUnitsUsageCost] = useState(() => { - const offerThroughput: number = throughput; - if (offerThroughput < minThroughputRU || (offerThroughput > maxThroughputRU && !canExceedMaximumValue)) { - return ""; - } - - const account = userContext.databaseAccount; - if (!account) { - return ""; - } - - const regions = - (account && account.properties && account.properties.readLocations && account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - - let estimatedSpendAcknowledge: string; - let estimatedSpend: string; - if (!isAutoPilotSelected) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, userContext.portalEnv, regions, multimaster); - estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - offerThroughput, - userContext.portalEnv, - regions, - multimaster, - isAutoPilotSelected - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - maxAutoPilotThroughputSet, - userContext.portalEnv, - regions, - multimaster - ); - estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( - maxAutoPilotThroughputSet, - userContext.portalEnv, - regions, - multimaster, - isAutoPilotSelected - ); - } - // TODO: change throughputSpendAckText to be a computed value, instead of having this side effect - setThroughputSpendAckText(estimatedSpendAcknowledge); - return estimatedSpend; - }); const canRequestSupport = () => { if ( configContext.platform !== Platform.Emulator && @@ -155,7 +84,6 @@ export const AddDatabasePaneF: FunctionComponent = ({ return false; }; - const [costsVisible, setCostsVisible] = useState(configContext.platform !== Platform.Emulator); const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; const upsellMessage: string = PricingUtils.getUpsellMessage( userContext.portalEnv, @@ -164,9 +92,7 @@ export const AddDatabasePaneF: FunctionComponent = ({ userContext.defaultExperience, false ); - const upsellMessageAriaLabel: string = `${upsellMessage}. Click ${ - isFreeTierAccount ? "to learn more" : "for more details" - }`; + const upsellAnchorUrl: string = isFreeTierAccount ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing; const upsellAnchorText: string = isFreeTierAccount ? "Learn more" : "More details"; @@ -174,15 +100,6 @@ export const AddDatabasePaneF: FunctionComponent = ({ AutoPilotUtils.minAutoPilotThroughput ); - const [autoPilotUsageCost, setAutoPilotUsageCost] = useState(() => { - const autoPilot = _isAutoPilotSelectedAndWhatTier(); - if (!autoPilot) { - return ""; - } - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */); - }); - const ruToolTipText: string = PricingUtils.getRuToolTipText(); - const canConfigureThroughput = !container.isServerlessEnabled(); const showUpsellMessage = () => { if (container.isServerlessEnabled()) { @@ -202,7 +119,6 @@ export const AddDatabasePaneF: FunctionComponent = ({ const throughputDefaults = container.collectionCreationDefaults.throughput; setThroughput(throughputDefaults.shared); setMaxThroughputRU(throughputDefaults.unlimitedmax); - setMinThroughputRU(throughputDefaults.unlimitedmin); }; useEffect(() => { _updateThroughputLimitByDatabase(); @@ -412,7 +328,7 @@ export const AddDatabasePaneF: FunctionComponent = ({

* - {databaseIdLabel} + {databaseIdLabel} {databaseIdTooltipText}

@@ -439,16 +355,14 @@ export const AddDatabasePaneF: FunctionComponent = ({ setDatabaseCreateNewShared(!databaseCreateNewShared)} - /> + />{" "} {databaseLevelThroughputTooltipText}
{databaseCreateNewShared && ( diff --git a/src/Explorer/Panes/PaneComponents.ts b/src/Explorer/Panes/PaneComponents.ts index 6bd6698b6..d71e12905 100644 --- a/src/Explorer/Panes/PaneComponents.ts +++ b/src/Explorer/Panes/PaneComponents.ts @@ -1,24 +1,23 @@ -import AddDatabasePaneTemplate from "./AddDatabasePane.html"; import AddCollectionPaneTemplate from "./AddCollectionPane.html"; +import BrowseQueriesPaneTemplate from "./BrowseQueriesPane.html"; +import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html"; import DeleteCollectionConfirmationPaneTemplate from "./DeleteCollectionConfirmationPane.html"; import DeleteDatabaseConfirmationPaneTemplate from "./DeleteDatabaseConfirmationPane.html"; +import ExecuteSprocParamsPaneTemplate from "./ExecuteSprocParamsPane.html"; +import GitHubReposPaneTemplate from "./GitHubReposPane.html"; import GraphNewVertexPaneTemplate from "./GraphNewVertexPane.html"; import GraphStylingPaneTemplate from "./GraphStylingPane.html"; -import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; -import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html"; -import TableColumnOptionsPaneTemplate from "./Tables/TableColumnOptionsPane.html"; -import TableQuerySelectPaneTemplate from "./Tables/TableQuerySelectPane.html"; -import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html"; -import SettingsPaneTemplate from "./SettingsPane.html"; -import ExecuteSprocParamsPaneTemplate from "./ExecuteSprocParamsPane.html"; -import UploadItemsPaneTemplate from "./UploadItemsPane.html"; import LoadQueryPaneTemplate from "./LoadQueryPane.html"; import SaveQueryPaneTemplate from "./SaveQueryPane.html"; -import BrowseQueriesPaneTemplate from "./BrowseQueriesPane.html"; -import UploadFilePaneTemplate from "./UploadFilePane.html"; -import StringInputPaneTemplate from "./StringInputPane.html"; +import SettingsPaneTemplate from "./SettingsPane.html"; import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html"; -import GitHubReposPaneTemplate from "./GitHubReposPane.html"; +import StringInputPaneTemplate from "./StringInputPane.html"; +import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; +import TableColumnOptionsPaneTemplate from "./Tables/TableColumnOptionsPane.html"; +import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html"; +import TableQuerySelectPaneTemplate from "./Tables/TableQuerySelectPane.html"; +import UploadFilePaneTemplate from "./UploadFilePane.html"; +import UploadItemsPaneTemplate from "./UploadItemsPane.html"; export class PaneComponent { constructor(data: any) { @@ -26,14 +25,6 @@ export class PaneComponent { } } -export class AddDatabasePaneComponent { - constructor() { - return { - viewModel: PaneComponent, - template: AddDatabasePaneTemplate, - }; - } -} export class AddCollectionPaneComponent { constructor() { diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index dd8bbb901..f966499ca 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -291,7 +291,7 @@ export class SplashScreen extends React.Component { iconSrc: AddDatabaseIcon, title: this.container.addDatabaseText(), description: null, - onClick: () => this.container.addDatabasePane.open(), + onClick: () => this.container.openAddDatabasePane(), }); } diff --git a/src/Main.tsx b/src/Main.tsx index 863efc532..dd9780c05 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -241,7 +241,6 @@ const App: React.FunctionComponent = () => { isConsoleExpanded={isNotificationConsoleExpanded} />
-