From 64f36e2d284ccae2478e1267c9a7b75ccb989920 Mon Sep 17 00:00:00 2001
From: victor-meng <56978073+victor-meng@users.noreply.github.com>
Date: Sat, 30 Oct 2021 19:45:16 -0700
Subject: [PATCH] Add throughput cap error message (#1151)

---
 src/Contracts/DataModels.ts                   |  1 +
 .../Controls/Settings/SettingsComponent.tsx   | 47 +++++++++++++-
 .../SettingsSubComponents/ScaleComponent.tsx  |  2 +
 .../ThroughputInputAutoPilotV3Component.tsx   |  3 +
 .../ThroughputInput/ThroughputInput.test.tsx  |  1 +
 .../ThroughputInput/ThroughputInput.tsx       | 65 ++++++++++++++++++-
 .../ThroughputInput.test.tsx.snap             |  1 +
 src/Explorer/Panes/AddCollectionPanel.tsx     | 10 ++-
 .../AddDatabasePanel/AddDatabasePanel.tsx     |  3 +
 .../AddDatabasePanel.test.tsx.snap            |  2 +
 .../CassandraAddCollectionPane.tsx            |  4 ++
 ...teCollectionConfirmationPane.test.tsx.snap |  6 ++
 .../ExecuteSprocParamsPane.test.tsx.snap      |  6 ++
 src/Explorer/Panes/PanelFooterComponent.tsx   | 10 ++-
 .../Panes/RightPaneForm/RightPaneForm.tsx     |  6 +-
 .../__snapshots__/RightPaneForm.test.tsx.snap |  6 ++
 .../StringInputPane.test.tsx.snap             |  6 ++
 .../TableQuerySelectPanel.test.tsx.snap       |  6 ++
 .../AddTableEntityPanel.test.tsx.snap         |  6 ++
 .../EditTableEntityPanel.test.tsx.snap        |  6 ++
 ...eteDatabaseConfirmationPanel.test.tsx.snap |  6 ++
 src/Platform/Hosted/extractFeatures.ts        |  2 +
 .../generatedClients/cosmos/sqlResources.ts   |  2 +-
 src/hooks/useDatabaseAccounts.tsx             |  5 +-
 24 files changed, 200 insertions(+), 12 deletions(-)

diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts
index 25d73d189..99db16f11 100644
--- a/src/Contracts/DataModels.ts
+++ b/src/Contracts/DataModels.ts
@@ -26,6 +26,7 @@ export interface DatabaseAccountExtendedProperties {
   isVirtualNetworkFilterEnabled?: boolean;
   ipRules?: IpRule[];
   privateEndpointConnections?: unknown[];
+  capacity?: { totalThroughputLimit: number };
 }
 
 export interface DatabaseAccountResponseLocation {
diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx
index 0094806c6..880a50b99 100644
--- a/src/Explorer/Controls/Settings/SettingsComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx
@@ -1,4 +1,5 @@
 import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
+import { useDatabases } from "Explorer/useDatabases";
 import * as React from "react";
 import DiscardIcon from "../../../../images/discard.svg";
 import SaveIcon from "../../../../images/save-cosmos.svg";
@@ -71,6 +72,7 @@ export interface SettingsComponentState {
   wasAutopilotOriginallySet: boolean;
   isScaleSaveable: boolean;
   isScaleDiscardable: boolean;
+  throughputError: string;
 
   timeToLive: TtlType;
   timeToLiveBaseline: TtlType;
@@ -124,6 +126,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
   private changeFeedPolicyVisible: boolean;
   private isFixedContainer: boolean;
   private shouldShowIndexingPolicyEditor: boolean;
+  private totalThroughputUsed: number;
   public mongoDBCollectionResource: MongoDBCollectionResource;
 
   constructor(props: SettingsComponentProps) {
@@ -155,6 +158,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
       wasAutopilotOriginallySet: false,
       isScaleSaveable: false,
       isScaleDiscardable: false,
+      throughputError: undefined,
 
       timeToLive: undefined,
       timeToLiveBaseline: undefined,
@@ -208,6 +212,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
         return true;
       },
     };
+
+    this.totalThroughputUsed = 0;
+    (useDatabases.getState().databases || []).forEach((database) => {
+      if (database.offer()) {
+        const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
+        this.totalThroughputUsed += dbThroughput;
+      }
+
+      (database.collections() || []).forEach((collection) => {
+        if (collection.offer()) {
+          const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
+          this.totalThroughputUsed += colThroughput;
+        }
+      });
+    });
   }
 
   componentDidMount(): void {
@@ -254,6 +273,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
       return false;
     }
 
+    if (this.state.throughputError) {
+      return false;
+    }
+
     return (
       this.state.isScaleSaveable ||
       this.state.isSubSettingsSaveable ||
@@ -643,10 +666,27 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
     return buttons;
   };
 
-  private onMaxAutoPilotThroughputChange = (newThroughput: number): void =>
-    this.setState({ autoPilotThroughput: newThroughput });
+  private onMaxAutoPilotThroughputChange = (newThroughput: number): void => {
+    let throughputError = "";
+    const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
+    if (throughputCap && throughputCap - this.totalThroughputUsed < newThroughput - this.offer.autoscaleMaxThroughput) {
+      throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
+        this.totalThroughputUsed + newThroughput
+      } RU/s. Change total throughput limit in cost management.`;
+    }
+    this.setState({ autoPilotThroughput: newThroughput, throughputError });
+  };
 
-  private onThroughputChange = (newThroughput: number): void => this.setState({ throughput: newThroughput });
+  private onThroughputChange = (newThroughput: number): void => {
+    let throughputError = "";
+    const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
+    if (throughputCap && throughputCap - this.totalThroughputUsed < newThroughput - this.offer.manualThroughput) {
+      throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
+        this.totalThroughputUsed + newThroughput
+      } RU/s. Change total throughput limit in cost management.`;
+    }
+    this.setState({ throughput: newThroughput, throughputError });
+  };
 
   private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
     this.setState({ isAutoPilotSelected: isAutoPilotSelected });
