Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual Region Selection #2037

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1a4880c
Add standin region selection to settings menu.
bogercraig Dec 27, 2024
3c72c8b
Retrieve read and write regions from user context and populate dropdo…
bogercraig Dec 28, 2024
8a59ff9
Change to only selecting region for cosmos client. Not setting up se…
bogercraig Dec 30, 2024
f286de6
Add read and write endpoint logging to cosmos client.
bogercraig Dec 31, 2024
5076250
Pass changing endpoint from settings menu to client. Encountered tok…
bogercraig Dec 31, 2024
ae2d91e
Rough implementation of region selection of endpoint for cosmos clien…
bogercraig Jan 1, 2025
d6f16aa
Update region selection to include global endpoint and generate a uni…
bogercraig Jan 3, 2025
f1163b1
Update cosmos client to fix bug.
bogercraig Jan 3, 2025
3984275
Swapping back to default endpoint value.
bogercraig Jan 17, 2025
cba6206
Rebase on client refresh bug fix.
bogercraig Jan 18, 2025
8542aa6
Enable region selection for NoSql, Table, Gremlin
bogercraig Jan 18, 2025
c4888af
Add logic to reset regional endpoint when global is selected.
bogercraig Jan 18, 2025
6631a30
Fix state changing when selecting region or resetting to global.
bogercraig Jan 22, 2025
4255ad5
Rough implementation of configuring regional endpoint when DE is load…
bogercraig Jan 23, 2025
8a9be72
Ininitial attempt at adding error handling, but still having issues w…
bogercraig Jan 24, 2025
1f9f2b3
Added rough error handling in local requestPlugin used in local envir…
bogercraig Jan 25, 2025
82c199d
Change how request plugin returns error so existing error handling ut…
bogercraig Jan 28, 2025
2865f3c
Only enable region selection for nosql accounts.
bogercraig Jan 28, 2025
2d83153
Limit region selection to portal and hosted AAD auth. SQL accounts o…
bogercraig Jan 28, 2025
464551b
Update error handling to account for generic error code.
bogercraig Jan 29, 2025
11bd9d6
Refactor error code extraction.
bogercraig Jan 29, 2025
a7e7f94
Update test snapshots and remove unneeded logging.
bogercraig Jan 31, 2025
065e425
Change error handling to use only the message rather than casting to …
bogercraig Feb 7, 2025
cd1a33c
Clean up debug logging in cosmos client.
bogercraig Feb 7, 2025
a6cc53f
Remove unused storage keys.
bogercraig Feb 7, 2025
bdc4419
Use endpoint instead of region name to track selected region. Preven…
bogercraig Feb 12, 2025
57ba8cf
Add initial button state update depending on region selection.
bogercraig Feb 13, 2025
6a763de
Disable CRUD buttons when read region selected.
bogercraig Feb 15, 2025
d1382dc
Default to write enabled in react.
bogercraig Feb 19, 2025
8751063
Disable query saving when read region is selected.
bogercraig Feb 21, 2025
86f897e
Patch clientWidth error on conflicts tab.
bogercraig Feb 24, 2025
a9422f4
Resolve merge conflicts from rebase.
bogercraig Feb 25, 2025
87d114c
Make sure proxy endpoints return in all cases.
bogercraig Feb 25, 2025
4f23277
Remove excess client logging and match main for ConflictsTab.
bogercraig Feb 25, 2025
7be059d
Cleaning up logging and fixing endpoint discovery bug.
bogercraig Mar 4, 2025
62b4cd4
Fix formatting.
bogercraig Mar 4, 2025
fabe871
Reformatting if statements with preferred formatting.
bogercraig Mar 7, 2025
3168cef
Migrate region selection to local persistence. Fixes account swappin…
bogercraig Mar 8, 2025
468b7ab
Relocate resetting interface context to helper function.
bogercraig Mar 8, 2025
c26b1c1
Remove legacy state storage for regional endpoint selection.
bogercraig Mar 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Common/CosmosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,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.selectedRegionalEndpoint ||
userContext.endpoint ||
userContext?.databaseAccount?.properties?.documentEndpoint
);
};

