diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx
new file mode 100644
index 000000000..4206eb723
--- /dev/null
+++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx
@@ -0,0 +1,17 @@
+import { shallow } from "enzyme";
+import React from "react";
+import Explorer from "../../Explorer";
+import { AddDatabasePanel } from "./AddDatabasePanel";
+
+const props = {
+ explorer: new Explorer(),
+ closePanel: (): void => undefined,
+ openNotificationConsole: (): void => undefined,
+};
+
+describe("AddDatabasePane Pane", () => {
+ it("should render Default properly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
new file mode 100644
index 000000000..5911b8f80
--- /dev/null
+++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
@@ -0,0 +1,341 @@
+import { Checkbox, Text, TextField } from "@fluentui/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 { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
+import { configContext, Platform } from "../../../ConfigContext";
+import * as DataModels from "../../../Contracts/DataModels";
+import { SubscriptionType } from "../../../Contracts/SubscriptionType";
+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 { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
+import Explorer from "../../Explorer";
+import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
+import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
+
+export interface AddDatabasePaneProps {
+ explorer: Explorer;
+ closePanel: () => void;
+ openNotificationConsole: () => void;
+}
+
+export const AddDatabasePanel: FunctionComponent = ({
+ explorer: container,
+ closePanel,
+ openNotificationConsole,
+}: AddDatabasePaneProps) => {
+ const { subscriptionType } = userContext;
+ const getSharedThroughputDefault = !(subscriptionType === SubscriptionType.EA || container.isServerlessEnabled());
+ const _isAutoPilotSelectedAndWhatTier = (): DataModels.AutoPilotCreationSettings => {
+ if (isAutoPilotSelected && maxAutoPilotThroughputSet) {
+ return {
+ maxThroughput: maxAutoPilotThroughputSet * 1,
+ };
+ }
+ return undefined;
+ };
+
+ const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
+ const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
+ const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
+ const databaseIdLabel: string = isCassandraAccount ? "Keyspace id" : "Database id";
+ const databaseIdPlaceHolder: string = isCassandraAccount ? "Type a new keyspace id" : "Type a new database id";
+
+ const [databaseId, setDatabaseId] = useState("");
+ const databaseIdTooltipText = `A ${
+ isCassandraAccount ? "keyspace" : "database"
+ } is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
+
+ const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
+ const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState(getSharedThroughputDefault);
+ const [formErrorsDetails, setFormErrorsDetails] = useState();
+ const [formErrors, setFormErrors] = useState("");
+
+ const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(container.isAutoscaleDefaultEnabled());
+
+ const throughputDefaults = container.collectionCreationDefaults.throughput;
+ const [throughput, setThroughput] = useState(
+ isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared
+ );
+
+ const [throughputSpendAck, setThroughputSpendAck] = useState(false);
+
+ const canRequestSupport = () => {
+ if (
+ configContext.platform !== Platform.Emulator &&
+ !userContext.isTryCosmosDBSubscription &&
+ configContext.platform !== Platform.Portal
+ ) {
+ const offerThroughput: number = throughput;
+ return offerThroughput <= 100000;
+ }
+
+ return false;
+ };
+ const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
+ const upsellMessage: string = PricingUtils.getUpsellMessage(
+ userContext.portalEnv,
+ isFreeTierAccount,
+ container.isFirstResourceCreated(),
+ false
+ );
+
+ const upsellAnchorUrl: string = isFreeTierAccount ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
+
+ const upsellAnchorText: string = isFreeTierAccount ? "Learn more" : "More details";
+ const maxAutoPilotThroughputSet = AutoPilotUtils.minAutoPilotThroughput;
+
+ const canConfigureThroughput = !container.isServerlessEnabled();
+ const showUpsellMessage = () => {
+ if (container.isServerlessEnabled()) {
+ return false;
+ }
+
+ if (isFreeTierAccount) {
+ return databaseCreateNewShared;
+ }
+
+ return true;
+ };
+ const [isExecuting, setIsExecuting] = useState(false);
+
+ useEffect(() => {
+ setDatabaseCreateNewShared(getSharedThroughputDefault);
+ }, [subscriptionType]);
+
+ const addDatabasePaneMessage = {
+ database: {
+ id: databaseId,
+ shared: databaseCreateNewShared,
+ },
+ subscriptionType: SubscriptionType[subscriptionType],
+ subscriptionQuotaId: userContext.quotaId,
+ defaultsCheck: {
+ flight: userContext.addCollectionFlight,
+ },
+ dataExplorerArea: Constants.Areas.ContextualPane,
+ };
+
+ useEffect(() => {
+ const addDatabasePaneOpenMessage = {
+ subscriptionType: SubscriptionType[subscriptionType],
+ subscriptionQuotaId: userContext.quotaId,
+ defaultsCheck: {
+ throughput: throughput,
+ flight: userContext.addCollectionFlight,
+ },
+ dataExplorerArea: Constants.Areas.ContextualPane,
+ };
+ TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
+ }, []);
+
+ const onSubmit = () => {
+ if (!_isValid()) {
+ return;
+ }
+
+ const offerThroughput: number = _computeOfferThroughput();
+
+ const addDatabasePaneStartMessage = {
+ ...addDatabasePaneMessage,
+ offerThroughput,
+ };
+ const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
+ setFormErrors("");
+ setIsExecuting(true);
+
+ const createDatabaseParams: DataModels.CreateDatabaseParams = {
+ databaseId: addDatabasePaneStartMessage.database.id,
+ databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
+ };
+ if (isAutoPilotSelected) {
+ createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.offerThroughput;
+ } else {
+ createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
+ }
+
+ createDatabase(createDatabaseParams).then(
+ () => {
+ _onCreateDatabaseSuccess(offerThroughput, startKey);
+ },
+ (error: string) => {
+ _onCreateDatabaseFailure(error, offerThroughput, startKey);
+ }
+ );
+ };
+
+ const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
+ setIsExecuting(false);
+ closePanel();
+ container.refreshAllDatabases();
+ const addDatabasePaneSuccessMessage = {
+ ...addDatabasePaneMessage,
+ offerThroughput,
+ };
+ TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey);
+ };
+
+ const _onCreateDatabaseFailure = (error: string, offerThroughput: number, startKey: number): void => {
+ setIsExecuting(false);
+ const errorMessage = getErrorMessage(error);
+ setFormErrors(errorMessage);
+ setFormErrorsDetails(errorMessage);
+ const addDatabasePaneFailedMessage = {
+ ...addDatabasePaneMessage,
+ offerThroughput,
+ error: errorMessage,
+ errorStack: getErrorStack(error),
+ };
+ TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
+ };
+
+ const _getThroughput = (): number => {
+ return isNaN(throughput) ? 0 : Number(throughput);
+ };
+
+ const _computeOfferThroughput = (): number => {
+ if (!canConfigureThroughput) {
+ return undefined;
+ }
+
+ return _getThroughput();
+ };
+
+ const _isValid = (): boolean => {
+ // TODO add feature flag that disables validation for customers with custom accounts
+ if (isAutoPilotSelected) {
+ const autoPilot = _isAutoPilotSelectedAndWhatTier();
+ if (
+ !autoPilot ||
+ !autoPilot.maxThroughput ||
+ !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
+ ) {
+ setFormErrors(
+ `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
+ );
+ return false;
+ }
+ }
+ const throughput = _getThroughput();
+
+ if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
+ setFormErrors(`Please acknowledge the estimated daily spend.`);
+ return false;
+ }
+
+ const autoscaleThroughput = maxAutoPilotThroughputSet * 1;
+
+ if (
+ isAutoPilotSelected &&
+ autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
+ !throughputSpendAck
+ ) {
+ setFormErrors(`Please acknowledge the estimated monthly spend.`);
+ return false;
+ }
+
+ return true;
+ };
+
+ const handleonChangeDBId = React.useCallback(
+ (event: React.FormEvent, newValue?: string) => {
+ setDatabaseId(newValue || "");
+ },
+ []
+ );
+
+ const props: RightPaneFormProps = {
+ expandConsole: container.expandConsole,
+ formError: formErrors,
+ formErrorDetail: formErrorsDetails,
+ isExecuting,
+ submitButtonText: "OK",
+ onSubmit,
+ };
+
+ return (
+
+
+ {showUpsellMessage && formErrors === "" && (
+
+ )}
+
+
+
+ *
+ {databaseIdLabel}
+ {databaseIdTooltipText}
+
+
+
+
+
+ setDatabaseCreateNewShared(!databaseCreateNewShared)}
+ />{" "}
+ {databaseLevelThroughputTooltipText}
+
+ {databaseCreateNewShared && (
+
+
setThroughput(throughput)}
+ setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
+ onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)}
+ />
+
+ {canRequestSupport() && (
+
+
+ Contact support{" "}
+
+ for more than {throughputDefaults.unlimitedmax?.toLocaleString()} RU/s.
+
+ )}
+
+ )}
+
+
+
+
+ );
+};
diff --git a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap
new file mode 100644
index 000000000..aa5566040
--- /dev/null
+++ b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap
@@ -0,0 +1,103 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AddDatabasePane Pane should render Default properly 1`] = `
+
+
+
+
+
+
+
+ *
+
+
+ Database id
+
+
+ A database is a logical container of one or more collections
+
+
+
+
+
+
+
+ Provisioned throughput at the database level will be shared across all collections within the database.
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
index a445e6fe4..29d835e45 100644
--- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
+++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
@@ -1105,7 +1105,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
>