@@ -893,6 +933,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
       onScaleSaveableChange: this.onScaleSaveableChange,
       onScaleDiscardableChange: this.onScaleDiscardableChange,
       initialNotification: this.props.settingsTab.pendingNotification(),
+      throughputError: this.state.throughputError,
     };
 
     if (!this.isCollectionSettingsTab) {
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx
index 98914e33a..c1739171d 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ScaleComponent.tsx
@@ -36,6 +36,7 @@ export interface ScaleComponentProps {
   onScaleSaveableChange: (isScaleSaveable: boolean) => void;
   onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
   initialNotification: DataModels.Notification;
+  throughputError?: string;
 }
 
 export class ScaleComponent extends React.Component<ScaleComponentProps> {
@@ -189,6 +190,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
       onScaleDiscardableChange={this.props.onScaleDiscardableChange}
       getThroughputWarningMessage={this.getThroughputWarningMessage}
       usageSizeInKB={this.props.collection?.usageSizeInKB()}
+      throughputError={this.props.throughputError}
     />
   );
 
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx
index 609d16b2d..c958b5a6a 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx
@@ -75,6 +75,7 @@ export interface ThroughputInputAutoPilotV3Props {
   onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
   getThroughputWarningMessage: () => JSX.Element;
   usageSizeInKB: number;
+  throughputError?: string;
 }
 
 interface ThroughputInputAutoPilotV3State {
@@ -540,6 +541,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
         value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
         onChange={this.onAutoPilotThroughputChange}
         min={minAutoPilotThroughput}
+        errorMessage={this.props.throughputError}
       />
       {!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
       {this.minRUperGBSurvey()}
@@ -579,6 +581,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
         }
         onChange={this.onThroughputChange}
         min={this.props.minimum}
+        errorMessage={this.props.throughputError}
       />
       {this.state.exceedFreeTierThroughput && (
         <MessageBar
diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx
index 6995f240a..9d1733da0 100644
--- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx
+++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx
@@ -7,6 +7,7 @@ const props = {
   isSharded: true,
   setThroughputValue: () => jest.fn(),
   setIsAutoscale: () => jest.fn(),
+  setIsThroughputCapExceeded: () => jest.fn(),
   onCostAcknowledgeChange: () => jest.fn(),
 };
 describe("ThroughputInput Pane", () => {
diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx
index de8b7757a..1b5972d36 100644
--- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx
+++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx
@@ -1,5 +1,6 @@
 import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
-import React, { FunctionComponent, useState } from "react";
+import { useDatabases } from "Explorer/useDatabases";
+import React, { FunctionComponent, useEffect, useState } from "react";
 import * as Constants from "../../../Common/Constants";
 import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
 import * as SharedConstants from "../../../Shared/Constants";
@@ -16,6 +17,7 @@ export interface ThroughputInputProps {
   showFreeTierExceedThroughputTooltip: boolean;
   setThroughputValue: (throughput: number) => void;
   setIsAutoscale: (isAutoscale: boolean) => void;
+  setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
   onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
 }
 
@@ -24,6 +26,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
   showFreeTierExceedThroughputTooltip,
   setThroughputValue,
   setIsAutoscale,
+  setIsThroughputCapExceeded,
   isSharded,
   onCostAcknowledgeChange,
 }: ThroughputInputProps) => {
@@ -31,10 +34,58 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
   const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
   const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
   const [throughputError, setThroughputError] = useState<string>("");
+  const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
 
   setIsAutoscale(isAutoscaleSelected);
   setThroughputValue(throughput);
 
+  const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
+
+  useEffect(() => {
+    // throughput cap check for the initial state
+    let totalThroughput = 0;
+    (useDatabases.getState().databases || []).forEach((database) => {
+      if (database.offer()) {
+        const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
+        totalThroughput += dbThroughput;
+      }
+
+      (database.collections() || []).forEach((collection) => {
+        if (collection.offer()) {
+          const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
+          totalThroughput += colThroughput;
+        }
+      });
+    });
+    setTotalThroughputUsed(totalThroughput);
+
+    if (throughputCap && throughputCap - totalThroughput < throughput) {
+      setThroughputError(
+        `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
+          totalThroughputUsed + throughput
+        } RU/s. Change total throughput limit in cost management.`
+      );
+
+      setIsThroughputCapExceeded(true);
+    }
+  }, []);
+
+  const checkThroughputCap = (newThroughput: number): boolean => {
+    if (throughputCap && throughputCap - totalThroughputUsed < newThroughput) {
+      setThroughputError(
+        `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
+          totalThroughputUsed + newThroughput
+        } RU/s. Change total throughput limit in cost management.`
+      );
+      setIsThroughputCapExceeded(true);
+      return false;
+    }
+
+    setThroughputError("");
+    setIsThroughputCapExceeded(false);
+    return true;
+  };
+
   const getThroughputLabelText = (): string => {
     let throughputHeaderText: string;
     if (isAutoscaleSelected) {
@@ -60,11 +111,17 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
     const newThroughput = parseInt(newInput);
     setThroughput(newThroughput);
     setThroughputValue(newThroughput);
+
     if (!isSharded && newThroughput > 10000) {
       setThroughputError("Unsharded collections support up to 10,000 RUs");
-    } else {
-      setThroughputError("");
+      return;
     }
+
+    if (!checkThroughputCap(newThroughput)) {
+      return;
+    }
+
+    setThroughputError("");
   };
 
   const getAutoScaleTooltip = (): string => {
@@ -96,11 +153,13 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
       setIsAutoScaleSelected(true);
       setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
       setIsAutoscale(true);
+      checkThroughputCap(AutoPilotUtils.minAutoPilotThroughput);
     } else {
       setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
       setIsAutoScaleSelected(false);
       setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
       setIsAutoscale(false);
+      checkThroughputCap(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
     }
   };
 
diff --git a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap
index 0798c01ef..d3a8ecf23 100644
--- a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap
+++ b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap
@@ -6,6 +6,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
   isSharded={true}
   onCostAcknowledgeChange={[Function]}
   setIsAutoscale={[Function]}
+  setIsThroughputCapExceeded={[Function]}
   setThroughputValue={[Function]}
   showFreeTierExceedThroughputTooltip={true}
 >
diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx
index 028b3c426..a5d6651d5 100644
--- a/src/Explorer/Panes/AddCollectionPanel.tsx
+++ b/src/Explorer/Panes/AddCollectionPanel.tsx
@@ -92,6 +92,7 @@ export interface AddCollectionPanelState {
   errorMessage: string;
   showErrorDetails: boolean;
   isExecuting: boolean;
+  isThroughputCapExceeded: boolean;
 }
 
 export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
@@ -122,6 +123,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
       errorMessage: "",
       showErrorDetails: false,
       isExecuting: false,
+      isThroughputCapExceeded: false,
     };
   }
 
@@ -249,6 +251,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
                     isSharded={this.state.isSharded}
                     setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
                     setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
+                    setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
+                      this.setState({ isThroughputCapExceeded })
+                    }
                     onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
                   />
                 )}
@@ -480,6 +485,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
               isSharded={this.state.isSharded}
               setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
               setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
+              setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
+                this.setState({ isThroughputCapExceeded })
+              }
               onCostAcknowledgeChange={(isAcknowledged: boolean) => {
                 this.isCostAcknowledged = isAcknowledged;
               }}