export async function getTokenFromAuthService(
Expand Down Expand Up @@ -203,6 +207,7 @@ export function client(): Cosmos.CosmosClient {
userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders,
connectionPolicy: {
enableEndpointDiscovery: userContext.selectedRegionalEndpoint ? false : true,
retryOptions: {
maxRetryAttemptCount: LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts),
fixedRetryIntervalInMilliseconds: LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval),
Expand Down
159 changes: 148 additions & 11 deletions src/Explorer/Panes/SettingsPane/SettingsPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
Checkbox,
ChoiceGroup,
DefaultButton,
Dropdown,
IChoiceGroupOption,
IDropdownOption,
ISpinButtonStyles,
IToggleStyles,
Position,
Expand All @@ -21,7 +23,14 @@ import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { Platform, configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog";
import { useDatabases } from "Explorer/useDatabases";
import { deleteAllStates } from "Shared/AppStatePersistenceUtility";
import {
AppStateComponentNames,
deleteAllStates,
deleteState,
hasState,
loadState,
saveState,
} from "Shared/AppStatePersistenceUtility";
import {
DefaultRUThreshold,
LocalStorageUtility,
Expand All @@ -37,6 +46,7 @@ import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react";
Expand Down Expand Up @@ -143,6 +153,17 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
? LocalStorageUtility.getEntryString(StorageKey.IsGraphAutoVizDisabled)
: "false",
);
const [selectedRegionalEndpoint, setSelectedRegionalEndpoint] = useState<string>(
hasState({
componentName: AppStateComponentNames.SelectedRegionalEndpoint,
globalAccountName: userContext.databaseAccount?.name,
})
? (loadState({
componentName: AppStateComponentNames.SelectedRegionalEndpoint,
globalAccountName: userContext.databaseAccount?.name,
}) as string)
: "",
);
const [retryAttempts, setRetryAttempts] = useState<number>(
LocalStorageUtility.hasItem(StorageKey.RetryAttempts)
? LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts)
Expand Down Expand Up @@ -189,6 +210,44 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
configContext.platform !== Platform.Fabric &&
!isEmulator;
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled() && !isEmulator;

const uniqueAccountRegions = new Set<string>();
const regionOptions: IDropdownOption[] = [];
regionOptions.push({
key: userContext?.databaseAccount?.properties?.documentEndpoint,
text: `Global (Default)`,
data: {
isGlobal: true,
writeEnabled: true,
},
});
userContext?.databaseAccount?.properties?.writeLocations?.forEach((loc) => {
if (!uniqueAccountRegions.has(loc.locationName)) {
uniqueAccountRegions.add(loc.locationName);
regionOptions.push({
key: loc.documentEndpoint,
text: `${loc.locationName} (Read/Write)`,
data: {
isGlobal: false,
writeEnabled: true,
},
});
}
});
userContext?.databaseAccount?.properties?.readLocations?.forEach((loc) => {
if (!uniqueAccountRegions.has(loc.locationName)) {
uniqueAccountRegions.add(loc.locationName);
regionOptions.push({
key: loc.documentEndpoint,
text: `${loc.locationName} (Read)`,
data: {
isGlobal: false,
writeEnabled: false,
},
});
}
});

const shouldShowCopilotSampleDBOption =
userContext.apiType === "SQL" &&
useQueryCopilot.getState().copilotEnabled &&
Expand Down Expand Up @@ -274,6 +333,46 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
}
}

const storedRegionalEndpoint = loadState({
componentName: AppStateComponentNames.SelectedRegionalEndpoint,
globalAccountName: userContext.databaseAccount?.name,
}) as string;
const selectedRegionIsGlobal =
selectedRegionalEndpoint === userContext?.databaseAccount?.properties?.documentEndpoint;
if (selectedRegionIsGlobal && storedRegionalEndpoint) {
deleteState({
componentName: AppStateComponentNames.SelectedRegionalEndpoint,
globalAccountName: userContext.databaseAccount?.name,
});
updateUserContext({
selectedRegionalEndpoint: undefined,
writeEnabledInSelectedRegion: true,
refreshCosmosClient: true,
});
useClientWriteEnabled.setState({ clientWriteEnabled: true });
} else if (
selectedRegionalEndpoint &&
!selectedRegionIsGlobal &&
selectedRegionalEndpoint !== storedRegionalEndpoint
) {
saveState(
{
componentName: AppStateComponentNames.SelectedRegionalEndpoint,
globalAccountName: userContext.databaseAccount?.name,
},
selectedRegionalEndpoint,
);
const validWriteEndpoint = userContext.databaseAccount?.properties?.writeLocations?.find(
(loc) => loc.documentEndpoint === selectedRegionalEndpoint,
);
updateUserContext({
selectedRegionalEndpoint: selectedRegionalEndpoint,
writeEnabledInSelectedRegion: !!validWriteEndpoint,
refreshCosmosClient: true,
});
useClientWriteEnabled.setState({ clientWriteEnabled: !!validWriteEndpoint });
}

LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
Expand Down Expand Up @@ -423,6 +522,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
setDefaultQueryResultsView(option.key as SplitterDirection);
};

const handleOnSelectedRegionOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IDropdownOption): void => {
setSelectedRegionalEndpoint(option.key as string);
};

const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent<HTMLElement>, newValue?: string): void => {
const retryAttempts = Number(newValue);
if (!isNaN(retryAttempts)) {
Expand Down Expand Up @@ -583,9 +686,41 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" &&
userContext.authType === AuthType.AAD &&
configContext.platform !== Platform.Fabric && (
<AccordionItem value="3">
<AccordionHeader>
<div className={styles.header}>Region Selection</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Changes region the Cosmos Client uses to access account.
</div>
<div>
<span className={styles.subHeader}>Select Region</span>
<InfoTooltip className={styles.headerIcon}>
Changes the account endpoint used to perform client operations.
</InfoTooltip>
</div>
<Dropdown
placeholder={
selectedRegionalEndpoint
? regionOptions.find((option) => option.key === selectedRegionalEndpoint)?.text
: regionOptions[0]?.text
}
onChange={handleOnSelectedRegionOptionChange}
options={regionOptions}
styles={{ root: { marginBottom: "10px" } }}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && !isEmulator && (
<>
<AccordionItem value="3">
<AccordionItem value="4">
<AccordionHeader>
<div className={styles.header}>Query Timeout</div>
</AccordionHeader>
Expand Down Expand Up @@ -626,7 +761,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel>
</AccordionItem>

<AccordionItem value="4">
<AccordionItem value="5">
<AccordionHeader>
<div className={styles.header}>RU Limit</div>
</AccordionHeader>
Expand Down Expand Up @@ -660,7 +795,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel>
</AccordionItem>

<AccordionItem value="5">
<AccordionItem value="6">
<AccordionHeader>
<div className={styles.header}>Default Query Results View</div>
</AccordionHeader>
Expand All @@ -681,8 +816,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
</>
)}

{showRetrySettings && (
<AccordionItem value="6">
<AccordionItem value="7">
<AccordionHeader>
<div className={styles.header}>Retry Settings</div>
</AccordionHeader>
Expand Down Expand Up @@ -755,7 +891,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{!isEmulator && (
<AccordionItem value="7">
<AccordionItem value="8">
<AccordionHeader>
<div className={styles.header}>Enable container pagination</div>
</AccordionHeader>
Expand All @@ -779,7 +915,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{shouldShowCrossPartitionOption && (
<AccordionItem value="8">
<AccordionItem value="9">
<AccordionHeader>
<div className={styles.header}>Enable cross-partition query</div>
</AccordionHeader>
Expand All @@ -804,7 +940,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{shouldShowParallelismOption && (
<AccordionItem value="9">
<AccordionItem value="10">
<AccordionHeader>
<div className={styles.header}>Max degree of parallelism</div>
</AccordionHeader>
Expand Down Expand Up @@ -837,7 +973,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{shouldShowPriorityLevelOption && (
<AccordionItem value="10">
<AccordionItem value="11">
<AccordionHeader>
<div className={styles.header}>Priority Level</div>
</AccordionHeader>
Expand All @@ -860,7 +996,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{shouldShowGraphAutoVizOption && (
<AccordionItem value="11">
<AccordionItem value="12">
<AccordionHeader>
<div className={styles.header}>Display Gremlin query results as:&nbsp;</div>
</AccordionHeader>
Expand All @@ -881,7 +1017,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem>
)}
{shouldShowCopilotSampleDBOption && (
<AccordionItem value="12">
<AccordionItem value="13">
<AccordionHeader>
<div className={styles.header}>Enable sample database</div>
</AccordionHeader>
Expand Down Expand Up @@ -927,6 +1063,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
<li>Reset your customized tab layout, including the splitter positions</li>
<li>Erase your table column preferences, including any custom columns</li>
<li>Clear your filter history</li>
<li>Reset region selection to global</li>
</ul>
</>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="3"
value="4"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -148,7 +148,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="4"
value="5"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -219,7 +219,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="5"
value="6"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -281,7 +281,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="6"
value="7"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -423,7 +423,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="7"
value="8"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -459,7 +459,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="8"
value="9"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -495,7 +495,7 @@ exports[`Settings Pane should render Default properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="9"
value="10"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -575,7 +575,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
className="customAccordion ___1uf6361_0000000 fz7g6wx"
>
<AccordionItem
value="6"
value="7"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -717,7 +717,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="7"
value="8"
>
<AccordionHeader>
<div
Expand Down Expand Up @@ -753,7 +753,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
</AccordionPanel>
</AccordionItem>
<AccordionItem
value="11"
value="12"
>
<AccordionHeader>
<div
Expand Down
Loading
Loading