diff --git a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx index 6337f947e..b6e847d54 100644 --- a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx +++ b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx @@ -31,7 +31,7 @@ export interface CommandButtonComponentProps { /** * Click handler for command button click */ - onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void; + onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void; /** * Label for the button diff --git a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx index fc67ad894..0c9de46ce 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx @@ -60,14 +60,16 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, iconName: btn.iconName, }, - onClick: (ev?: React.MouseEvent | React.KeyboardEvent) => { - btn.onCommandClick(ev); - let copilotEnabled = false; - if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { - copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; - } - TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); - }, + onClick: btn.onCommandClick + ? (ev?: React.MouseEvent | React.KeyboardEvent) => { + btn.onCommandClick(ev); + let copilotEnabled = false; + if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { + copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; + } + TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); + } + : undefined, key: `${btn.commandButtonLabel}${index}`, text: label, "data-test": label, diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 9b2412c4b..ff96d6716 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -9,12 +9,14 @@ import { Toggle, } from "@fluentui/react"; import * as Constants from "Common/Constants"; +import { SplitterDirection } from "Common/Splitter"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import { configContext } from "ConfigContext"; import { DefaultRUThreshold, LocalStorageUtility, StorageKey, + getDefaultQueryResultsView, getRUThreshold, ruThresholdEnabled as isRUThresholdEnabled, } from "Shared/StorageUtility"; @@ -47,6 +49,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ 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), ); @@ -121,6 +126,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ 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( @@ -197,6 +203,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ { key: Constants.PriorityLevel.High, text: "High" }, ]; + const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [ + { key: SplitterDirection.Vertical, text: "Vertical" }, + { key: SplitterDirection.Horizontal, text: "Horizontal" }, + ]; + const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, @@ -234,6 +245,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ } }; + 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)) { @@ -438,6 +456,25 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )} +
+
+
+ + Default Query Results View + + Select the default view to use when displaying query results. +
+
+ +
+
+
)}
diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 6f4ac0c06..f887a53dd 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -205,6 +205,67 @@ exports[`Settings Pane should render Default properly 1`] = `
+
+
+
+ + Default Query Results View + + + Select the default view to use when displaying query results. + +
+
+ +
+
+
diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 532ce4662..bfcaa9bda 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ import { FeedOptions, QueryOperationOptions } from "@azure/cosmos"; +import { SplitterDirection } from "Common/Splitter"; import { Platform, configContext } from "ConfigContext"; import { useDialog } from "Explorer/Controls/Dialog"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; @@ -12,7 +13,13 @@ import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { KeyboardAction } from "KeyboardShortcuts"; import { QueryConstants } from "Shared/Constants"; -import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility"; +import { + LocalStorageUtility, + StorageKey, + getDefaultQueryResultsView, + getRUThreshold, + ruThresholdEnabled, +} from "Shared/StorageUtility"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { TabsState, useTabs } from "hooks/useTabs"; @@ -25,6 +32,7 @@ import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; import DownloadQueryIcon from "../../../../images/DownloadQuery.svg"; import CancelQueryIcon from "../../../../images/Entity_cancel.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; +import CheckIcon from "../../../../images/check-1.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg"; import { NormalizedEventKey } from "../../../Common/Constants"; import { getErrorMessage } from "../../../Common/ErrorHandlingUtils"; @@ -103,6 +111,7 @@ interface IQueryTabStates { cancelQueryTimeoutID: NodeJS.Timeout; copilotActive: boolean; currentTabActive: boolean; + queryResultsView: SplitterDirection; } export const QueryTabFunctionComponent = (props: IQueryTabComponentProps): any => { @@ -147,6 +156,7 @@ export default class QueryTabComponent extends React.Component this._setViewLayout(SplitterDirection.Vertical), + hasPopup: false, + }; + const horizontalButton: CommandButtonComponentProps = { + isSelected: this.state.queryResultsView === SplitterDirection.Horizontal, + iconSrc: this.state.queryResultsView === SplitterDirection.Horizontal ? CheckIcon : undefined, + commandButtonLabel: "Horizontal", + ariaLabel: "Horizontal", + onCommandClick: () => this._setViewLayout(SplitterDirection.Horizontal), + hasPopup: false, + }; + + return { + commandButtonLabel: "View", + ariaLabel: "View", + hasPopup: true, + children: [verticalButton, horizontalButton], + }; + } + private _setViewLayout(direction: SplitterDirection): void { + this.setState({ queryResultsView: direction }); + + // We'll need to refresh the context buttons to update the selected state of the view buttons + setTimeout(() => { + useCommandBar.getState().setContextButtons(this.getTabsButtons()); + }, 100); + } + private _toggleCopilot = (active: boolean) => { this.setState({ copilotActive: active }); useQueryCopilot.getState().setCopilotEnabledforExecution(active); @@ -634,7 +680,12 @@ export default class QueryTabComponent extends React.Component )}
- +
{ @@ -51,4 +53,12 @@ export const getRUThreshold = (): number => { return DefaultRUThreshold; }; +export const getDefaultQueryResultsView = (): SplitterDirection => { + const defaultQueryResultsViewRaw = LocalStorageUtility.getEntryString(StorageKey.DefaultQueryResultsView); + if (defaultQueryResultsViewRaw === SplitterDirection.Horizontal) { + return SplitterDirection.Horizontal; + } + return SplitterDirection.Vertical; +}; + export const DefaultRUThreshold = 5000;