add new command bar behind a feature flag

This commit is contained in:
Ashley Stanton-Nurse 2024-08-08 11:11:45 -07:00 committed by GitHub
parent 7128133874
commit 53d3413c62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 308 additions and 338 deletions

View File

@ -2618,6 +2618,7 @@ a:link {
.tabPanesContainer { .tabPanesContainer {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
flex-direction: column;
overflow: hidden; overflow: hidden;
} }

View File

@ -1,23 +1,14 @@
/** /**
* React component for Command button component. * React component for Command button component.
*/ */
import Explorer from "Explorer/Explorer";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import * as React from "react"; import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as StringUtils from "../../../Utils/StringUtils";
/** /**
* Options for this component * Options for this component
*/ */
export interface CommandButtonComponentProps { export interface CommandButtonComponentProps {
/**
* font icon name for the button
*/
iconName?: string;
/** /**
* image source for the button icon * image source for the button icon
*/ */
@ -31,7 +22,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, container: Explorer) => void;
/** /**
* Label for the button * Label for the button
@ -120,157 +111,3 @@ export interface CommandButtonComponentProps {
*/ */
keyboardAction?: KeyboardAction; keyboardAction?: KeyboardAction;
} }
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
private dropdownElt: HTMLElement;
private expandButtonElt: HTMLElement;
public componentDidUpdate(): void {
if (!this.dropdownElt || !this.expandButtonElt) {
return;
}
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
}
private onKeyPress(event: React.KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
this.commandClickCallback && this.commandClickCallback(event);
event.stopPropagation();
return false;
}
return true;
}
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide();
$(this.dropdownElt).show().focus();
event.stopPropagation();
return false;
}
if (event.keyCode === KeyCodes.UpArrow) {
$(this.dropdownElt).hide();
event.stopPropagation();
return false;
}
return true;
}
private getCommandButtonId(): string {
if (this.props.id) {
return this.props.id;
} else {
return `commandButton-${StringUtils.stripSpacesFromString(this.props.commandButtonLabel)}`;
}
}
public static renderButton(options: CommandButtonComponentProps, key?: string): JSX.Element {
return <CommandButtonComponent key={key} {...options} />;
}
private commandClickCallback(e: React.SyntheticEvent): void {
if (this.props.disabled) {
return;
}
// TODO Query component's parent, not document
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
if (el) {
el.style.display = "none";
}
this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel,
});
}
private renderChildren(): JSX.Element {
if (!this.props.children || this.props.children.length < 1) {
return <React.Fragment />;
}
return (
<div
className="commandExpand"
tabIndex={0}
ref={(ref: HTMLElement) => {
this.expandButtonElt = ref;
}}
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
>
<div className="commandDropdownLauncher">
<span className="partialSplitter" />
<span className="expandDropdown">
<img src={CollapseChevronDownIcon} />
</span>
</div>
<div
className="commandDropdownContainer"
ref={(ref: HTMLElement) => {
this.dropdownElt = ref;
}}
>
<div className="commandDropdown">
{this.props.children.map((c: CommandButtonComponentProps, index: number): JSX.Element => {
return CommandButtonComponent.renderButton(c, `${index}`);
})}
</div>
</div>
</div>
);
}
public static renderLabel(
props: CommandButtonComponentProps,
key?: string,
refct?: (input: HTMLElement) => void,
): JSX.Element {
if (!props.commandButtonLabel) {
return <React.Fragment />;
}
return (
<span className="commandLabel" key={key} ref={refct}>
{props.commandButtonLabel}
</span>
);
}
public render(): JSX.Element {
let mainClassName = "commandButtonComponent";
if (this.props.disabled) {
mainClassName += " commandDisabled";
}
if (this.props.isSelected) {
mainClassName += " selectedButton";
}
let contentClassName = "commandContent";
if (this.props.children && this.props.children.length > 0) {
contentClassName += " hasHiddenItems";
}
return (
<div className="commandButtonReact">
<span
className={mainClassName}
role="menuitem"
tabIndex={this.props.tabIndex}
onKeyPress={(e: React.KeyboardEvent<HTMLSpanElement>) => this.onKeyPress(e)}
title={this.props.tooltipText}
id={this.getCommandButtonId()}
aria-disabled={this.props.disabled}
aria-haspopup={this.props.hasPopup}
aria-label={this.props.ariaLabel}
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
>
<div className={contentClassName}>
<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />
{CommandButtonComponent.renderLabel(this.props)}
</div>
</span>
{this.props.children && this.renderChildren()}
</div>
);
}
}

