diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index 99db16f11..0f5db93ff 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -27,6 +27,7 @@ export interface DatabaseAccountExtendedProperties { ipRules?: IpRule[]; privateEndpointConnections?: unknown[]; capacity?: { totalThroughputLimit: number }; + locations?: DatabaseAccountResponseLocation[]; } export interface DatabaseAccountResponseLocation { diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 880a50b99..132622e9f 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -213,20 +213,9 @@ export class SettingsComponent extends React.Component { - 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; - } - }); - }); + if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) { + this.calculateTotalThroughputUsed(); + } } componentDidMount(): void { @@ -504,6 +493,26 @@ export class SettingsComponent extends React.Component this.setState({ isMongoIndexingPolicyDiscardable }); + private calculateTotalThroughputUsed = (): void => { + this.totalThroughputUsed = 0; + (useDatabases.getState().databases || []).forEach(async (database) => { + if (database.offer()) { + const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput; + this.totalThroughputUsed += dbThroughput; + } + + (database.collections() || []).forEach(async (collection) => { + if (collection.offer()) { + const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput; + this.totalThroughputUsed += colThroughput; + } + }); + }); + + const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; + this.totalThroughputUsed *= numberOfRegions; + }; + public getAnalyticalStorageTtl = (): number => { if (this.isAnalyticalStorageEnabled) { if (this.state.analyticalStorageTtlSelection === TtlType.On) { @@ -669,9 +678,11 @@ export class SettingsComponent extends React.Component { let throughputError = ""; const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; - if (throughputCap && throughputCap - this.totalThroughputUsed < newThroughput - this.offer.autoscaleMaxThroughput) { + const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; + const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions; + if (throughputCap && throughputCap - this.totalThroughputUsed < throughputDelta) { 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 + this.totalThroughputUsed + throughputDelta } RU/s. Change total throughput limit in cost management.`; } this.setState({ autoPilotThroughput: newThroughput, throughputError }); @@ -680,9 +691,11 @@ export class SettingsComponent extends React.Component { let throughputError = ""; const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; + const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; + const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions; 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 + this.totalThroughputUsed + throughputDelta } RU/s. Change total throughput limit in cost management.`; } this.setState({ throughput: newThroughput, throughputError }); diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 1b5972d36..2a6c637ac 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -40,6 +40,7 @@ export const ThroughputInput: FunctionComponent = ({ setThroughputValue(throughput); const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; + const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; useEffect(() => { // throughput cap check for the initial state @@ -57,12 +58,13 @@ export const ThroughputInput: FunctionComponent = ({ } }); }); + totalThroughput *= numberOfRegions; 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 + totalThroughput + throughput * numberOfRegions } RU/s. Change total throughput limit in cost management.` ); @@ -74,7 +76,7 @@ export const ThroughputInput: FunctionComponent = ({ 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 + totalThroughputUsed + newThroughput * numberOfRegions } RU/s. Change total throughput limit in cost management.` ); setIsThroughputCapExceeded(true); diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 6a3a49bfb..58ef9ce66 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1176,7 +1176,9 @@ export default class Explorer { ); } else { - await useDatabases.getState().loadDatabaseOffers(); + userContext.databaseAccount?.properties.capacity?.totalThroughputLimit + ? await useDatabases.getState().loadAllOffers() + : await useDatabases.getState().loadDatabaseOffers(); useSidePanel .getState() .openSidePanel("New " + getCollectionName(), ); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index c659d7712..97bf851c1 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -308,8 +308,12 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { return { iconSrc: AddDatabaseIcon, iconAlt: label, - onCommandClick: () => - useSidePanel.getState().openSidePanel("New " + getDatabaseName(), ), + onCommandClick: async () => { + if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) { + await useDatabases.getState().loadAllOffers(); + } + useSidePanel.getState().openSidePanel("New " + getDatabaseName(), ); + }, commandButtonLabel: label, ariaLabel: label, hasPopup: true, diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index b52593707..10866cf6f 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -307,10 +307,14 @@ export class SplashScreen extends React.Component { iconSrc: AddDatabaseIcon, title: "New " + getDatabaseName(), description: undefined, - onClick: () => + onClick: async () => { + if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) { + await useDatabases.getState().loadAllOffers(); + } useSidePanel .getState() - .openSidePanel("New " + getDatabaseName(), ), + .openSidePanel("New " + getDatabaseName(), ); + }, }); } diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index fd444c0ed..5f836f7fb 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -576,7 +576,9 @@ export default class Collection implements ViewModels.Collection { public onSettingsClick = async (): Promise => { useSelectedNode.getState().setSelectedNode(this); - await this.loadOffer(); + userContext.databaseAccount?.properties.capacity?.totalThroughputLimit + ? await useDatabases.getState().loadAllOffers() + : await this.loadOffer(); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings); TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { description: "Settings node", diff --git a/src/Explorer/Tree/Database.tsx b/src/Explorer/Tree/Database.tsx index 5dc01343f..0ea1130fb 100644 --- a/src/Explorer/Tree/Database.tsx +++ b/src/Explorer/Tree/Database.tsx @@ -57,7 +57,7 @@ export default class Database implements ViewModels.Database { this.isOfferRead = false; } - public onSettingsClick = (): void => { + public onSettingsClick = async (): Promise => { useSelectedNode.getState().setSelectedNode(this); this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings); TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { @@ -66,6 +66,10 @@ export default class Database implements ViewModels.Database { dataExplorerArea: Constants.Areas.ResourceTree, }); + if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) { + await useDatabases.getState().loadAllOffers(); + } + const pendingNotificationsPromise: Promise = this.getPendingThroughputSplitNotification(); const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2; const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id()); diff --git a/src/Explorer/useDatabases.ts b/src/Explorer/useDatabases.ts index 1f9886a7d..7d1760af3 100644 --- a/src/Explorer/useDatabases.ts +++ b/src/Explorer/useDatabases.ts @@ -18,6 +18,7 @@ interface DatabasesState { findCollection: (databaseId: string, collectionId: string) => ViewModels.Collection; isLastCollection: () => boolean; loadDatabaseOffers: () => Promise; + loadAllOffers: () => Promise; isFirstResourceCreated: () => boolean; findSelectedDatabase: () => ViewModels.Database; validateDatabaseId: (id: string) => boolean; @@ -97,6 +98,19 @@ export const useDatabases: UseStore = create((set, get) => ({ }) ); }, + loadAllOffers: async () => { + await Promise.all( + get().databases?.map(async (database: ViewModels.Database) => { + await database.loadOffer(); + await database.loadCollections(); + await Promise.all( + (database.collections() || []).map(async (collection: ViewModels.Collection) => { + await collection.loadOffer(); + }) + ); + }) + ); + }, isFirstResourceCreated: () => { const databases = get().databases;