import { Checkbox, ChoiceGroup, IChoiceGroupOption, ISpinButtonStyles, IToggleStyles, Icon, MessageBar, MessageBarType, Position, SpinButton, Toggle, TooltipHost, } from "@fluentui/react"; import * as Constants from "Common/Constants"; import { SplitterDirection } from "Common/Splitter"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import { Platform, configContext } from "ConfigContext"; import { useDatabases } from "Explorer/useDatabases"; import { DefaultRUThreshold, LocalStorageUtility, StorageKey, getDefaultQueryResultsView, getRUThreshold, ruThresholdEnabled as isRUThresholdEnabled, } from "Shared/StorageUtility"; import * as StringUtility from "Shared/StringUtility"; import { updateUserContext, userContext } from "UserContext"; import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils"; import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useSidePanel } from "hooks/useSidePanel"; import React, { FunctionComponent, useState } from "react"; import Explorer from "../../Explorer"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { AuthType } from "AuthType"; import create, { UseStore } from "zustand"; import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; export interface DataPlaneRbacState { dataPlaneRbacEnabled: boolean; aadTokenUpdated: boolean; getState?: () => DataPlaneRbacState; setDataPlaneRbacEnabled: (dataPlaneRbacEnabled: boolean) => void; setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void; } type DataPlaneRbacStore = UseStore>; export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({ dataPlaneRbacEnabled: false, })); export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ explorer, }: { explorer: Explorer; }): JSX.Element => { const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const [isExecuting, setIsExecuting] = useState(false); const [refreshExplorer, setRefreshExplorer] = useState(false); const [pageOption, setPageOption] = useState( LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage ? Constants.Queries.UnlimitedPageOption : Constants.Queries.CustomPageOption, ); const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState( LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled) ? LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) : Constants.RBACOptions.setAutomaticRBACOption, ); const [showDataPlaneRBACWarning, setShowDataPlaneRBACWarning] = useState(false); const [ruThresholdEnabled, setRUThresholdEnabled] = useState(isRUThresholdEnabled()); const [ruThreshold, setRUThreshold] = useState(getRUThreshold()); const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState( LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled), ); const [queryTimeout, setQueryTimeout] = useState(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout)); const [defaultQueryResultsView, setDefaultQueryResultsView] = useState( getDefaultQueryResultsView(), ); const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState( LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout), ); const [customItemPerPage, setCustomItemPerPage] = useState( LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0, ); const [containerPaginationEnabled, setContainerPaginationEnabled] = useState( LocalStorageUtility.hasItem(StorageKey.ContainerPaginationEnabled) ? LocalStorageUtility.getEntryString(StorageKey.ContainerPaginationEnabled) === "true" : false, ); const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState( LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) ? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true" : false, ); const [graphAutoVizDisabled, setGraphAutoVizDisabled] = useState( LocalStorageUtility.hasItem(StorageKey.IsGraphAutoVizDisabled) ? LocalStorageUtility.getEntryString(StorageKey.IsGraphAutoVizDisabled) : "false", ); const [retryAttempts, setRetryAttempts] = useState( LocalStorageUtility.hasItem(StorageKey.RetryAttempts) ? LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts) : Constants.Queries.DefaultRetryAttempts, ); const [retryInterval, setRetryInterval] = useState( LocalStorageUtility.hasItem(StorageKey.RetryInterval) ? LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval) : Constants.Queries.DefaultRetryIntervalInMs, ); const [MaxWaitTimeInSeconds, setMaxWaitTimeInSeconds] = useState( LocalStorageUtility.hasItem(StorageKey.MaxWaitTimeInSeconds) ? LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds) : Constants.Queries.DefaultMaxWaitTimeInSeconds, ); const [maxDegreeOfParallelism, setMaxDegreeOfParallelism] = useState( LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) ? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism) : Constants.Queries.DefaultMaxDegreeOfParallelism, ); const [priorityLevel, setPriorityLevel] = useState( LocalStorageUtility.hasItem(StorageKey.PriorityLevel) ? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel) : Constants.PriorityLevel.Default, ); const [copilotSampleDBEnabled, setCopilotSampleDBEnabled] = useState( LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true", ); const explorerVersion = configContext.gitSha; const shouldShowQueryPageOptions = userContext.apiType === "SQL"; const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin"; const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin"; const shouldShowParallelismOption = userContext.apiType !== "Gremlin"; const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled(); const shouldShowCopilotSampleDBOption = userContext.apiType === "SQL" && useQueryCopilot.getState().copilotEnabled && useDatabases.getState().sampleDataResourceTokenCollection; const handlerOnSubmit = async () => { setIsExecuting(true); LocalStorageUtility.setEntryNumber( StorageKey.ActualItemPerPage, isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage, ); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); LocalStorageUtility.setEntryString(StorageKey.DataPlaneRbacEnabled, enableDataPlaneRBACOption); if ( enableDataPlaneRBACOption === Constants.RBACOptions.setTrueRBACOption || (enableDataPlaneRBACOption === Constants.RBACOptions.setAutomaticRBACOption && userContext.databaseAccount.properties.disableLocalAuth) ) { updateUserContext({ dataPlaneRbacEnabled: true, hasDataPlaneRbacSettingChanged: true, }); useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true }); } else { updateUserContext({ dataPlaneRbacEnabled: false, hasDataPlaneRbacSettingChanged: true, }); const { databaseAccount: account, subscriptionId, resourceGroup } = userContext; if (!userContext.features.enableAadDataPlane && !userContext.masterKey) { let keys; try { keys = await listKeys(subscriptionId, resourceGroup, account.name); updateUserContext({ masterKey: keys.primaryMasterKey, }); } catch (error) { // if listKeys fail because of permissions issue, then make call to get ReadOnlyKeys if (error.code === "AuthorizationFailed") { keys = await getReadOnlyKeys(subscriptionId, resourceGroup, account.name); updateUserContext({ masterKey: keys.primaryReadonlyMasterKey, }); } else { logConsoleError(`Error occurred fetching keys for the account." ${error.message}`); throw error; } } useDataPlaneRbac.setState({ dataPlaneRbacEnabled: false }); } } LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); LocalStorageUtility.setEntryNumber(StorageKey.RetryInterval, retryInterval); LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds); LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString()); LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.DefaultQueryResultsView, defaultQueryResultsView); if (shouldShowGraphAutoVizOption) { LocalStorageUtility.setEntryBoolean( StorageKey.IsGraphAutoVizDisabled, StringUtility.toBoolean(graphAutoVizDisabled), ); } if (ruThresholdEnabled) { LocalStorageUtility.setEntryNumber(StorageKey.RUThreshold, ruThreshold); } if (queryTimeoutEnabled) { LocalStorageUtility.setEntryNumber(StorageKey.QueryTimeout, queryTimeout); LocalStorageUtility.setEntryBoolean( StorageKey.AutomaticallyCancelQueryAfterTimeout, automaticallyCancelQueryAfterTimeout, ); } setIsExecuting(false); logConsoleInfo( `Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`, ); logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`); logConsoleInfo( `Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber( StorageKey.MaxDegreeOfParellism, )}`, ); logConsoleInfo(`Updated priority level setting to ${LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)}`); if (shouldShowGraphAutoVizOption) { logConsoleInfo( `Graph result will be displayed as ${ LocalStorageUtility.getEntryBoolean(StorageKey.IsGraphAutoVizDisabled) ? "JSON" : "Graph" }`, ); } logConsoleInfo( `Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`, ); refreshExplorer && (await explorer.refreshExplorer()); closeSidePanel(); }; const isCustomPageOptionSelected = () => { return pageOption === Constants.Queries.CustomPageOption; }; const handleOnGremlinChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { setGraphAutoVizDisabled(option.key); }; const genericPaneProps: RightPaneFormProps = { formError: "", isExecuting, submitButtonText: "Apply", onSubmit: () => handlerOnSubmit(), }; const pageOptionList: IChoiceGroupOption[] = [ { key: Constants.Queries.CustomPageOption, text: "Custom" }, { key: Constants.Queries.UnlimitedPageOption, text: "Unlimited" }, ]; const graphAutoOptionList: IChoiceGroupOption[] = [ { key: "false", text: "Graph" }, { key: "true", text: "JSON" }, ]; const priorityLevelOptionList: IChoiceGroupOption[] = [ { key: Constants.PriorityLevel.Low, text: "Low" }, { key: Constants.PriorityLevel.High, text: "High" }, ]; const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [ { key: Constants.RBACOptions.setAutomaticRBACOption, text: "Automatic" }, { key: Constants.RBACOptions.setTrueRBACOption, text: "True" }, { key: Constants.RBACOptions.setFalseRBACOption, text: "False" }, ]; const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [ { key: SplitterDirection.Vertical, text: "Vertical" }, { key: SplitterDirection.Horizontal, text: "Horizontal" }, ]; const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, ): void => { setPriorityLevel(option.key); }; const handleOnPageOptionChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { setPageOption(option.key); }; const handleOnDataPlaneRBACOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, ): void => { setEnableDataPlaneRBACOption(option.key); const shouldShowWarning = (option.key === Constants.RBACOptions.setTrueRBACOption || (option.key === Constants.RBACOptions.setAutomaticRBACOption && userContext.databaseAccount.properties.disableLocalAuth === true)) && !useDataPlaneRbac.getState().aadTokenUpdated; setShowDataPlaneRBACWarning(shouldShowWarning); }; const handleOnRUThresholdToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setRUThresholdEnabled(checked); }; const handleOnRUThresholdSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const ruThreshold = Number(newValue); if (!isNaN(ruThreshold)) { setRUThreshold(ruThreshold); } }; const handleOnQueryTimeoutToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setQueryTimeoutEnabled(checked); }; const handleOnAutomaticallyCancelQueryToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setAutomaticallyCancelQueryAfterTimeout(checked); }; const handleOnQueryTimeoutSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const queryTimeout = Number(newValue); if (!isNaN(queryTimeout)) { setQueryTimeout(queryTimeout); } }; const handleOnDefaultQueryResultsViewChange = ( ev: React.MouseEvent, option: IChoiceGroupOption, ): void => { setDefaultQueryResultsView(option.key as SplitterDirection); }; const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const retryAttempts = Number(newValue); if (!isNaN(retryAttempts)) { setRetryAttempts(retryAttempts); } }; const handleOnRetryIntervalSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const retryInterval = Number(newValue); if (!isNaN(retryInterval)) { setRetryInterval(retryInterval); } }; const handleOnMaxWaitTimeSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const MaxWaitTimeInSeconds = Number(newValue); if (!isNaN(MaxWaitTimeInSeconds)) { setMaxWaitTimeInSeconds(MaxWaitTimeInSeconds); } }; const handleSampleDatabaseChange = async (ev: React.MouseEvent, checked?: boolean): Promise => { setCopilotSampleDBEnabled(checked); useQueryCopilot.getState().setCopilotSampleDBEnabled(checked); setRefreshExplorer(false); }; const choiceButtonStyles = { root: { clear: "both", }, flexContainer: [ { selectors: { ".ms-ChoiceFieldGroup root-133": { clear: "both", }, ".ms-ChoiceField-wrapper label": { fontSize: 12, paddingTop: 0, }, ".ms-ChoiceField": { marginTop: 0, }, }, }, ], }; const toggleStyles: IToggleStyles = { label: { fontSize: 12, fontWeight: 400, display: "block", }, root: {}, container: {}, pill: {}, thumb: {}, text: {}, }; const spinButtonStyles: ISpinButtonStyles = { label: { fontSize: 12, fontWeight: 400, }, root: { paddingBottom: 10, }, labelWrapper: {}, icon: {}, spinButtonWrapper: {}, input: {}, arrowButtonsContainer: {}, }; return (
{shouldShowQueryPageOptions && (
Page Options Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
{isCustomPageOptionSelected() && (
Query results per page Enter the number of query results that should be shown per page.
{ setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage); }} onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)} onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)} min={1} step={1} className="textfontclr" incrementButtonAriaLabel="Increase value by 1" decrementButtonAriaLabel="Decrease value by 1" />
)}
)} {userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && ( <>
Enable Entra ID RBAC Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID RBAC. {" "} Learn more{" "} } > {showDataPlaneRBACWarning && configContext.platform === Platform.Portal && ( setShowDataPlaneRBACWarning(false)} dismissButtonAriaLabel="Close" > Please click on "Login for Entra ID RBAC" prior to performing Entra ID RBAC operations )}
)} {userContext.apiType === "SQL" && ( <>
RU Threshold If a query exceeds a configured RU threshold, the query will be aborted.
{ruThresholdEnabled && (
)}
Query Timeout When a query reaches a specified time limit, a popup with an option to cancel the query will show unless automatic cancellation has been enabled
{queryTimeoutEnabled && (
)}
Default Query Results View Select the default view to use when displaying query results.
)}
Retry Settings Retry policy associated with throttled requests during CosmosDB queries.
Max retry attempts Max number of retries to be performed for a request. Default value 9.
setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)} onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)} onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)} styles={spinButtonStyles} />
Fixed retry interval (ms) Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response. Default value is 0 milliseconds.
setRetryInterval(parseInt(newValue) + 1000 || retryInterval)} onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)} onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)} styles={spinButtonStyles} />
Max wait time (s) Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds.
setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)} onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)} onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)} styles={spinButtonStyles} />
Enable container pagination Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
setContainerPaginationEnabled(!containerPaginationEnabled)} />
{shouldShowCrossPartitionOption && (
Enable cross-partition query Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)} />
)} {shouldShowParallelismOption && (
Max degree of parallelism Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)} onDecrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)} onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)} ariaLabel="Max degree of parallelism" />
)} {shouldShowPriorityLevelOption && (
Priority Level Sets the priority level for data-plane requests from Data Explorer when using Priority-Based Execution. If "None" is selected, Data Explorer will not specify priority level, and the server-side default priority level will be used.
)} {shouldShowGraphAutoVizOption && (
Display Gremlin query results as:  Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.
)} {shouldShowCopilotSampleDBOption && (
Enable sample database This is a sample database and collection with synthetic product data you can use to explore using NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is created by, and maintained by Microsoft at no cost to you.
)}
Explorer Version
{explorerVersion}
); };