View File

@ -7,6 +7,7 @@ import {
ContainerVectorPolicyComponent, ContainerVectorPolicyComponent,
ContainerVectorPolicyComponentProps, ContainerVectorPolicyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
@ -32,7 +33,6 @@ import {
PartitionKeyComponent, PartitionKeyComponent,
PartitionKeyComponentProps, PartitionKeyComponentProps,
} from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent"; } from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";

View File

@ -107,7 +107,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
const onOpenChange = useCallback( const onOpenChange = useCallback(
(_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
if (data.type === "Click" && !isBranch && node.onClick) { if (data.type === "Click" && node.onClick) {
node.onClick(); node.onClick();
} }
if (!node.isExpanded && data.open && node.onExpanded) { if (!node.isExpanded && data.open && node.onExpanded) {
@ -119,7 +119,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
node.onCollapsed?.(); node.onCollapsed?.();
} }
}, },
[isBranch, node, setIsLoading], [node, setIsLoading],
); );
const onMenuOpenChange = useCallback( const onMenuOpenChange = useCallback(

View File

@ -5,6 +5,7 @@ import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
@ -47,7 +48,6 @@ import { useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, useDialog } from "./Controls/Dialog"; import { DialogProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { SnapshotRequest } from "./Notebook/NotebookComponent/types"; import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";

View File

@ -4,83 +4,51 @@
* and update any knockout observables passed from the parent. * and update any knockout observables passed from the parent.
*/ */
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import * as React from "react"; import * as React from "react";
import create, { UseStore } from "zustand";
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants"; import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
interface Props { interface Props {
container: Explorer; container: Explorer;
} }
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => { export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const selectedNodeState = useSelectedNode(); const selectedNodeState = useSelectedNode();
const buttons = useCommandBar((state) => state.contextButtons); const contextButtons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden); const isHidden = useCommandBar((state) => state.isHidden);
const backgroundColor = StyleConstants.BaseLight; const backgroundColor = StyleConstants.BaseLight;
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor, container);
const buttons = if (contextButtons?.length > 0) {
userContext.apiType === "Postgres"
? CommandBarComponentButtonFactory.createPostgreButtons(container)
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
return (
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
}
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState),
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (buttons && buttons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
} }
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor); const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(
contextButtons || [],
backgroundColor,
container,
);
if (uiFabricTabsButtons.length > 0) { if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider")); uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
} }
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); const uiFabricPlatformButtons = CommandBarUtil.convertButton(platformButtons || [], backgroundColor, container);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricPlatformButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
@ -88,7 +56,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
(useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) && (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) &&
connectionInfo?.status !== ConnectionStatusType.Connect connectionInfo?.status !== ConnectionStatusType.Connect
) { ) {
uiFabricControlButtons.unshift( uiFabricPlatformButtons.unshift(
CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"), CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"),
); );
} }
@ -107,8 +75,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
}, },
}; };
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons); const allButtons = staticButtons.concat(contextButtons).concat(platformButtons);
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons); const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons, container);
setKeyboardHandlers(keyboardHandlers); setKeyboardHandlers(keyboardHandlers);
return ( return (
@ -116,7 +84,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
<FluentCommandBar <FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands" ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)} items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons} farItems={uiFabricPlatformButtons}
styles={rootStyle} styles={rootStyle}
overflowButtonProps={{ ariaLabel: "More commands" }} overflowButtonProps={{ ariaLabel: "More commands" }}
/> />

View File

@ -3,15 +3,12 @@ import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels"; import { CollectionBase } from "../../../Contracts/ViewModels";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined)); afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => { describe("Enable Azure Synapse Link Button", () => {
@ -19,7 +16,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@ -30,7 +26,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Button should be visible", () => { it("Button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@ -46,7 +42,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@ -62,7 +58,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@ -75,7 +71,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@ -108,7 +103,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@ -118,13 +113,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@ -134,12 +129,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openPostgresShellButtonLabel = "Open PSQL shell"; const openPostgresShellButtonLabel = "Open PSQL shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell"; const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell";
beforeAll(() => {
mockExplorer = {} as Explorer;
});
it("creates Postgres shell button", () => { it("creates Postgres shell button", () => {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createPostgreButtons();
const openPostgresShellButton = buttons.find( const openPostgresShellButton = buttons.find(
(button) => button.commandButtonLabel === openPostgresShellButtonLabel, (button) => button.commandButtonLabel === openPostgresShellButtonLabel,
); );
@ -147,7 +138,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("creates vCore Mongo shell button", () => { it("creates vCore Mongo shell button", () => {
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons();
const openVCoreMongoShellButton = buttons.find( const openVCoreMongoShellButton = buttons.find(
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel, (button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel,
); );
@ -162,8 +153,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken, authType: AuthType.ResourceToken,
}); });
@ -175,7 +164,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB", kind: "DocumentDB",
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState);
expect(buttons.length).toBe(2); expect(buttons.length).toBe(2);
expect(buttons[0].commandButtonLabel).toBe("New SQL Query"); expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
expect(buttons[0].disabled).toBe(false); expect(buttons[0].disabled).toBe(false);

View File

@ -21,7 +21,6 @@ import { userContext } from "../../../UserContext";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen"; import { OpenFullScreen } from "../../OpenFullScreen";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
@ -32,19 +31,20 @@ import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode";
let counter = 0; let counter = 0;
export function createStaticCommandBarButtons( export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
container: Explorer, if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
selectedNodeState: SelectedNodeState, return userContext.apiType === "Postgres" ? createPostgreButtons() : createVCoreMongoButtons();
): CommandButtonComponentProps[] { }
if (userContext.authType === AuthType.ResourceToken) { if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState); return createStaticCommandBarButtonsForResourceToken(selectedNodeState);
} }
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
// Avoid starting with a divider // Avoid starting with a divider
const addDivider = () => { const addDivider = () => {
if (buttons.length > 0) { if (buttons.length > 0 && !buttons[buttons.length - 1].isDivider) {
buttons.push(createDivider()); buttons.push(createDivider());
} }
}; };
@ -54,7 +54,7 @@ export function createStaticCommandBarButtons(
userContext.apiType !== "Tables" && userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra" userContext.apiType !== "Cassandra"
) { ) {
const addSynapseLink = createOpenSynapseLinkDialogButton(container); const addSynapseLink = createOpenSynapseLinkDialogButton();
if (addSynapseLink) { if (addSynapseLink) {
addDivider(); addDivider();
buttons.push(addSynapseLink); buttons.push(addSynapseLink);
@ -67,9 +67,9 @@ export function createStaticCommandBarButtons(
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated); const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
useEffect(() => { useEffect(() => {
const buttonProps = createLoginForEntraIDButton(container); const buttonProps = createLoginForEntraIDButton();
setLoginButtonProps(buttonProps); setLoginButtonProps(buttonProps);
}, [dataPlaneRbacEnabled, aadTokenUpdated, container]); }, [dataPlaneRbacEnabled, aadTokenUpdated]);
if (loginButtonProps) { if (loginButtonProps) {
addDivider(); addDivider();
@ -87,8 +87,8 @@ export function createStaticCommandBarButtons(
} }
if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) { if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) {
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton();
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn); buttons.push(openQueryBtn);
} }
@ -103,6 +103,7 @@ export function createStaticCommandBarButtons(
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
tooltipText: userContext.features.commandBarV2 ? "New..." : label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: disabled:
@ -115,21 +116,12 @@ export function createStaticCommandBarButtons(
} }
} }
return buttons;
}
export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell"; const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: (_, container) => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { if (useNotebook.getState().isShellEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
@ -141,6 +133,7 @@ export function createContextCommandBarButtons(
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
addDivider();
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }
@ -153,25 +146,27 @@ export function createContextCommandBarButtons(
const newCassandraShellButton: CommandButtonComponentProps = { const newCassandraShellButton: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: (_, container) => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
addDivider();
buttons.push(newCassandraShellButton); buttons.push(newCassandraShellButton);
} }
return buttons; return buttons;
} }
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createPlatformButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [ const buttons: CommandButtonComponentProps[] = [
{ {
iconSrc: SettingsIcon, iconSrc: SettingsIcon,
iconAlt: "Settings", iconAlt: "Settings",
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />), onCommandClick: (_, container) =>
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: "Settings", ariaLabel: "Settings",
tooltipText: "Settings", tooltipText: "Settings",
@ -207,7 +202,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openCESCVAFeedbackBlade(), onCommandClick: (_, container) => container.openCESCVAFeedbackBlade(),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: label, ariaLabel: label,
tooltipText: label, tooltipText: label,
@ -239,7 +234,7 @@ function areScriptsSupported(): boolean {
); );
} }
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
return undefined; return undefined;
} }
@ -257,7 +252,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
return { return {
iconSrc: SynapseIcon, iconSrc: SynapseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openEnableSynapseLinkDialog(), onCommandClick: (_, container) => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled:
@ -266,12 +261,12 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
}; };
} }
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps { function createLoginForEntraIDButton(): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) { if (configContext.platform !== Platform.Portal) {
return undefined; return undefined;
} }
const handleCommandClick = async () => { const handleCommandClick: CommandButtonComponentProps["onCommandClick"] = async (_, container) => {
await container.openLoginForEntraIDPopUp(); await container.openLoginForEntraIDPopUp();
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true }); useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
}; };
@ -398,13 +393,14 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
return buttons; return buttons;
} }
function createOpenQueryButton(container: Explorer): CommandButtonComponentProps { function createOpenQueryButton(): CommandButtonComponentProps {
const label = "Open Query"; const label = "Open Query";
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
iconAlt: label, iconAlt: label,
tooltipText: userContext.features.commandBarV2 ? "Open Query..." : "Open Query",
keyboardAction: KeyboardAction.OPEN_QUERY, keyboardAction: KeyboardAction.OPEN_QUERY,
onCommandClick: () => onCommandClick: (_, container) =>
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />), useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
@ -427,10 +423,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
}; };
} }
function createOpenTerminalButtonByKind( function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind): CommandButtonComponentProps {
container: Explorer,
terminalKind: ViewModels.TerminalKind,
): CommandButtonComponentProps {
const terminalFriendlyName = (): string => { const terminalFriendlyName = (): string => {
switch (terminalKind) { switch (terminalKind) {
case ViewModels.TerminalKind.Cassandra: case ViewModels.TerminalKind.Cassandra:
@ -453,7 +446,7 @@ function createOpenTerminalButtonByKind(
return { return {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: (_, container) => {
if (useNotebook.getState().isNotebookEnabled) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(terminalKind); container.openNotebookTerminal(terminalKind);
} }
@ -467,11 +460,10 @@ function createOpenTerminalButtonByKind(
} }
function createStaticCommandBarButtonsForResourceToken( function createStaticCommandBarButtonsForResourceToken(
container: Explorer,
selectedNodeState: SelectedNodeState, selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] { ): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState); const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton();
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection; const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
const isResourceTokenCollectionNodeSelected: boolean = const isResourceTokenCollectionNodeSelected: boolean =
@ -484,20 +476,20 @@ function createStaticCommandBarButtonsForResourceToken(
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected; openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
if (!openQueryBtn.disabled) { if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()];
} }
return [newSqlQueryBtn, openQueryBtn]; return [newSqlQueryBtn, openQueryBtn];
} }
export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] { export function createPostgreButtons(): CommandButtonComponentProps[] {
const openPostgreShellBtn = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Postgres); const openPostgreShellBtn = createOpenTerminalButtonByKind(ViewModels.TerminalKind.Postgres);
return [openPostgreShellBtn]; return [openPostgreShellBtn];
} }
export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] { export function createVCoreMongoButtons(): CommandButtonComponentProps[] {
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo); const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(ViewModels.TerminalKind.VCoreMongo);
return [openVCoreMongoTerminalButton]; return [openVCoreMongoTerminalButton];
} }