@@ -676,7 +684,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
           )}
         </div>
 
-        <PanelFooterComponent buttonLabel="OK" />
+        <PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
 
         {this.state.isExecuting && <PanelLoadingScreen />}
       </form>
diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
index 3ee59bd30..0c3568ecf 100644
--- a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
+++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
@@ -50,6 +50,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
   );
   const [formErrors, setFormErrors] = useState<string>("");
   const [isExecuting, setIsExecuting] = useState<boolean>(false);
+  const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
 
   const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
 
@@ -166,6 +167,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
     formError: formErrors,
     isExecuting,
     submitButtonText: "OK",
+    isSubmitButtonDisabled: isThroughputCapExceeded,
     onSubmit,
   };
 
@@ -236,6 +238,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
             isSharded={databaseCreateNewShared}
             setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
             setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
+            setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
             onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
           />
         )}
diff --git a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap
index 9c4bbaa32..bf462a66c 100644
--- a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap
+++ b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap
@@ -4,6 +4,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
 <RightPaneForm
   formError=""
   isExecuting={false}
+  isSubmitButtonDisabled={false}
   onSubmit={[Function]}
   submitButtonText="OK"
 >
@@ -92,6 +93,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
       isSharded={true}
       onCostAcknowledgeChange={[Function]}
       setIsAutoscale={[Function]}
