diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 11f399ba4..5031a3786 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -232,6 +232,10 @@ export class SavedQueries { public static readonly PartitionKeyProperty: string = "id"; } +export class RegionSelectionOptions { + public static readonly Global: string = "Global"; +} + export class DocumentsGridMetrics { public static DocumentsPerPage: number = 100; public static IndividualRowHeight: number = 34; diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 94a7959d6..bb800eacc 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -119,7 +119,11 @@ export const endpoint = () => { const location = _global.parent ? _global.parent.location : _global.location; return configContext.EMULATOR_ENDPOINT || location.origin; } - return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint; + return ( + userContext.endpoint || + userContext.selectedRegionalEndpoint || + userContext?.databaseAccount?.properties?.documentEndpoint + ); }; export async function getTokenFromAuthService( @@ -253,13 +257,15 @@ export function client(): Cosmos.CosmosClient { const currentWriteRegion = await client.getWriteEndpoint(); console.log(`Current write endpoint: ${JSON.stringify(currentWriteRegion)}`); - console.log(`Current userContext endpoint: ${JSON.stringify(userContext?.endpoint)}`); + console.log( + `Current userContext.selectedRegionalEndpoint: ${JSON.stringify(userContext?.selectedRegionalEndpoint)}`, + ); } const options: Cosmos.CosmosClientOptions = { - endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called + // endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called // endpoint: "https://test-craig-nosql-westus3.documents.azure.com:443/", - // endpoint: "https://test-craig-nosql-eastus2.documents.azure.com:443/", + endpoint: "https://test-craig-nosql-eastus2.documents.azure.com:443/", key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey, tokenProvider, userAgentSuffix: "Azure Portal", diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index a5b1f7abb..72a20d3e4 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -144,10 +144,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ? LocalStorageUtility.getEntryString(StorageKey.IsGraphAutoVizDisabled) : "false", ); - const [readRegion, setReadRegion] = useState( - LocalStorageUtility.hasItem(StorageKey.ReadRegion) - ? LocalStorageUtility.getEntryString(StorageKey.ReadRegion) - : userContext?.databaseAccount?.properties?.readLocations?.[0]?.locationName, + const [selectedRegion, setSelectedRegion] = useState( + LocalStorageUtility.hasItem(StorageKey.SelectedRegion) + ? LocalStorageUtility.getEntryString(StorageKey.SelectedRegion) + : Constants.RegionSelectionOptions.Global, ); const [retryAttempts, setRetryAttempts] = useState( LocalStorageUtility.hasItem(StorageKey.RetryAttempts) @@ -186,10 +186,44 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin"; const shouldShowParallelismOption = userContext.apiType !== "Gremlin"; const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled(); - const readRegionOptions = userContext?.databaseAccount?.properties?.readLocations?.map((location) => ({ - key: location.locationName, - text: location.locationName, - })); + + const uniqueAccountRegions = new Set(); + const regionOptions: IDropdownOption[] = []; + regionOptions.push({ + key: Constants.RegionSelectionOptions.Global, + text: `${Constants.RegionSelectionOptions.Global} (Default)`, + data: { + endpoint: userContext?.databaseAccount?.properties?.documentEndpoint, + writeEnabled: true, + }, + }); + userContext?.databaseAccount?.properties?.writeLocations?.forEach((loc) => { + if (!uniqueAccountRegions.has(loc.locationName)) { + uniqueAccountRegions.add(loc.locationName); + regionOptions.push({ + key: loc.locationName, + text: `${loc.locationName} (Read/Write)`, + data: { + endpoint: loc.documentEndpoint, + writeEnabled: true, + }, + }); + } + }); + userContext?.databaseAccount?.properties?.readLocations?.forEach((loc) => { + if (!uniqueAccountRegions.has(loc.locationName)) { + uniqueAccountRegions.add(loc.locationName); + regionOptions.push({ + key: loc.locationName, + text: `${loc.locationName} (Read)`, + data: { + endpoint: loc.documentEndpoint, + writeEnabled: false, + }, + }); + } + }); + const shouldShowCopilotSampleDBOption = userContext.apiType === "SQL" && useQueryCopilot.getState().copilotEnabled && @@ -273,20 +307,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ } } - // Check if region selection has been updated. Update database account in user context accordingly. - const updatedDatabaseAccount = { - ...userContext.databaseAccount, - properties: { - ...userContext.databaseAccount.properties, - documentEndpoint: userContext?.databaseAccount?.properties?.readLocations?.find( - (loc) => loc.locationName === readRegion, - )?.documentEndpoint, - }, - }; + // TODO: Check if region selection has been updated. Update database account in user context accordingly. updateUserContext({ - databaseAccount: updatedDatabaseAccount, + selectedRegionalEndpoint: regionOptions.find((option) => option.key === selectedRegion)?.data?.endpoint, hasCosmosClientRegionSettingChanged: true, }); + // TODO: If Global selected, then clear out region selection, but keep change variable enabled. console.log( `userContext?.databaseAccount?.properties?.documentEndpoint details: ${JSON.stringify( userContext?.databaseAccount?.properties?.documentEndpoint, @@ -295,7 +321,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); - LocalStorageUtility.setEntryString(StorageKey.ReadRegion, readRegion); + LocalStorageUtility.setEntryString(StorageKey.SelectedRegion, selectedRegion); LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); LocalStorageUtility.setEntryNumber(StorageKey.RetryInterval, retryInterval); LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds); @@ -443,9 +469,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ setDefaultQueryResultsView(option.key as SplitterDirection); }; - const handleOnReadRegionOptionChange = (ev: React.FormEvent, option: IDropdownOption): void => { - // TODO: Region validation? - setReadRegion(option.text); + const handleOnSelectedRegionOptionChange = (ev: React.FormEvent, option: IDropdownOption): void => { + setSelectedRegion(option.key as string); }; const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { @@ -723,9 +748,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ option.key === selectedRegion)?.text} + onChange={handleOnSelectedRegionOptionChange} + options={regionOptions} styles={{ root: { marginBottom: "10px" } }} /> diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts index f77acc8bc..7ecacb34e 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -11,7 +11,7 @@ export enum StorageKey { RUThreshold, QueryTimeoutEnabled, QueryTimeout, - ReadRegion, + SelectedRegion, WriteRegion, RetryAttempts, RetryInterval, diff --git a/src/UserContext.ts b/src/UserContext.ts index d30951780..07e0dabab 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -101,6 +101,7 @@ export interface UserContext { readonly isReplica?: boolean; collectionCreationDefaults: CollectionCreationDefaults; sampleDataConnectionInfo?: ParsedResourceTokenConnectionString; + readonly selectedRegionalEndpoint?: string; readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams; readonly feedbackPolicies?: AdminFeedbackPolicySettings; readonly dataPlaneRbacEnabled?: boolean; diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts index ad9f41947..d2ef4e8ff 100644 --- a/src/Utils/AuthorizationUtils.ts +++ b/src/Utils/AuthorizationUtils.ts @@ -73,11 +73,10 @@ export async function acquireMsalTokenForAccount( if (userContext.databaseAccount.properties?.documentEndpoint === undefined) { throw new Error("Database account has no document endpoint defined"); } - // const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace( - // /\/+$/, - // "/.default", - // ); - const hrefEndpoint = new URL("https://test-craig-nosql.documents.azure.com").href.replace(/\/+$/, "/.default"); + const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace( + /\/+$/, + "/.default", + ); const msalInstance = await getMsalInstance(); const knownAccounts = msalInstance.getAllAccounts(); // If user_hint is provided, we will try to use it to find the account.