View File

@ -1,8 +1,10 @@
import { ICommandBarItemProps } from "@fluentui/react"; import { ICommandBarItemProps } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
describe("CommandBarUtil tests", () => { describe("CommandBarUtil tests", () => {
const mockExplorer = {} as Explorer;
const createButton = (): CommandButtonComponentProps => { const createButton = (): CommandButtonComponentProps => {
return { return {
iconSrc: "icon", iconSrc: "icon",
@ -22,7 +24,7 @@ describe("CommandBarUtil tests", () => {
const btn = createButton(); const btn = createButton();
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
const converteds = CommandBarUtil.convertButton([btn], backgroundColor); const converteds = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer);
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(undefined); expect(converted.split).toBe(undefined);
@ -46,7 +48,7 @@ describe("CommandBarUtil tests", () => {
btn.children.push(child); btn.children.push(child);
} }
const converteds = CommandBarUtil.convertButton([btn], "backgroundColor"); const converteds = CommandBarUtil.convertButton([btn], "backgroundColor", mockExplorer);
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(true); expect(converted.split).toBe(true);
@ -62,7 +64,7 @@ describe("CommandBarUtil tests", () => {
btns.push(createButton()); btns.push(createButton());
} }
const converteds = CommandBarUtil.convertButton(btns, "backgroundColor"); const converteds = CommandBarUtil.convertButton(btns, "backgroundColor", mockExplorer);
const uniqueKeys = converteds const uniqueKeys = converteds
.map((btn: ICommandBarItemProps) => btn.key) .map((btn: ICommandBarItemProps) => btn.key)
.filter((value: string, index: number, self: string[]) => self.indexOf(value) === index); .filter((value: string, index: number, self: string[]) => self.indexOf(value) === index);
@ -74,10 +76,10 @@ describe("CommandBarUtil tests", () => {
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
btn.commandButtonLabel = undefined; btn.commandButtonLabel = undefined;
let converted = CommandBarUtil.convertButton([btn], backgroundColor)[0]; let converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0];
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
converted = CommandBarUtil.convertButton([btn], backgroundColor)[0]; converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0];
delete btn.commandButtonLabel; delete btn.commandButtonLabel;
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
}); });

View File

@ -25,7 +25,11 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
* Convert our NavbarButtonConfig to UI Fabric buttons * Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns * @param btns
*/ */
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => { export const convertButton = (
btns: CommandButtonComponentProps[],
backgroundColor: string,
container: Explorer,
): ICommandBarItemProps[] => {
const buttonHeightPx = const buttonHeightPx =
configContext.platform == Platform.Fabric configContext.platform == Platform.Fabric
? StyleConstants.FabricCommandBarButtonHeight ? StyleConstants.FabricCommandBarButtonHeight
@ -54,15 +58,14 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
iconProps: { iconProps: {
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined, alignSelf: undefined,
filter: getFilter(btn.disabled), filter: getFilter(btn.disabled),
}, },
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName,
}, },
onClick: btn.onCommandClick onClick: btn.onCommandClick
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => { ? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev); btn.onCommandClick(ev, container);
let copilotEnabled = false; let copilotEnabled = false;
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
@ -135,7 +138,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
result.split = true; result.split = true;
result.subMenuProps = { result.subMenuProps = {
items: convertButton(btn.children, backgroundColor), items: convertButton(btn.children, backgroundColor, container),
styles: { styles: {
list: { list: {
// TODO Figure out how to do it the proper way with subComponentStyles. // TODO Figure out how to do it the proper way with subComponentStyles.
@ -186,7 +189,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
option?: IDropdownOption, option?: IDropdownOption,
index?: number, index?: number,
): void => { ): void => {
btn.children[index].onCommandClick(event); btn.children[index].onCommandClick(event, container);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text }); TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
}; };
@ -237,14 +240,17 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
}; };
}; };
export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap { export function createKeyboardHandlers(
allButtons: CommandButtonComponentProps[],
container: Explorer,
): KeyboardHandlerMap {
const handlers: KeyboardHandlerMap = {}; const handlers: KeyboardHandlerMap = {};
function createHandlers(buttons: CommandButtonComponentProps[]) { function createHandlers(buttons: CommandButtonComponentProps[]) {
buttons.forEach((button) => { buttons.forEach((button) => {
if (!button.disabled && button.keyboardAction) { if (!button.disabled && button.keyboardAction) {
handlers[button.keyboardAction] = (e) => { handlers[button.keyboardAction] = (e) => {
button.onCommandClick(e); button.onCommandClick(e, container);
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action // If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
return true; return true;

View File

@ -0,0 +1,16 @@
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import create, { UseStore } from "zustand";
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));

View File

@ -0,0 +1,159 @@
import {
makeStyles,
Menu,
MenuButton,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
Toolbar,
ToolbarButton,
ToolbarDivider,
ToolbarGroup,
Tooltip,
} from "@fluentui/react-components";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "Explorer/Explorer";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { createKeyboardHandlers } from "Explorer/Menus/CommandBar/CommandBarUtil";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import React, { MouseEventHandler } from "react";
const useToolbarStyles = makeStyles({
toolbar: {
height: tokens.layoutRowHeight,
justifyContent: "space-between", // Ensures that the two toolbar groups are at opposite ends of the toolbar
...cosmosShorthands.borderBottom(),
},
toolbarGroup: {
display: "flex",
},
});
export interface CommandBarV2Props {
explorer: Explorer;
}
export const CommandBarV2: React.FC<CommandBarV2Props> = ({ explorer }: CommandBarV2Props) => {
const styles = useToolbarStyles();
const selectedNodeState = useSelectedNode();
const contextButtons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden);
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
if (isHidden) {
setKeyboardHandlers({});
return null;
}
const allButtons = staticButtons.concat(contextButtons).concat(platformButtons);
const keyboardHandlers = createKeyboardHandlers(allButtons, explorer);
setKeyboardHandlers(keyboardHandlers);
return (
<CosmosFluentProvider>
<Toolbar className={styles.toolbar}>
<ToolbarGroup role="presentation" className={styles.toolbarGroup}>
{staticButtons.map((button, index) =>
renderButton(explorer, button, `static-${index}`, contextButtons?.length > 0),
)}
{staticButtons.length > 0 && contextButtons?.length > 0 && <ToolbarDivider />}
{contextButtons.map((button, index) => renderButton(explorer, button, `context-${index}`, false))}
</ToolbarGroup>
<ToolbarGroup role="presentation">
{platformButtons.map((button, index) => renderButton(explorer, button, `platform-${index}`, true))}
</ToolbarGroup>
</Toolbar>
</CosmosFluentProvider>
);
};
// This allows us to migrate individual buttons over to using a JSX.Element for the icon, without requiring us to change them all at once.
function renderIcon(iconSrcOrElement: string | JSX.Element, alt?: string): JSX.Element {
if (typeof iconSrcOrElement === "string") {
return <img src={iconSrcOrElement} alt={alt} />;
}
return iconSrcOrElement;
}
function renderButton(
explorer: Explorer,
btn: CommandButtonComponentProps,
key: string,
iconOnly: boolean,
): JSX.Element {
if (btn.isDivider) {
return <ToolbarDivider key={key} />;
}
const hasChildren = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const tooltip = btn.tooltipText || (iconOnly ? label : undefined);
const onClick: MouseEventHandler | undefined =
btn.onCommandClick && !hasChildren ? (e) => btn.onCommandClick(e, explorer) : undefined;
// We don't know which element will be the top-level element, so just slap a key on all of 'em
let button = hasChildren ? (
<MenuButton key={key} appearance="subtle" aria-label={btn.ariaLabel} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</MenuButton>
) : (
<ToolbarButton key={key} aria-label={btn.ariaLabel} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</ToolbarButton>
);
if (tooltip) {
button = (
<Tooltip key={key} content={tooltip} relationship="description" withArrow>
{button}
</Tooltip>
);
}
if (hasChildren) {
button = (
<Menu key={key}>
<MenuTrigger disableButtonEnhancement>{button}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return button;
}
function renderMenuItem(explorer: Explorer, btn: CommandButtonComponentProps, key: string): JSX.Element {
const hasChildren = !!btn.children && btn.children.length > 0;
const onClick: MouseEventHandler | undefined = btn.onCommandClick
? (e) => btn.onCommandClick(e, explorer)
: undefined;
const item = (
<MenuItem key={key} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{btn.commandButtonLabel || btn.tooltipText}
</MenuItem>
);
if (hasChildren) {
return (
<Menu>
<MenuTrigger disableButtonEnhancement>{item}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return item;
}

View File

@ -3,7 +3,7 @@ import { Stack } from "@fluentui/react";
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants"; import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar"; import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";

View File

@ -5,7 +5,7 @@ import { Platform, updateConfigContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact"; import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { import {
ButtonsDependencies, ButtonsDependencies,
DELETE_BUTTON_ID, DELETE_BUTTON_ID,
@ -461,7 +461,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy(); expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy();
}); });
@ -471,7 +471,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined(); expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined();
@ -483,7 +483,7 @@ describe("Documents tab (noSql API)", () => {
await useCommandBar await useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(useDialog.getState().showOkCancelModalDialog).toHaveBeenCalled(); expect(useDialog.getState().showOkCancelModalDialog).toHaveBeenCalled();
@ -494,7 +494,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(ProgressModalDialog).toHaveBeenCalled(); expect(ProgressModalDialog).toHaveBeenCalled();
@ -508,7 +508,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
// The implementation uses setTimeout, so wait for it to finish // The implementation uses setTimeout, so wait for it to finish

View File

@ -28,7 +28,7 @@ import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { import {
ColumnsSelection, ColumnsSelection,

View File

@ -1,7 +1,7 @@
import { deleteDocuments } from "Common/MongoProxyClient"; import { deleteDocuments } from "Common/MongoProxyClient";
import { Platform, updateConfigContext } from "ConfigContext"; import { Platform, updateConfigContext } from "ConfigContext";
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact"; import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { import {
DELETE_BUTTON_ID, DELETE_BUTTON_ID,
DISCARD_BUTTON_ID, DISCARD_BUTTON_ID,
@ -163,7 +163,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy(); expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy();
}); });
@ -173,7 +173,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined(); expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined();
@ -188,7 +188,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined); .onCommandClick(undefined, undefined);
}); });
expect(mockDeleteDocuments).toHaveBeenCalled(); expect(mockDeleteDocuments).toHaveBeenCalled();

View File

@ -152,7 +152,6 @@ export default class NotebookTabV2 extends NotebookTabBase {
ariaLabel: saveLabel, ariaLabel: saveLabel,
children: saveButtonChildren.length && [ children: saveButtonChildren.length && [
{ {
iconName: "Save",
onCommandClick: () => this.notebookComponentAdapter.notebookSave(), onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
commandButtonLabel: saveLabel, commandButtonLabel: saveLabel,
hasPopup: false, hasPopup: false,

View File

@ -6,6 +6,7 @@ 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 { monaco } from "Explorer/LazyMonaco"; import { monaco } from "Explorer/LazyMonaco";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext"; import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar"; import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
@ -54,7 +55,6 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
import TabsBase from "../TabsBase"; import TabsBase from "../TabsBase";

View File

@ -1,5 +1,6 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { Pivot, PivotItem } from "@fluentui/react"; import { Pivot, PivotItem } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React from "react"; import React from "react";
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
@ -15,7 +16,6 @@ import { useTabs } from "../../../hooks/useTabs";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import StoredProcedure from "../../Tree/StoredProcedure"; import StoredProcedure from "../../Tree/StoredProcedure";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import ScriptTabBase from "../ScriptTabBase"; import ScriptTabBase from "../ScriptTabBase";

View File

@ -6,7 +6,8 @@ import { IpRule } from "Contracts/DataModels";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { CollectionTabKind } from "Contracts/ViewModels"; import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { CommandBarV2 } from "Explorer/Menus/CommandBarV2/CommandBarV2";
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab"; import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab"; import { ConnectTab } from "Explorer/Tabs/ConnectTab";
@ -106,6 +107,7 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
</ul> </ul>
</div> </div>
<div className="tabPanesContainer"> <div className="tabPanesContainer">
{userContext.features.commandBarV2 && <CommandBarV2 explorer={explorer} />}
{activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)} {activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)}
{openedTabs.map((tab) => ( {openedTabs.map((tab) => (
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} /> <TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />

View File

@ -1,3 +1,4 @@
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts";
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@ -9,7 +10,6 @@ import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
// TODO: Use specific actions for logging telemetry data // TODO: Use specific actions for logging telemetry data

View File

@ -1,5 +1,6 @@
import { TriggerDefinition } from "@azure/cosmos"; import { TriggerDefinition } from "@azure/cosmos";
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react"; import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react"; import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
@ -14,7 +15,6 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../Controls/Editor/EditorReact"; import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import TriggerTab from "./TriggerTab"; import TriggerTab from "./TriggerTab";
const triggerTypeOptions: IDropdownOption[] = [ const triggerTypeOptions: IDropdownOption[] = [

View File

@ -1,5 +1,6 @@
import { UserDefinedFunctionDefinition } from "@azure/cosmos"; import { UserDefinedFunctionDefinition } from "@azure/cosmos";
import { Label, TextField } from "@fluentui/react"; import { Label, TextField } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react"; import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
@ -13,7 +14,6 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../Controls/Editor/EditorReact"; import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import UserDefinedFunctionTab from "./UserDefinedFunctionTab"; import UserDefinedFunctionTab from "./UserDefinedFunctionTab";
interface IUserDefinedFunctionTabContentState { interface IUserDefinedFunctionTabContentState {

View File

@ -1,4 +1,5 @@
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2"; import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
import * as ko from "knockout"; import * as ko from "knockout";
@ -23,7 +24,6 @@ import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import ConflictsTab from "../Tabs/ConflictsTab"; import ConflictsTab from "../Tabs/ConflictsTab";
import GraphTab from "../Tabs/GraphTab"; import GraphTab from "../Tabs/GraphTab";

View File

@ -1,4 +1,5 @@
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity";
import { shouldShowScriptNodes } from "Explorer/Tree/treeNodeUtil"; import { shouldShowScriptNodes } from "Explorer/Tree/treeNodeUtil";
import { getItemName } from "Utils/APITypeUtils"; import { getItemName } from "Utils/APITypeUtils";
@ -28,7 +29,6 @@ import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFacto
import { useDialog } from "../Controls/Dialog"; import { useDialog } from "../Controls/Dialog";
import { LegacyTreeComponent, LegacyTreeNode } from "../Controls/TreeComponent/LegacyTreeComponent"; import { LegacyTreeComponent, LegacyTreeNode } from "../Controls/TreeComponent/LegacyTreeComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil"; import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";

View File

@ -1,4 +1,4 @@
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";

View File

@ -2,7 +2,7 @@ import { CapabilityNames } from "Common/Constants";
import { Platform, updateConfigContext } from "ConfigContext"; import { Platform, updateConfigContext } from "ConfigContext";
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DeleteDatabaseConfirmationPanel } from "Explorer/Panes/DeleteDatabaseConfirmationPanel"; import { DeleteDatabaseConfirmationPanel } from "Explorer/Panes/DeleteDatabaseConfirmationPanel";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";

View File

@ -1,5 +1,6 @@
import { DatabaseRegular, DocumentMultipleRegular, SettingsRegular } from "@fluentui/react-icons"; import { DatabaseRegular, DocumentMultipleRegular, SettingsRegular } from "@fluentui/react-icons";
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";
import StoredProcedure from "Explorer/Tree/StoredProcedure"; import StoredProcedure from "Explorer/Tree/StoredProcedure";
@ -17,7 +18,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";

View File

@ -20,9 +20,11 @@ import "../externals/jquery.typeahead.min.css";
import "../externals/jquery.typeahead.min.js"; import "../externals/jquery.typeahead.min.js";
// Image Dependencies // Image Dependencies
import { Platform } from "ConfigContext"; import { Platform } from "ConfigContext";
import { CommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel"; import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
import { SidebarContainer } from "Explorer/Sidebar"; import { SidebarContainer } from "Explorer/Sidebar";
import { KeyboardShortcutRoot } from "KeyboardShortcuts"; import { KeyboardShortcutRoot } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import "allotment/dist/style.css"; import "allotment/dist/style.css";
import "../images/CosmosDB_rgb_ui_lighttheme.ico"; import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
@ -48,7 +50,6 @@ import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/TreeComponent/treeComponent.less"; import "./Explorer/Controls/TreeComponent/treeComponent.less";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less"; import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less"; import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
@ -86,7 +87,7 @@ const App: React.FunctionComponent = () => {
<div id="divExplorer" className="flexContainer hideOverflows"> <div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div> <div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */} {/* Main Command Bar - Start */}
<CommandBar container={explorer} /> {!userContext.features.commandBarV2 && <CommandBar container={explorer} />}
{/* Collections Tree and Tabs - Begin */} {/* Collections Tree and Tabs - Begin */}
<SidebarContainer explorer={explorer} /> <SidebarContainer explorer={explorer} />
{/* Collections Tree and Tabs - End */} {/* Collections Tree and Tabs - End */}

View File

@ -1,22 +1,18 @@
import * as React from "react"; import * as React from "react";
import { CommandButtonComponent } from "../../../Explorer/Controls/CommandButton/CommandButtonComponent";
import FeedbackIcon from "../../../../images/Feedback.svg"; import FeedbackIcon from "../../../../images/Feedback.svg";
export const FeedbackCommandButton: React.FunctionComponent = () => { export const FeedbackCommandButton: React.FunctionComponent = () => {
const onClick = () => {
window.open("https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback");
};
return ( return (
<div className="feedbackConnectSettingIcons"> <div className="feedbackConnectSettingIcons">
<CommandButtonComponent <div className="commandButtonReact">
id="commandbutton-feedback" <a href="#" title="Send feedback" aria-haspopup="dialog" onClick={onClick}>
iconSrc={FeedbackIcon} <img src={FeedbackIcon} alt="Send feedback" />
iconAlt="feeback button" </a>
onCommandClick={() => </div>
window.open("https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback")
}
ariaLabel="feeback button"
tooltipText="Send feedback"
hasPopup={true}
disabled={false}
/>
</div> </div>
); );
}; };

View File

@ -38,6 +38,7 @@ export type Features = {
readonly copilotChatFixedMonacoEditorHeight: boolean; readonly copilotChatFixedMonacoEditorHeight: boolean;
readonly enablePriorityBasedExecution: boolean; readonly enablePriorityBasedExecution: boolean;
readonly disableConnectionStringLogin: boolean; readonly disableConnectionStringLogin: boolean;
readonly commandBarV2: boolean;
// can be set via both flight and feature flag // can be set via both flight and feature flag
autoscaleDefault: boolean; autoscaleDefault: boolean;
@ -108,6 +109,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"), copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"), enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"), disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
commandBarV2: "true" === get("commandbarv2"),
}; };
} }