+      setIsThroughputCapExceeded={[Function]}
       setThroughputValue={[Function]}
     />
   </div>
diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx
index 64ee3fb3f..9aa4faf6b 100644
--- a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx
+++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx
@@ -43,6 +43,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
   const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
   const [isExecuting, setIsExecuting] = useState<boolean>();
   const [formError, setFormError] = useState<string>("");
+  const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
   const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
 
   const addCollectionPaneOpenMessage = {
@@ -149,6 +150,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
     formError,
     isExecuting,
     submitButtonText: "OK",
+    isSubmitButtonDisabled: isThroughputCapExceeded,
     onSubmit,
   };
 
@@ -262,6 +264,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
               isSharded
               setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
               setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
+              setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
               onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
             />
           )}
@@ -334,6 +337,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
             isSharded={false}
             setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
             setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
+            setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
             onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
           />
         )}
diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
index de359f986..b31dc19d3 100644
--- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
+++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap
@@ -369,18 +369,21 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
       </div>
       <PanelFooterComponent
         buttonLabel="OK"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="OK"
+            disabled={false}
             id="sidePanelOkButton"
             text="OK"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="OK"
+              disabled={false}
               id="sidePanelOkButton"
               text="OK"
               theme={
@@ -660,6 +663,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
             >
               <CustomizedDefaultButton
                 ariaLabel="OK"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -941,6 +945,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
               >
                 <DefaultButton
                   ariaLabel="OK"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -1223,6 +1228,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
                   <BaseButton
                     ariaLabel="OK"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap
index 4e673b39c..3f163902a 100644
--- a/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap
+++ b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap
@@ -5305,18 +5305,21 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="Execute"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="Execute"
+            disabled={false}
             id="sidePanelOkButton"
             text="Execute"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="Execute"
+              disabled={false}
               id="sidePanelOkButton"
               text="Execute"
               theme={
@@ -5596,6 +5599,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="Execute"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -5877,6 +5881,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
               >
                 <DefaultButton
                   ariaLabel="Execute"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -6159,6 +6164,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
                   <BaseButton
                     ariaLabel="Execute"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/PanelFooterComponent.tsx b/src/Explorer/Panes/PanelFooterComponent.tsx
index d007a5e36..c24ac5fb3 100644
--- a/src/Explorer/Panes/PanelFooterComponent.tsx
+++ b/src/Explorer/Panes/PanelFooterComponent.tsx
@@ -3,12 +3,20 @@ import React from "react";
 
 export interface PanelFooterProps {
   buttonLabel: string;
+  isButtonDisabled?: boolean;
 }
 
 export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
   buttonLabel,
+  isButtonDisabled,
 }: PanelFooterProps): JSX.Element => (
   <div className="panelFooter">
-    <PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
+    <PrimaryButton
+      type="submit"
+      id="sidePanelOkButton"
+      text={buttonLabel}
+      ariaLabel={buttonLabel}
+      disabled={!!isButtonDisabled}
+    />
   </div>
 );
