Allow query result view to be toggled from command bar (#1833)
* allow query result view to be toggled from command bar also provides a default results view option that's stored in the browser's local storage * update SettingsPane test snapshot
This commit is contained in:
parent
7002da0b51
commit
9b12775151
|
@ -31,7 +31,7 @@ export interface CommandButtonComponentProps {
|
||||||
/**
|
/**
|
||||||
* Click handler for command button click
|
* Click handler for command button click
|
||||||
*/
|
*/
|
||||||
onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the button
|
* Label for the button
|
||||||
|
|
|
@ -60,14 +60,16 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||||
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
||||||
iconName: btn.iconName,
|
iconName: btn.iconName,
|
||||||
},
|
},
|
||||||
onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
onClick: btn.onCommandClick
|
||||||
btn.onCommandClick(ev);
|
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
||||||
let copilotEnabled = false;
|
btn.onCommandClick(ev);
|
||||||
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
|
let copilotEnabled = false;
|
||||||
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
|
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
|
||||||
}
|
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
|
||||||
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled });
|
}
|
||||||
},
|
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled });
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
key: `${btn.commandButtonLabel}${index}`,
|
key: `${btn.commandButtonLabel}${index}`,
|
||||||
text: label,
|
text: label,
|
||||||
"data-test": label,
|
"data-test": label,
|
||||||
|
|
|
@ -9,12 +9,14 @@ import {
|
||||||
Toggle,
|
Toggle,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import * as Constants from "Common/Constants";
|
import * as Constants from "Common/Constants";
|
||||||
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
import {
|
import {
|
||||||
DefaultRUThreshold,
|
DefaultRUThreshold,
|
||||||
LocalStorageUtility,
|
LocalStorageUtility,
|
||||||
StorageKey,
|
StorageKey,
|
||||||
|
getDefaultQueryResultsView,
|
||||||
getRUThreshold,
|
getRUThreshold,
|
||||||
ruThresholdEnabled as isRUThresholdEnabled,
|
ruThresholdEnabled as isRUThresholdEnabled,
|
||||||
} from "Shared/StorageUtility";
|
} from "Shared/StorageUtility";
|
||||||
|
@ -47,6 +49,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||||
LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled),
|
LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled),
|
||||||
);
|
);
|
||||||
const [queryTimeout, setQueryTimeout] = useState<number>(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout));
|
const [queryTimeout, setQueryTimeout] = useState<number>(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout));
|
||||||
|
const [defaultQueryResultsView, setDefaultQueryResultsView] = useState<SplitterDirection>(
|
||||||
|
getDefaultQueryResultsView(),
|
||||||
|
);
|
||||||
const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState<boolean>(
|
const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState<boolean>(
|
||||||
LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout),
|
LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout),
|
||||||
);
|
);
|
||||||
|
@ -121,6 +126,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
||||||
LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString());
|
LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString());
|
||||||
|
LocalStorageUtility.setEntryString(StorageKey.DefaultQueryResultsView, defaultQueryResultsView);
|
||||||
|
|
||||||
if (shouldShowGraphAutoVizOption) {
|
if (shouldShowGraphAutoVizOption) {
|
||||||
LocalStorageUtility.setEntryBoolean(
|
LocalStorageUtility.setEntryBoolean(
|
||||||
|
@ -197,6 +203,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [
|
||||||
|
{ key: SplitterDirection.Vertical, text: "Vertical" },
|
||||||
|
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
||||||
|
];
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
|
@ -234,6 +245,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnDefaultQueryResultsViewChange = (
|
||||||
|
ev: React.MouseEvent<HTMLElement>,
|
||||||
|
option: IChoiceGroupOption,
|
||||||
|
): void => {
|
||||||
|
setDefaultQueryResultsView(option.key as SplitterDirection);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent<HTMLElement>, newValue?: string): void => {
|
const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent<HTMLElement>, newValue?: string): void => {
|
||||||
const retryAttempts = Number(newValue);
|
const retryAttempts = Number(newValue);
|
||||||
if (!isNaN(retryAttempts)) {
|
if (!isNaN(retryAttempts)) {
|
||||||
|
@ -438,6 +456,25 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="settingsSection">
|
||||||
|
<div className="settingsSectionPart">
|
||||||
|
<div>
|
||||||
|
<legend id="defaultQueryResultsView" className="settingsSectionLabel legendLabel">
|
||||||
|
Default Query Results View
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>Select the default view to use when displaying query results.</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ChoiceGroup
|
||||||
|
ariaLabelledBy="defaultQueryResultsView"
|
||||||
|
selectedKey={defaultQueryResultsView}
|
||||||
|
options={defaultQueryResultsViewOptionList}
|
||||||
|
styles={choiceButtonStyles}
|
||||||
|
onChange={handleOnDefaultQueryResultsViewChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
|
|
|
@ -205,6 +205,67 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="settingsSection"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionPart"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<legend
|
||||||
|
className="settingsSectionLabel legendLabel"
|
||||||
|
id="defaultQueryResultsView"
|
||||||
|
>
|
||||||
|
Default Query Results View
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>
|
||||||
|
Select the default view to use when displaying query results.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<StyledChoiceGroup
|
||||||
|
ariaLabelledBy="defaultQueryResultsView"
|
||||||
|
onChange={[Function]}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"key": "vertical",
|
||||||
|
"text": "Vertical",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "horizontal",
|
||||||
|
"text": "Horizontal",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedKey="vertical"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"flexContainer": Array [
|
||||||
|
Object {
|
||||||
|
"selectors": Object {
|
||||||
|
".ms-ChoiceField": Object {
|
||||||
|
"marginTop": 0,
|
||||||
|
},
|
||||||
|
".ms-ChoiceField-wrapper label": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
"paddingTop": 0,
|
||||||
|
},
|
||||||
|
".ms-ChoiceFieldGroup root-133": Object {
|
||||||
|
"clear": "both",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"root": Object {
|
||||||
|
"clear": "both",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { FeedOptions, QueryOperationOptions } from "@azure/cosmos";
|
import { FeedOptions, QueryOperationOptions } from "@azure/cosmos";
|
||||||
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||||
|
@ -12,7 +13,13 @@ import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
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 { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { TabsState, useTabs } from "hooks/useTabs";
|
import { TabsState, useTabs } from "hooks/useTabs";
|
||||||
|
@ -25,6 +32,7 @@ import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
|
import CheckIcon from "../../../../images/check-1.svg";
|
||||||
import SaveQueryIcon from "../../../../images/save-cosmos.svg";
|
import SaveQueryIcon from "../../../../images/save-cosmos.svg";
|
||||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
@ -103,6 +111,7 @@ interface IQueryTabStates {
|
||||||
cancelQueryTimeoutID: NodeJS.Timeout;
|
cancelQueryTimeoutID: NodeJS.Timeout;
|
||||||
copilotActive: boolean;
|
copilotActive: boolean;
|
||||||
currentTabActive: boolean;
|
currentTabActive: boolean;
|
||||||
|
queryResultsView: SplitterDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QueryTabFunctionComponent = (props: IQueryTabComponentProps): any => {
|
export const QueryTabFunctionComponent = (props: IQueryTabComponentProps): any => {
|
||||||
|
@ -147,6 +156,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
cancelQueryTimeoutID: undefined,
|
cancelQueryTimeoutID: undefined,
|
||||||
copilotActive: this._queryCopilotActive(),
|
copilotActive: this._queryCopilotActive(),
|
||||||
currentTabActive: true,
|
currentTabActive: true,
|
||||||
|
queryResultsView: getDefaultQueryResultsView(),
|
||||||
};
|
};
|
||||||
this.isCloseClicked = false;
|
this.isCloseClicked = false;
|
||||||
this.splitterId = this.props.tabId + "_splitter";
|
this.splitterId = this.props.tabId + "_splitter";
|
||||||
|
@ -508,9 +518,45 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buttons.push(this.createViewButtons());
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createViewButtons(): CommandButtonComponentProps {
|
||||||
|
const verticalButton: CommandButtonComponentProps = {
|
||||||
|
isSelected: this.state.queryResultsView === SplitterDirection.Vertical,
|
||||||
|
iconSrc: this.state.queryResultsView === SplitterDirection.Vertical ? CheckIcon : undefined,
|
||||||
|
commandButtonLabel: "Vertical",
|
||||||
|
ariaLabel: "Vertical",
|
||||||
|
onCommandClick: () => 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) => {
|
private _toggleCopilot = (active: boolean) => {
|
||||||
this.setState({ copilotActive: active });
|
this.setState({ copilotActive: active });
|
||||||
useQueryCopilot.getState().setCopilotEnabledforExecution(active);
|
useQueryCopilot.getState().setCopilotEnabledforExecution(active);
|
||||||
|
@ -634,7 +680,12 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)}
|
)}
|
||||||
<div className="tabPaneContentContainer">
|
<div className="tabPaneContentContainer">
|
||||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
<SplitterLayout
|
||||||
|
vertical={this.state.queryResultsView === SplitterDirection.Vertical}
|
||||||
|
primaryIndex={0}
|
||||||
|
primaryMinSize={100}
|
||||||
|
secondaryMinSize={200}
|
||||||
|
>
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="queryEditor" style={{ height: "100%" }}>
|
<div className="queryEditor" style={{ height: "100%" }}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import * as LocalStorageUtility from "./LocalStorageUtility";
|
import * as LocalStorageUtility from "./LocalStorageUtility";
|
||||||
import * as SessionStorageUtility from "./SessionStorageUtility";
|
import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||||
import * as StringUtility from "./StringUtility";
|
import * as StringUtility from "./StringUtility";
|
||||||
|
@ -27,6 +28,7 @@ export enum StorageKey {
|
||||||
GalleryCalloutDismissed,
|
GalleryCalloutDismissed,
|
||||||
VisitedAccounts,
|
VisitedAccounts,
|
||||||
PriorityLevel,
|
PriorityLevel,
|
||||||
|
DefaultQueryResultsView,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasRUThresholdBeenConfigured = (): boolean => {
|
export const hasRUThresholdBeenConfigured = (): boolean => {
|
||||||
|
@ -51,4 +53,12 @@ export const getRUThreshold = (): number => {
|
||||||
return DefaultRUThreshold;
|
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;
|
export const DefaultRUThreshold = 5000;
|
||||||
|
|
Loading…
Reference in New Issue