diff --git a/src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx b/src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx
index 26c356a0f..a444db02d 100644
--- a/src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx
+++ b/src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx
@@ -9,6 +9,7 @@ export interface RightPaneFormProps {
   onSubmit: () => void;
   submitButtonText: string;
   isSubmitButtonHidden?: boolean;
+  isSubmitButtonDisabled?: boolean;
   children?: ReactNode;
 }
 
@@ -18,6 +19,7 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
   onSubmit,
   submitButtonText,
   isSubmitButtonHidden = false,
+  isSubmitButtonDisabled = false,
   children,
 }: RightPaneFormProps) => {
   const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
@@ -30,7 +32,9 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
       <form className="panelFormWrapper" onSubmit={handleOnSubmit}>
         {formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
         {children}
-        {!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
+        {!isSubmitButtonHidden && (
+          <PanelFooterComponent buttonLabel={submitButtonText} isButtonDisabled={isSubmitButtonDisabled} />
+        )}
       </form>
       {isExecuting && <PanelLoadingScreen />}
     </>
diff --git a/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap b/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap
index 3438c3398..bafc521fc 100644
--- a/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap
+++ b/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap
@@ -14,18 +14,21 @@ exports[`Right Pane Form should render Default properly 1`] = `
   >
     <PanelFooterComponent
       buttonLabel="Load"
+      isButtonDisabled={false}
     >
       <div
         className="panelFooter"
       >
         <CustomizedPrimaryButton
           ariaLabel="Load"
+          disabled={false}
           id="sidePanelOkButton"
           text="Load"
           type="submit"
         >
           <PrimaryButton
             ariaLabel="Load"
+            disabled={false}
             id="sidePanelOkButton"
             text="Load"
             theme={
@@ -305,6 +308,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
           >
             <CustomizedDefaultButton
               ariaLabel="Load"
+              disabled={false}
               id="sidePanelOkButton"
               onRenderDescription={[Function]}
               primary={true}
@@ -586,6 +590,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
             >
               <DefaultButton
                 ariaLabel="Load"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -868,6 +873,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
                 <BaseButton
                   ariaLabel="Load"
                   baseClassName="ms-Button"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
diff --git a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap
index 0c1815afe..e83d2db32 100644
--- a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap
+++ b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap
@@ -675,18 +675,21 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="Create"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="Create"
+            disabled={false}
             id="sidePanelOkButton"
             text="Create"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="Create"
+              disabled={false}
               id="sidePanelOkButton"
               text="Create"
               theme={
@@ -966,6 +969,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="Create"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -1247,6 +1251,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
               >
                 <DefaultButton
                   ariaLabel="Create"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -1529,6 +1534,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
                   <BaseButton
                     ariaLabel="Create"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap b/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap
index 84159803b..ad7c2346c 100644
--- a/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap
+++ b/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap
@@ -1262,18 +1262,21 @@ exports[`Table query select Panel should render Default properly 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="OK"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="OK"
+            disabled={false}
             id="sidePanelOkButton"
             text="OK"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="OK"
+              disabled={false}
               id="sidePanelOkButton"
               text="OK"
               theme={
@@ -1553,6 +1556,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="OK"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -1834,6 +1838,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
               >
                 <DefaultButton
                   ariaLabel="OK"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -2116,6 +2121,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
                   <BaseButton
                     ariaLabel="OK"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap
index c78e43d5c..2714a4cd6 100644
--- a/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap
+++ b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap
@@ -356,18 +356,21 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="Add Entity"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="Add Entity"
+            disabled={false}
             id="sidePanelOkButton"
             text="Add Entity"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="Add Entity"
+              disabled={false}
               id="sidePanelOkButton"
               text="Add Entity"
               theme={
@@ -647,6 +650,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="Add Entity"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -928,6 +932,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
               >
                 <DefaultButton
                   ariaLabel="Add Entity"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -1210,6 +1215,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
                   <BaseButton
                     ariaLabel="Add Entity"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap
index 018fe7118..ff6bc9ef3 100644
--- a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap
+++ b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap
@@ -357,18 +357,21 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="Update"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="Update"
+            disabled={false}
             id="sidePanelOkButton"
             text="Update"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="Update"
+              disabled={false}
               id="sidePanelOkButton"
               text="Update"
               theme={
@@ -648,6 +651,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="Update"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -929,6 +933,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
               >
                 <DefaultButton
                   ariaLabel="Update"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -1211,6 +1216,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
                   <BaseButton
                     ariaLabel="Update"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap
index 5613a797e..ac2eef412 100644
--- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap
+++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap
@@ -1041,18 +1041,21 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
       </div>
       <PanelFooterComponent
         buttonLabel="OK"
+        isButtonDisabled={false}
       >
         <div
           className="panelFooter"
         >
           <CustomizedPrimaryButton
             ariaLabel="OK"
+            disabled={false}
             id="sidePanelOkButton"
             text="OK"
             type="submit"
           >
             <PrimaryButton
               ariaLabel="OK"
+              disabled={false}
               id="sidePanelOkButton"
               text="OK"
               theme={
@@ -1332,6 +1335,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
             >
               <CustomizedDefaultButton
                 ariaLabel="OK"
+                disabled={false}
                 id="sidePanelOkButton"
                 onRenderDescription={[Function]}
                 primary={true}
@@ -1613,6 +1617,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
               >
                 <DefaultButton
                   ariaLabel="OK"
+                  disabled={false}
                   id="sidePanelOkButton"
                   onRenderDescription={[Function]}
                   primary={true}
@@ -1895,6 +1900,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
                   <BaseButton
                     ariaLabel="OK"
                     baseClassName="ms-Button"
+                    disabled={false}
                     id="sidePanelOkButton"
                     onRenderDescription={[Function]}
                     primary={true}
diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts
index 3bea00924..655b9430a 100644
--- a/src/Platform/Hosted/extractFeatures.ts
+++ b/src/Platform/Hosted/extractFeatures.ts
@@ -34,6 +34,7 @@ export type Features = {
   readonly mongoProxyEndpoint?: string;
   readonly mongoProxyAPIs?: string;
   readonly notebooksTemporarilyDown: boolean;
+  readonly enableThroughputCap: boolean;
 };
 
 export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
@@ -86,6 +87,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
     notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
     phoenix: "true" === get("phoenix"),
     notebooksDownBanner: "true" === get("notebooksDownBanner"),
+    enableThroughputCap: "true" === get("enablethroughputcap"),
   };
 }
 
diff --git a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
index 40c68fb86..8c4a237ea 100644
--- a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
@@ -9,7 +9,7 @@
 import { configContext } from "../../../../ConfigContext";
 import { armRequest } from "../../request";
 import * as Types from "./types";
-const apiVersion = "2021-04-15";
+const apiVersion = "2021-10-15";
 
 /* Lists the SQL databases under an existing Azure Cosmos DB database account. */
 export async function listSqlDatabases(
diff --git a/src/hooks/useDatabaseAccounts.tsx b/src/hooks/useDatabaseAccounts.tsx
index c120d6a71..1390ceaed 100644
--- a/src/hooks/useDatabaseAccounts.tsx
+++ b/src/hooks/useDatabaseAccounts.tsx
@@ -1,6 +1,7 @@
 import useSWR from "swr";
 import { configContext } from "../ConfigContext";
 import { DatabaseAccount } from "../Contracts/DataModels";
+import { userContext } from "../UserContext";
 
 interface AccountListResult {
   nextLink: string;
@@ -14,8 +15,8 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
   headers.append("Authorization", bearer);
 
   let accounts: Array<DatabaseAccount> = [];
-
-  let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2021-06-15`;
+  const apiVersion = userContext.features.enableThroughputCap ? "2021-10-15" : "2021-06-15";
+  let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=${apiVersion}`;
 
   while (nextLink) {
     const response: Response = await fetch(nextLink, { headers });