Move selectedNode to zustand (#903)

This commit is contained in:
victor-meng 2021-06-24 11:56:33 -07:00 committed by GitHub
parent a7239c7579
commit 4d2a6999d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 481 additions and 955 deletions

View File

@ -89,7 +89,6 @@ export interface Database extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>; selectedSubnodeKind: ko.Observable<CollectionTabKind>;
selectDatabase(): void;
expandDatabase(): Promise<void>; expandDatabase(): Promise<void>;
collapseDatabase(): void; collapseDatabase(): void;

View File

@ -21,6 +21,7 @@ import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmat
import StoredProcedure from "./Tree/StoredProcedure"; import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger"; import Trigger from "./Tree/Trigger";
import UserDefinedFunction from "./Tree/UserDefinedFunction"; import UserDefinedFunction from "./Tree/UserDefinedFunction";
import { useSelectedNode } from "./useSelectedNode";
export interface CollectionContextMenuButtonParams { export interface CollectionContextMenuButtonParams {
databaseId: string; databaseId: string;
@ -48,10 +49,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
onClick: () => onClick: () =>
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
"Delete " + getDatabaseName(),
<DeleteDatabaseConfirmationPanel explorer={container} selectedDatabase={container.findSelectedDatabase()} />
),
label: `Delete ${getDatabaseName()}`, label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem", styleClass: "deleteDatabaseMenuItem",
}); });
@ -82,7 +80,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
if (container.isShellEnabled()) { if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else { } else {
@ -97,7 +95,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
}, },
label: "New Stored Procedure", label: "New Stored Procedure",
@ -106,7 +104,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: AddUdfIcon, iconSrc: AddUdfIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
}, },
label: "New UDF", label: "New UDF",
@ -115,7 +113,7 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: AddTriggerIcon, iconSrc: AddTriggerIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, undefined);
}, },
label: "New Trigger", label: "New Trigger",

View File

@ -126,7 +126,6 @@ describe("SettingsComponent", () => {
isDatabaseExpanded: undefined, isDatabaseExpanded: undefined,
isDatabaseShared: ko.computed(() => true), isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: undefined, selectedSubnodeKind: undefined,
selectDatabase: undefined,
expandDatabase: undefined, expandDatabase: undefined,
collapseDatabase: undefined, collapseDatabase: undefined,
loadCollections: undefined, loadCollections: undefined,

View File

@ -36,7 +36,6 @@ describe("SettingsUtils", () => {
isDatabaseExpanded: ko.observable(false), isDatabaseExpanded: ko.observable(false),
isDatabaseShared: ko.computed(() => true), isDatabaseShared: ko.computed(() => true),
selectedSubnodeKind: ko.observable(undefined), selectedSubnodeKind: ko.observable(undefined),
selectDatabase: undefined,
expandDatabase: undefined, expandDatabase: undefined,
collapseDatabase: undefined, collapseDatabase: undefined,
loadCollections: undefined, loadCollections: undefined,

View File

@ -34,7 +34,6 @@ exports[`SettingsComponent renders 1`] = `
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function], "isSchemaEnabled": [Function],
"isShellEnabled": [Function], "isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function], "isSynapseLinkUpdating": [Function],
@ -59,8 +58,6 @@ exports[`SettingsComponent renders 1`] = `
"container": [Circular], "container": [Circular],
"parameters": [Function], "parameters": [Function],
}, },
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
@ -123,7 +120,6 @@ exports[`SettingsComponent renders 1`] = `
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function], "isSchemaEnabled": [Function],
"isShellEnabled": [Function], "isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function], "isSynapseLinkUpdating": [Function],
@ -148,8 +144,6 @@ exports[`SettingsComponent renders 1`] = `
"container": [Circular], "container": [Circular],
"parameters": [Function], "parameters": [Function],
}, },
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],

View File

@ -67,6 +67,7 @@ import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken"; import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
import StoredProcedure from "./Tree/StoredProcedure"; import StoredProcedure from "./Tree/StoredProcedure";
import { useDatabases } from "./useDatabases"; import { useDatabases } from "./useDatabases";
import { useSelectedNode } from "./useSelectedNode";
BindingHandlersRegisterer.registerBindingHandlers(); BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@ -83,14 +84,10 @@ export default class Explorer {
public tableDataClient: TableDataClient; public tableDataClient: TableDataClient;
// Resource Tree // Resource Tree
public selectedDatabaseId: ko.Computed<string>;
public selectedCollectionId: ko.Computed<string>;
public selectedNode: ko.Observable<ViewModels.TreeNode>;
private resourceTree: ResourceTreeAdapter; private resourceTree: ResourceTreeAdapter;
// Resource Token // Resource Token
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>; public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken; public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs // Tabs
@ -163,18 +160,10 @@ export default class Explorer {
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>(); this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema); this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
this.selectedNode = ko.observable<ViewModels.TreeNode>(); useSelectedNode.subscribe(() => {
this.selectedNode.subscribe((nodeSelected: ViewModels.TreeNode) => {
// Make sure switching tabs restores tabs display // Make sure switching tabs restores tabs display
this.isTabsContentExpanded(false); this.isTabsContentExpanded(false);
}); });
this.isResourceTokenCollectionNodeSelected = ko.computed<boolean>(() => {
return (
this.selectedNode() &&
this.resourceTokenCollection() &&
this.selectedNode().id() === this.resourceTokenCollection().id()
);
});
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => { this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
if (userContext.features.enableFixedCollectionWithSharedThroughput) { if (userContext.features.enableFixedCollectionWithSharedThroughput) {
@ -187,31 +176,11 @@ export default class Explorer {
return isCapabilityEnabled("EnableMongo"); return isCapabilityEnabled("EnableMongo");
}); });
this.selectedDatabaseId = ko.computed<string>(() => {
const selectedNode = this.selectedNode();
if (!selectedNode) {
return "";
}
switch (selectedNode.nodeKind) {
case "Collection":
return (selectedNode as ViewModels.CollectionBase).databaseId || "";
case "Database":
return selectedNode.id() || "";
case "DocumentId":
case "StoredProcedure":
case "Trigger":
case "UserDefinedFunction":
return selectedNode.collection.databaseId || "";
default:
return "";
}
});
this.tabsManager = params?.tabsManager ?? new TabsManager(); this.tabsManager = params?.tabsManager ?? new TabsManager();
this.tabsManager.openedTabs.subscribe((tabs) => { this.tabsManager.openedTabs.subscribe((tabs) => {
if (tabs.length === 0) { if (tabs.length === 0) {
this.selectedNode(undefined); useSelectedNode.getState().setSelectedNode(undefined);
useCommandBar.getState().setContextButtons([]); useCommandBar.getState().setContextButtons([]);
} }
}); });
@ -363,22 +332,6 @@ export default class Explorer {
// TODO: return result // TODO: return result
} }
public isDatabaseNodeOrNoneSelected(): boolean {
return this.isNoneSelected() || this.isDatabaseNodeSelected();
}
public isDatabaseNodeSelected(): boolean {
return (this.selectedNode() && this.selectedNode().nodeKind === "Database") || false;
}
public isNodeKindSelected(nodeKind: string): boolean {
return (this.selectedNode() && this.selectedNode().nodeKind === nodeKind) || false;
}
public isNoneSelected(): boolean {
return this.selectedNode() == null;
}
public refreshDatabaseForResourceToken(): Promise<void> { public refreshDatabaseForResourceToken(): Promise<void> {
const databaseId = userContext.parsedResourceToken?.databaseId; const databaseId = userContext.parsedResourceToken?.databaseId;
const collectionId = userContext.parsedResourceToken?.collectionId; const collectionId = userContext.parsedResourceToken?.collectionId;
@ -388,7 +341,7 @@ export default class Explorer {
return readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => { return readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection)); this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
this.selectedNode(this.resourceTokenCollection()); useSelectedNode.getState().setSelectedNode(this.resourceTokenCollection());
}); });
} }
@ -414,11 +367,9 @@ export default class Explorer {
}, },
startKey startKey
); );
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
const deltaDatabases = this.getDeltaDatabases(databases); const deltaDatabases = this.getDeltaDatabases(databases);
this.addDatabasesToList(deltaDatabases.toAdd); this.addDatabasesToList(deltaDatabases.toAdd);
this.deleteDatabasesFromList(deltaDatabases.toDelete); this.deleteDatabasesFromList(deltaDatabases.toDelete);
this.selectedNode(currentlySelectedNode);
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then( this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
() => { () => {
deferred.resolve(); deferred.resolve();
@ -611,34 +562,6 @@ export default class Explorer {
} }
}; };
public findSelectedDatabase(): ViewModels.Database {
if (!this.selectedNode()) {
return null;
}
if (this.selectedNode().nodeKind === "Database") {
return _.find(
useDatabases.getState().databases,
(database: ViewModels.Database) => database.id() === this.selectedNode().id()
);
}
return this.findSelectedCollection().database;
}
public isSelectedDatabaseShared(): boolean {
const database = this.findSelectedDatabase();
if (!!database) {
return database.offer && !!database.offer();
}
return false;
}
public findSelectedCollection(): ViewModels.Collection {
return (this.selectedNode().nodeKind === "Collection"
? this.selectedNode()
: this.selectedNode().collection) as ViewModels.Collection;
}
private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> { private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> {
// we reload collections for all databases so the resource tree reflects any collection-level changes // we reload collections for all databases so the resource tree reflects any collection-level changes
// i.e addition of stored procedures, etc. // i.e addition of stored procedures, etc.
@ -1331,7 +1254,7 @@ export default class Explorer {
} }
public openUploadItemsPanePane(): void { public openUploadItemsPanePane(): void {
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />); useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />);
} }
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void { public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
useSidePanel useSidePanel

View File

@ -8,9 +8,9 @@ import * as React from "react";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useObservable } from "../../../hooks/useObservable";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
@ -29,13 +29,13 @@ export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
})); }));
export const CommandBar: React.FC<Props> = ({ container }: Props) => { export const CommandBar: React.FC<Props> = ({ container }: Props) => {
useObservable(container.selectedNode); const selectedNodeState = useSelectedNode();
const buttons = useCommandBar((state) => state.contextButtons); const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight; const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container); const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat( const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container) CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
); );
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container); const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);

View File

@ -1,17 +1,22 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager"; import NotebookManager from "../../Notebook/NotebookManager";
import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer; let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => { describe("Enable Azure Synapse Link Button", () => {
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link"; const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@ -23,14 +28,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
}); });
it("Account is not serverless - button should be visible", () => { it("Account is not serverless - button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
); );
@ -45,7 +48,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
); );
@ -55,6 +58,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Enable notebook button", () => { describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@ -67,9 +71,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
}); });
afterEach(() => { afterEach(() => {
@ -82,7 +83,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn).toBeUndefined();
}); });
@ -94,7 +95,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn).toBeUndefined();
}); });
@ -103,7 +104,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined(); expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(false); expect(enableNotebookBtn.disabled).toBe(false);
@ -114,7 +115,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined(); expect(enableNotebookBtn).toBeDefined();
expect(enableNotebookBtn.disabled).toBe(true); expect(enableNotebookBtn.disabled).toBe(true);
@ -126,6 +127,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Mongo Shell button", () => { describe("Open Mongo Shell button", () => {
const openMongoShellBtnLabel = "Open Mongo Shell"; const openMongoShellBtnLabel = "Open Mongo Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@ -137,9 +139,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isShellEnabled = ko.observable(true); mockExplorer.isShellEnabled = ko.observable(true);
}); });
@ -163,7 +162,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
updateUserContext({ updateUserContext({
apiType: "SQL", apiType: "SQL",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
@ -173,13 +172,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be hidden", () => { it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
@ -187,7 +186,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled and is available - button should be hidden", () => { it("Notebooks is not enabled and is available - button should be hidden", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
@ -195,7 +194,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false); expect(openMongoShellBtn.disabled).toBe(false);
@ -206,7 +205,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false); expect(openMongoShellBtn.disabled).toBe(false);
@ -218,7 +217,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
mockExplorer.isShellEnabled = ko.observable(false); mockExplorer.isShellEnabled = ko.observable(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
}); });
@ -226,6 +225,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Cassandra Shell button", () => { describe("Open Cassandra Shell button", () => {
const openCassandraShellBtnLabel = "Open Cassandra Shell"; const openCassandraShellBtnLabel = "Open Cassandra Shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@ -237,8 +237,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
}); });
beforeEach(() => { beforeEach(() => {
@ -262,7 +260,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
console.log(mockExplorer); console.log(mockExplorer);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@ -272,13 +270,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, 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); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@ -286,7 +284,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is not enabled and is available - button should be shown and enabled", () => { it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@ -294,7 +292,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false); expect(openCassandraShellBtn.disabled).toBe(false);
@ -305,7 +303,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true); mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false); expect(openCassandraShellBtn.disabled).toBe(false);
@ -316,6 +314,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("GitHub buttons", () => { describe("GitHub buttons", () => {
const connectToGitHubBtnLabel = "Connect to GitHub"; const connectToGitHubBtnLabel = "Connect to GitHub";
const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
@ -328,7 +327,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.notebookManager = new NotebookManager(); mockExplorer.notebookManager = new NotebookManager();
@ -346,7 +344,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => { it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeDefined(); expect(connectToGitHubBtn).toBeDefined();
}); });
@ -355,7 +353,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isNotebookEnabled = ko.observable(true); mockExplorer.isNotebookEnabled = ko.observable(true);
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true); mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const manageGitHubSettingsBtn = buttons.find( const manageGitHubSettingsBtn = buttons.find(
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel
); );
@ -363,7 +361,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => { it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeUndefined(); expect(connectToGitHubBtn).toBeUndefined();
@ -376,10 +374,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
describe("Resource token", () => { describe("Resource token", () => {
const mockCollection = { id: ko.observable("test") } as CollectionBase;
useSelectedNode.getState().setSelectedNode(mockCollection);
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken, authType: AuthType.ResourceToken,
@ -392,7 +392,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB", kind: "DocumentDB",
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, 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

@ -31,12 +31,16 @@ import Explorer from "../../Explorer";
import { OpenFullScreen } from "../../OpenFullScreen"; import { OpenFullScreen } from "../../OpenFullScreen";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { SelectedNodeState } from "../../useSelectedNode";
let counter = 0; let counter = 0;
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createStaticCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
if (userContext.authType === AuthType.ResourceToken) { if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container); return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
} }
const newCollectionBtn = createNewCollectionGroup(container); const newCollectionBtn = createNewCollectionGroup(container);
@ -71,7 +75,9 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
buttons.push(createNotebookWorkspaceResetButton(container)); buttons.push(createNotebookWorkspaceResetButton(container));
if ( if (
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) || (userContext.apiType === "Mongo" &&
container.isShellEnabled() &&
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra"
) { ) {
buttons.push(createDivider()); buttons.push(createDivider());
@ -87,18 +93,18 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
} }
} }
if (!container.isDatabaseNodeOrNoneSelected()) { if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
if (isQuerySupported) { if (isQuerySupported) {
buttons.push(createDivider()); buttons.push(createDivider());
const newSqlQueryBtn = createNewSQLQueryButton(container); const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
buttons.push(newSqlQueryBtn); buttons.push(newSqlQueryBtn);
} }
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) { if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn); buttons.push(openQueryBtn);
} }
@ -108,16 +114,16 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
newStoredProcedureBtn.children = createScriptCommandButtons(container); newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
buttons.push(newStoredProcedureBtn); buttons.push(newStoredProcedureBtn);
} }
} }
@ -125,16 +131,19 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
return buttons; return buttons;
} }
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell"; const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (container.isShellEnabled()) { if (container.isShellEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else { } else {
@ -144,7 +153,7 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo", disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
}; };
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }
@ -279,20 +288,20 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
}; };
} }
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandButtonComponentProps {
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
const label = "New SQL Query"; const label = "New SQL Query";
return { return {
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
} else if (userContext.apiType === "Mongo") { } else if (userContext.apiType === "Mongo") {
const label = "New Query"; const label = "New Query";
@ -300,23 +309,24 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
} }
return undefined; return undefined;
} }
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { export function createScriptCommandButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(); const shouldEnableScriptsCommands: boolean =
!selectedNodeState.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
if (shouldEnableScriptsCommands) { if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure"; const label = "New Stored Procedure";
@ -324,13 +334,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newStoredProcedureBtn); buttons.push(newStoredProcedureBtn);
} }
@ -341,13 +351,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddUdfIcon, iconSrc: AddUdfIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newUserDefinedFunctionBtn); buttons.push(newUserDefinedFunctionBtn);
} }
@ -358,13 +368,13 @@ export function createScriptCommandButtons(container: Explorer): CommandButtonCo
iconSrc: AddTriggerIcon, iconSrc: AddTriggerIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
}; };
buttons.push(newTriggerBtn); buttons.push(newTriggerBtn);
} }
@ -411,12 +421,12 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
}; };
} }
function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps { function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
const label = "Open Query From Disk"; const label = "Open Query From Disk";
return { return {
iconSrc: OpenQueryFromDiskIcon, iconSrc: OpenQueryFromDiskIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane explorer={container} />), onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@ -537,19 +547,25 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
}; };
} }
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] { function createStaticCommandBarButtonsForResourceToken(
const newSqlQueryBtn = createNewSQLQueryButton(container); container: Explorer,
selectedNodeState: SelectedNodeState
): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton(container);
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); const isResourceTokenCollectionNodeSelected: boolean =
container.resourceTokenCollection() &&
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
newSqlQueryBtn.onCommandClick = () => { newSqlQueryBtn.onCommandClick = () => {
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection(); const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined); resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
}; };
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
if (!openQueryBtn.disabled) { if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
} }
return [newSqlQueryBtn, openQueryBtn]; return [newSqlQueryBtn, openQueryBtn];

View File

@ -14,6 +14,7 @@ import {
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab"; import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
interface BrowseQueriesPaneProps { interface BrowseQueriesPaneProps {
explorer: Explorer; explorer: Explorer;
@ -24,7 +25,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
}: BrowseQueriesPaneProps): JSX.Element => { }: BrowseQueriesPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const loadSavedQuery = (savedQuery: Query): void => { const loadSavedQuery = (savedQuery: Query): void => {
const selectedCollection: Collection = explorer && explorer.findSelectedCollection(); const selectedCollection: Collection = useSelectedNode.getState().findSelectedCollection();
if (!selectedCollection) { if (!selectedCollection) {
// should never get into this state because this pane is only accessible through the query tab // should never get into this state because this pane is only accessible through the query tab
logError("No collection was selected", "BrowseQueriesPane.loadSavedQuery"); logError("No collection was selected", "BrowseQueriesPane.loadSavedQuery");

View File

@ -1,17 +1,18 @@
jest.mock("../../../Common/dataAccess/deleteCollection"); jest.mock("../../../Common/dataAccess/deleteCollection");
jest.mock("../../../Shared/Telemetry/TelemetryProcessor"); jest.mock("../../../Shared/Telemetry/TelemetryProcessor");
import { mount, ReactWrapper, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection"; import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
import DeleteFeedback from "../../../Common/DeleteFeedback"; import DeleteFeedback from "../../../Common/DeleteFeedback";
import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels"; import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels";
import { Collection, Database, TreeNode } from "../../../Contracts/ViewModels"; import { Collection, Database } from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane"; import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
describe("Delete Collection Confirmation Pane", () => { describe("Delete Collection Confirmation Pane", () => {
@ -54,47 +55,39 @@ describe("Delete Collection Confirmation Pane", () => {
it("should return true if last collection and database does not have shared throughput else false", () => { it("should return true if last collection and database does not have shared throughput else false", () => {
const fakeExplorer = new Explorer(); const fakeExplorer = new Explorer();
fakeExplorer.refreshAllDatabases = () => undefined; fakeExplorer.refreshAllDatabases = () => undefined;
fakeExplorer.isSelectedDatabaseShared = () => false;
const props = { const wrapper = shallow(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
explorer: fakeExplorer,
closePanel: (): void => undefined,
collectionName: "container",
};
const wrapper = shallow(<DeleteCollectionConfirmationPane {...props} />);
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false); expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
const database = { id: ko.observable("testDB") } as Database; const database = { id: ko.observable("testDB") } as Database;
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]); database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
database.nodeKind = "Database";
database.isDatabaseShared = ko.computed(() => false);
useDatabases.getState().addDatabases([database]); useDatabases.getState().addDatabases([database]);
wrapper.setProps(props); useSelectedNode.getState().setSelectedNode(database);
wrapper.setProps({ explorer: fakeExplorer });
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true); expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
props.explorer.isSelectedDatabaseShared = () => true; database.isDatabaseShared = ko.computed(() => true);
wrapper.setProps(props); wrapper.setProps({ explorer: fakeExplorer });
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false); expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
}); });
}); });
describe("submit()", () => { describe("submit()", () => {
let wrapper: ReactWrapper;
const selectedCollectionId = "testCol"; const selectedCollectionId = "testCol";
const databaseId = "testDatabase"; const databaseId = "testDatabase";
const fakeExplorer = {} as Explorer; const fakeExplorer = {} as Explorer;
fakeExplorer.findSelectedCollection = () => {
return {
id: ko.observable<string>(selectedCollectionId),
databaseId,
rid: "test",
} as Collection;
};
fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
fakeExplorer.selectedNode = ko.observable<TreeNode>();
fakeExplorer.refreshAllDatabases = () => undefined; fakeExplorer.refreshAllDatabases = () => undefined;
fakeExplorer.isSelectedDatabaseShared = () => false; const database = { id: ko.observable(databaseId) } as Database;
const database = { id: ko.observable("testDB") } as Database; const collection = {
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]); id: ko.observable(selectedCollectionId),
useDatabases.getState().addDatabases([database]); nodeKind: "Collection",
database,
databaseId,
} as Collection;
database.collections = ko.observableArray<Collection>([collection]);
database.isDatabaseShared = ko.computed(() => false);
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
@ -112,15 +105,17 @@ describe("Delete Collection Confirmation Pane", () => {
}); });
beforeEach(() => { beforeEach(() => {
const props = { useDatabases.getState().addDatabases([database]);
explorer: fakeExplorer, useSelectedNode.getState().setSelectedNode(collection);
closePanel: (): void => undefined, });
collectionName: "container",
}; afterEach(() => {
wrapper = mount(<DeleteCollectionConfirmationPane {...props} />); useDatabases.getState().clearDatabases();
useSelectedNode.getState().setSelectedNode(undefined);
}); });
it("should call delete collection", () => { it("should call delete collection", () => {
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmCollectionId")).toBe(true); expect(wrapper.exists("#confirmCollectionId")).toBe(true);
@ -137,6 +132,7 @@ describe("Delete Collection Confirmation Pane", () => {
}); });
it("should record feedback", async () => { it("should record feedback", async () => {
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
expect(wrapper.exists("#confirmCollectionId")).toBe(true); expect(wrapper.exists("#confirmCollectionId")).toBe(true);
wrapper wrapper
.find("#confirmCollectionId") .find("#confirmCollectionId")

View File

@ -14,6 +14,7 @@ import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface DeleteCollectionConfirmationPaneProps { export interface DeleteCollectionConfirmationPaneProps {
@ -24,19 +25,19 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
explorer, explorer,
}: DeleteCollectionConfirmationPaneProps) => { }: DeleteCollectionConfirmationPaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const isLastCollection = useDatabases((state) => state.isLastCollection);
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>(""); const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
const [inputCollectionName, setInputCollectionName] = useState<string>(""); const [inputCollectionName, setInputCollectionName] = useState<string>("");
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
const [isExecuting, setIsExecuting] = useState(false); const [isExecuting, setIsExecuting] = useState(false);
const shouldRecordFeedback = (): boolean => { const shouldRecordFeedback = (): boolean =>
return isLastCollection() && !explorer.isSelectedDatabaseShared(); useDatabases.getState().isLastCollection() &&
}; !useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
const collectionName = getCollectionName().toLocaleLowerCase(); const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = "Delete " + collectionName; const paneTitle = "Delete " + collectionName;
const onSubmit = async (): Promise<void> => { const onSubmit = async (): Promise<void> => {
const collection = explorer.findSelectedCollection(); const collection = useSelectedNode.getState().findSelectedCollection();
if (!collection || inputCollectionName !== collection.id()) { if (!collection || inputCollectionName !== collection.id()) {
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName; const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
setFormError(errorMessage); setFormError(errorMessage);
@ -61,7 +62,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
await deleteCollection(collection.databaseId, collection.id()); await deleteCollection(collection.databaseId, collection.id());
setIsExecuting(false); setIsExecuting(false);
explorer.selectedNode(collection.database); useSelectedNode.getState().setSelectedNode(collection.database);
explorer.tabsManager?.closeTabsByComparator( explorer.tabsManager?.closeTabsByComparator(
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId (tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
); );

View File

@ -2,15 +2,9 @@
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = ` exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
<DeleteCollectionConfirmationPane <DeleteCollectionConfirmationPane
closePanel={[Function]}
collectionName="container"
explorer={ explorer={
Object { Object {
"findSelectedCollection": [Function],
"isSelectedDatabaseShared": [Function],
"refreshAllDatabases": [Function], "refreshAllDatabases": [Function],
"selectedCollectionId": [Function],
"selectedNode": [Function],
} }
} }
> >
@ -372,355 +366,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
</TextFieldBase> </TextFieldBase>
</StyledTextFieldBase> </StyledTextFieldBase>
</div> </div>
<div
className="deleteCollectionFeedback"
>
<Text
block={true}
variant="small"
>
<span
className="css-66"
>
Help us improve Azure Cosmos DB!
</span>
</Text>
<Text
block={true}
variant="small"
>
<span
className="css-66"
>
What is the reason why you are deleting this
container
?
</span>
</Text>
<StyledTextFieldBase
id="deleteCollectionFeedbackInput"
multiline={true}
onChange={[Function]}
rows={3}
styles={
Object {
"fieldGroup": Object {
"width": 300,
},
}
}
value=""
>
<TextFieldBase
deferredValidationTime={200}
id="deleteCollectionFeedbackInput"
multiline={true}
onChange={[Function]}
resizable={true}
rows={3}
styles={[Function]}
theme={
Object {
"disableGlobalClassNames": false,
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "32px",
"fontWeight": 600,
},
},
"isInverted": false,
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
"blue": "#0078d4",
"blueDark": "#002050",
"blueLight": "#00bcf2",
"blueMid": "#00188f",
"green": "#107c10",
"greenDark": "#004b1c",
"greenLight": "#bad80a",
"magenta": "#b4009e",
"magentaDark": "#5c005c",
"magentaLight": "#e3008c",
"neutralDark": "#201f1e",
"neutralLight": "#edebe9",
"neutralLighter": "#f3f2f1",
"neutralLighterAlt": "#faf9f8",
"neutralPrimary": "#323130",
"neutralPrimaryAlt": "#3b3a39",
"neutralQuaternary": "#d2d0ce",
"neutralQuaternaryAlt": "#e1dfdd",
"neutralSecondary": "#605e5c",
"neutralSecondaryAlt": "#8a8886",
"neutralTertiary": "#a19f9d",
"neutralTertiaryAlt": "#c8c6c4",
"orange": "#d83b01",
"orangeLight": "#ea4300",
"orangeLighter": "#ff8c00",
"purple": "#5c2d91",
"purpleDark": "#32145a",
"purpleLight": "#b4a0ff",
"red": "#e81123",
"redDark": "#a4262c",
"teal": "#008272",
"tealDark": "#004b50",
"tealLight": "#00b294",
"themeDark": "#005a9e",
"themeDarkAlt": "#106ebe",
"themeDarker": "#004578",
"themeLight": "#c7e0f4",
"themeLighter": "#deecf9",
"themeLighterAlt": "#eff6fc",
"themePrimary": "#0078d4",
"themeSecondary": "#2b88d8",
"themeTertiary": "#71afe5",
"white": "#ffffff",
"whiteTranslucent40": "rgba(255,255,255,.4)",
"yellow": "#ffb900",
"yellowDark": "#d29200",
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
"actionLinkHovered": "#201f1e",
"blockingBackground": "#FDE7E9",
"blockingIcon": "#FDE7E9",
"bodyBackground": "#ffffff",
"bodyBackgroundChecked": "#edebe9",
"bodyBackgroundHovered": "#f3f2f1",
"bodyDivider": "#edebe9",
"bodyFrameBackground": "#ffffff",
"bodyFrameDivider": "#edebe9",
"bodyStandoutBackground": "#faf9f8",
"bodySubtext": "#605e5c",
"bodyText": "#323130",
"bodyTextChecked": "#000000",
"buttonBackground": "#ffffff",
"buttonBackgroundChecked": "#c8c6c4",
"buttonBackgroundCheckedHovered": "#edebe9",
"buttonBackgroundDisabled": "#f3f2f1",
"buttonBackgroundHovered": "#f3f2f1",
"buttonBackgroundPressed": "#edebe9",
"buttonBorder": "#8a8886",
"buttonBorderDisabled": "#f3f2f1",
"buttonText": "#323130",
"buttonTextChecked": "#201f1e",
"buttonTextCheckedHovered": "#000000",
"buttonTextDisabled": "#a19f9d",
"buttonTextHovered": "#201f1e",
"buttonTextPressed": "#201f1e",
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"cardStandoutBackground": "#ffffff",
"defaultStateBackground": "#faf9f8",
"disabledBackground": "#f3f2f1",
"disabledBodySubtext": "#c8c6c4",
"disabledBodyText": "#a19f9d",
"disabledBorder": "#c8c6c4",
"disabledSubtext": "#d2d0ce",
"disabledText": "#a19f9d",
"errorBackground": "#FDE7E9",
"errorIcon": "#A80000",
"errorText": "#a4262c",
"focusBorder": "#605e5c",
"infoBackground": "#f3f2f1",
"infoIcon": "#605e5c",
"inputBackground": "#ffffff",
"inputBackgroundChecked": "#0078d4",
"inputBackgroundCheckedHovered": "#005a9e",
"inputBorder": "#605e5c",
"inputBorderHovered": "#323130",
"inputFocusBorderAlt": "#0078d4",
"inputForegroundChecked": "#ffffff",
"inputIcon": "#0078d4",
"inputIconDisabled": "#a19f9d",
"inputIconHovered": "#005a9e",
"inputPlaceholderBackgroundChecked": "#deecf9",
"inputPlaceholderText": "#605e5c",
"inputText": "#323130",
"inputTextHovered": "#201f1e",
"link": "#0078d4",
"linkHovered": "#004578",
"listBackground": "#ffffff",
"listHeaderBackgroundHovered": "#f3f2f1",
"listHeaderBackgroundPressed": "#edebe9",
"listItemBackgroundChecked": "#edebe9",
"listItemBackgroundCheckedHovered": "#e1dfdd",
"listItemBackgroundHovered": "#f3f2f1",
"listText": "#323130",
"listTextColor": "#323130",
"menuBackground": "#ffffff",
"menuDivider": "#c8c6c4",
"menuHeader": "#0078d4",
"menuIcon": "#0078d4",
"menuItemBackgroundChecked": "#edebe9",
"menuItemBackgroundHovered": "#f3f2f1",
"menuItemBackgroundPressed": "#edebe9",
"menuItemText": "#323130",
"menuItemTextHovered": "#201f1e",
"messageLink": "#005A9E",
"messageLinkHovered": "#004578",
"messageText": "#323130",
"primaryButtonBackground": "#0078d4",
"primaryButtonBackgroundDisabled": "#f3f2f1",
"primaryButtonBackgroundHovered": "#106ebe",
"primaryButtonBackgroundPressed": "#005a9e",
"primaryButtonBorder": "transparent",
"primaryButtonText": "#ffffff",
"primaryButtonTextDisabled": "#d2d0ce",
"primaryButtonTextHovered": "#ffffff",
"primaryButtonTextPressed": "#ffffff",
"severeWarningBackground": "#FED9CC",
"severeWarningIcon": "#D83B01",
"smallInputBorder": "#605e5c",
"successBackground": "#DFF6DD",
"successIcon": "#107C10",
"successText": "#107C10",
"variantBorder": "#edebe9",
"variantBorderHovered": "#a19f9d",
"warningBackground": "#FFF4CE",
"warningHighlight": "#ffb900",
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
"s1": "8px",
"s2": "4px",
},
}
}
validateOnLoad={true}
value=""
>
<div
className="ms-TextField ms-TextField--multiline root-55"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-67"
>
<textarea
aria-invalid={false}
className="ms-TextField-field field-68"
id="deleteCollectionFeedbackInput"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onInput={[Function]}
rows={3}
value=""
/>
</div>
</div>
</div>
</TextFieldBase>
</StyledTextFieldBase>
</div>
</div> </div>
</div> </div>
<PanelFooterComponent <PanelFooterComponent
@ -2433,7 +2078,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
> >
<button <button
aria-label="OK" aria-label="OK"
className="ms-Button ms-Button--primary root-70" className="ms-Button ms-Button--primary root-66"
data-is-focusable={true} data-is-focusable={true}
id="sidePanelOkButton" id="sidePanelOkButton"
onClick={[Function]} onClick={[Function]}
@ -2445,16 +2090,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
type="submit" type="submit"
> >
<span <span
className="ms-Button-flexContainer flexContainer-71" className="ms-Button-flexContainer flexContainer-67"
data-automationid="splitbuttonprimary" data-automationid="splitbuttonprimary"
> >
<span <span
className="ms-Button-textContainer textContainer-72" className="ms-Button-textContainer textContainer-68"
> >
<span <span
className="ms-Button-label label-74" className="ms-Button-label label-70"
id="id__6" id="id__3"
key="id__6" key="id__3"
> >
OK OK
</span> </span>

View File

@ -1,6 +1,6 @@
jest.mock("../../Common/dataAccess/deleteDatabase"); jest.mock("../../Common/dataAccess/deleteDatabase");
jest.mock("../../Shared/Telemetry/TelemetryProcessor"); jest.mock("../../Shared/Telemetry/TelemetryProcessor");
import { mount, ReactWrapper, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase"; import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
@ -13,49 +13,14 @@ import { updateUserContext } from "../../UserContext";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { TabsManager } from "../Tabs/TabsManager"; import { TabsManager } from "../Tabs/TabsManager";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel"; import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
describe("Delete Database Confirmation Pane", () => { describe("Delete Database Confirmation Pane", () => {
describe("shouldRecordFeedback()", () => { const selectedDatabaseId = "testDatabase";
it("should return true if last non empty database or is last database that has shared throughput, else false", () => { let fakeExplorer: Explorer;
const fakeExplorer = {} as Explorer; let database: Database;
fakeExplorer.refreshAllDatabases = () => undefined;
fakeExplorer.isSelectedDatabaseShared = () => false;
const database = {} as Database;
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
database.id = ko.observable<string>("testDatabase");
const props = {
explorer: fakeExplorer,
closePanel: (): void => undefined,
openNotificationConsole: (): void => undefined,
selectedDatabase: database,
};
const wrapper = shallow(<DeleteDatabaseConfirmationPanel {...props} />);
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
useDatabases.getState().addDatabases([database]);
wrapper.setProps(props);
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
useDatabases.getState().clearDatabases();
});
});
describe("submit()", () => {
const selectedDatabaseId = "testDatabse";
const database = { id: ko.observable("testDatabase") } as Database;
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
database.id = ko.observable<string>(selectedDatabaseId);
const fakeExplorer = {} as Explorer;
fakeExplorer.refreshAllDatabases = () => undefined;
fakeExplorer.isSelectedDatabaseShared = () => false;
fakeExplorer.tabsManager = new TabsManager();
fakeExplorer.selectedNode = ko.observable();
let wrapper: ReactWrapper;
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
@ -69,23 +34,39 @@ describe("Delete Database Confirmation Pane", () => {
}); });
(deleteDatabase as jest.Mock).mockResolvedValue(undefined); (deleteDatabase as jest.Mock).mockResolvedValue(undefined);
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined); (TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);
useDatabases.getState().addDatabases([database]);
}); });
beforeEach(() => { beforeEach(() => {
const props = { fakeExplorer = {} as Explorer;
explorer: fakeExplorer, fakeExplorer.refreshAllDatabases = () => undefined;
closePanel: (): void => undefined, fakeExplorer.tabsManager = new TabsManager();
openNotificationConsole: (): void => undefined,
selectedDatabase: database,
};
wrapper = mount(<DeleteDatabaseConfirmationPanel {...props} />); database = {} as Database;
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
database.id = ko.observable<string>(selectedDatabaseId);
database.nodeKind = "Database";
useDatabases.getState().addDatabases([database]);
useSelectedNode.getState().setSelectedNode(database);
}); });
afterAll(() => useDatabases.getState().clearDatabases()); afterEach(() => {
useDatabases.getState().clearDatabases();
useSelectedNode.getState().setSelectedNode(undefined);
});
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
const wrapper = shallow(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
useDatabases.getState().addDatabases([database]);
wrapper.setProps({ explorer: fakeExplorer });
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
useDatabases.getState().clearDatabases();
});
it("Should call delete database", () => { it("Should call delete database", () => {
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmDatabaseId")).toBe(true); expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
@ -100,6 +81,7 @@ describe("Delete Database Confirmation Pane", () => {
}); });
it("should record feedback", async () => { it("should record feedback", async () => {
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
expect(wrapper.exists("#confirmDatabaseId")).toBe(true); expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
wrapper wrapper
.find("#confirmDatabaseId") .find("#confirmDatabaseId")
@ -129,5 +111,4 @@ describe("Delete Database Confirmation Pane", () => {
}); });
wrapper.unmount(); wrapper.unmount();
}); });
});
}); });

View File

@ -14,17 +14,16 @@ import { userContext } from "../../UserContext";
import { logConsoleError } from "../../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent"; import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
interface DeleteDatabaseConfirmationPanelProps { interface DeleteDatabaseConfirmationPanelProps {
explorer: Explorer; explorer: Explorer;
selectedDatabase: Database;
} }
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
explorer, explorer,
selectedDatabase,
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => { }: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase); const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
@ -33,6 +32,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
const [databaseInput, setDatabaseInput] = useState<string>(""); const [databaseInput, setDatabaseInput] = useState<string>("");
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>(""); const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
const selectedDatabase: Database = useSelectedNode.getState().findSelectedDatabase();
const submit = async (): Promise<void> => { const submit = async (): Promise<void> => {
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) { if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
@ -54,7 +54,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
closeSidePanel(); closeSidePanel();
explorer.refreshAllDatabases(); explorer.refreshAllDatabases();
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id()); explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
explorer.selectedNode(undefined); useSelectedNode.getState().setSelectedNode(undefined);
selectedDatabase selectedDatabase
.collections() .collections()
.forEach((collection: Collection) => .forEach((collection: Collection) =>

View File

@ -23,7 +23,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function], "isSchemaEnabled": [Function],
"isShellEnabled": [Function], "isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function], "isSynapseLinkUpdating": [Function],
@ -48,8 +47,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"container": [Circular], "container": [Circular],
"parameters": [Function], "parameters": [Function],
}, },
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],

View File

@ -1,17 +1,10 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import Explorer from "../../Explorer";
import { LoadQueryPane } from "./LoadQueryPane"; import { LoadQueryPane } from "./LoadQueryPane";
describe("Load Query Pane", () => { describe("Load Query Pane", () => {
it("should render Default properly", () => { it("should render Default properly", () => {
const fakeExplorer = {} as Explorer; const wrapper = shallow(<LoadQueryPane />);
const props = {
explorer: fakeExplorer,
closePanel: (): void => undefined,
};
const wrapper = shallow(<LoadQueryPane {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View File

@ -7,14 +7,10 @@ import { Collection } from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
interface LoadQueryPaneProps { export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
explorer: Explorer;
}
export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer }: LoadQueryPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
@ -58,7 +54,7 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer
}; };
const loadQueryFromFile = async (file: File): Promise<void> => { const loadQueryFromFile = async (file: File): Promise<void> => {
const selectedCollection: Collection = explorer?.findSelectedCollection(); const selectedCollection: Collection = useSelectedNode.getState().findSelectedCollection();
const reader = new FileReader(); const reader = new FileReader();
let fileData: string; let fileData: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -13,7 +13,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isSchemaEnabled": [Function], "isSchemaEnabled": [Function],
"isShellEnabled": [Function], "isShellEnabled": [Function],
"isSynapseLinkUpdating": [Function], "isSynapseLinkUpdating": [Function],
@ -38,8 +37,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"container": [Circular], "container": [Circular],
"parameters": [Function], "parameters": [Function],
}, },
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],

View File

@ -1,13 +1,10 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import Explorer from "../../Explorer";
import { UploadItemsPane } from "./UploadItemsPane"; import { UploadItemsPane } from "./UploadItemsPane";
const props = {
explorer: new Explorer(),
};
describe("Upload Items Pane", () => { describe("Upload Items Pane", () => {
it("should render Default properly", () => { it("should render Default properly", () => {
const wrapper = shallow(<UploadItemsPane {...props} />); const wrapper = shallow(<UploadItemsPane />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View File

@ -3,15 +3,11 @@ import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload"; import { Upload } from "../../../Common/Upload/Upload";
import { UploadDetailsRecord } from "../../../Contracts/ViewModels"; import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import { getErrorMessage } from "../../Tables/Utilities"; import { getErrorMessage } from "../../Tables/Utilities";
import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface UploadItemsPaneProps { export const UploadItemsPane: FunctionComponent = () => {
explorer: Explorer;
}
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explorer }: UploadItemsPaneProps) => {
const [files, setFiles] = useState<FileList>(); const [files, setFiles] = useState<FileList>();
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]); const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
@ -25,7 +21,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
return; return;
} }
const selectedCollection = explorer.findSelectedCollection(); const selectedCollection = useSelectedNode.getState().findSelectedCollection();
setIsExecuting(true); setIsExecuting(true);
selectedCollection selectedCollection

View File

@ -1,26 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Delete Database Confirmation Pane submit() Should call delete database 1`] = ` exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
<DeleteDatabaseConfirmationPanel <DeleteDatabaseConfirmationPanel
closePanel={[Function]}
explorer={ explorer={
Object { Object {
"isSelectedDatabaseShared": [Function],
"refreshAllDatabases": [Function], "refreshAllDatabases": [Function],
"selectedNode": [Function],
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
"openedTabs": [Function], "openedTabs": [Function],
}, },
} }
} }
openNotificationConsole={[Function]}
selectedDatabase={
Object {
"collections": [Function],
"id": [Function],
}
}
> >
<RightPaneForm <RightPaneForm
formError="" formError=""

View File

@ -7,7 +7,6 @@ jest.mock("../Explorer");
const createExplorer = () => { const createExplorer = () => {
const mock = new Explorer(); const mock = new Explorer();
mock.selectedNode = ko.observable();
mock.isNotebookEnabled = ko.observable(false); mock.isNotebookEnabled = ko.observable(false);
mock.tabsManager = new TabsManager(); mock.tabsManager = new TabsManager();
return mock as jest.Mocked<Explorer>; return mock as jest.Mocked<Explorer>;

View File

@ -23,6 +23,7 @@ import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity"; import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
export interface SplashScreenItem { export interface SplashScreenItem {
iconSrc: string; iconSrc: string;
@ -60,7 +61,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
public componentDidMount() { public componentDidMount() {
this.subscriptions.push( this.subscriptions.push(
this.container.selectedNode.subscribe(() => this.setState({})), { dispose: useSelectedNode.subscribe(() => this.setState({})) },
this.container.isNotebookEnabled.subscribe(() => this.setState({})) this.container.isNotebookEnabled.subscribe(() => this.setState({}))
); );
} }
@ -228,12 +229,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
return items; return items;
} }
if (!this.container.isDatabaseNodeOrNoneSelected()) { if (!useSelectedNode.getState().isDatabaseNodeOrNoneSelected()) {
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({ items.push({
iconSrc: NewQueryIcon, iconSrc: NewQueryIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
}, },
title: "New SQL Query", title: "New SQL Query",
@ -243,7 +244,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
items.push({ items.push({
iconSrc: NewQueryIcon, iconSrc: NewQueryIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
}, },
title: "New Query", title: "New Query",
@ -266,20 +267,14 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "New Stored Procedure", title: "New Stored Procedure",
description: null, description: null,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
}, },
}); });
} }
/* Scale & Settings */ /* Scale & Settings */
let isShared = false; const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
if (this.container.isDatabaseNodeSelected()) {
isShared = this.container.findSelectedDatabase().isDatabaseShared();
} else if (this.container.isNodeKindSelected("Collection")) {
const database: ViewModels.Database = this.container.findSelectedCollection().getDatabase();
isShared = database && database.isDatabaseShared();
}
const label = isShared ? "Settings" : "Scale & Settings"; const label = isShared ? "Settings" : "Scale & Settings";
items.push({ items.push({
@ -287,7 +282,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: label, title: label,
description: null, description: null,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onSettingsClick(); selectedCollection && selectedCollection.onSettingsClick();
}, },
}); });

View File

@ -28,6 +28,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList"; import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
import DocumentId from "../Tree/DocumentId"; import DocumentId from "../Tree/DocumentId";
import { useSelectedNode } from "../useSelectedNode";
import template from "./DocumentsTab.html"; import template from "./DocumentsTab.html";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
@ -911,13 +912,13 @@ export default class DocumentsTab extends TabsBase {
iconSrc: UploadIcon, iconSrc: UploadIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && container.openUploadItemsPanePane(); selectedCollection && container.openUploadItemsPanePane();
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(), disabled: useSelectedNode.getState().isDatabaseNodeOrNoneSelected(),
}; };
} }
} }

View File

@ -15,6 +15,7 @@ import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import StoredProcedure from "../../Tree/StoredProcedure"; import StoredProcedure from "../../Tree/StoredProcedure";
import { useSelectedNode } from "../../useSelectedNode";
import ScriptTabBase from "../ScriptTabBase"; import ScriptTabBase from "../ScriptTabBase";
export interface IStorProcTabComponentAccessor { export interface IStorProcTabComponentAccessor {
@ -298,12 +299,13 @@ export default class StoredProcedureTabComponent extends React.Component<
} }
const database: ViewModels.Database = this.props.collectionBase.getDatabase(); const database: ViewModels.Database = this.props.collectionBase.getDatabase();
const setSelectedNode = useSelectedNode.getState().setSelectedNode;
if (!database.isDatabaseExpanded()) { if (!database.isDatabaseExpanded()) {
this.props.collectionBase.container.selectedNode(database); setSelectedNode(database);
} else if (!this.props.collectionBase.isCollectionExpanded() || !this.collection.isStoredProceduresExpanded()) { } else if (!this.props.collectionBase.isCollectionExpanded() || !this.collection.isStoredProceduresExpanded()) {
this.props.collectionBase.container.selectedNode(this.props.collectionBase); setSelectedNode(this.props.collectionBase);
} else { } else {
this.props.collectionBase.container.selectedNode(this.node); setSelectedNode(this.node);
} }
} }

View File

@ -9,6 +9,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
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 { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "../useSelectedNode";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import { TabsManager } from "./TabsManager"; import { TabsManager } from "./TabsManager";
// TODO: Use specific actions for logging telemetry data // TODO: Use specific actions for logging telemetry data
@ -74,12 +75,13 @@ export default class TabsBase extends WaitsForTemplateViewModel {
protected updateSelectedNode(): void { protected updateSelectedNode(): void {
const relatedDatabase = (this.collection && this.collection.getDatabase()) || this.database; const relatedDatabase = (this.collection && this.collection.getDatabase()) || this.database;
const setSelectedNode = useSelectedNode.getState().setSelectedNode;
if (relatedDatabase && !relatedDatabase.isDatabaseExpanded()) { if (relatedDatabase && !relatedDatabase.isDatabaseExpanded()) {
this.getContainer().selectedNode(relatedDatabase); setSelectedNode(relatedDatabase);
} else if (this.collection && !this.collection.isCollectionExpanded()) { } else if (this.collection && !this.collection.isCollectionExpanded()) {
this.getContainer().selectedNode(this.collection); setSelectedNode(this.collection);
} else { } else {
this.getContainer().selectedNode(this.node); setSelectedNode(this.node);
} }
} }

View File

@ -31,10 +31,6 @@ describe("Collection", () => {
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection { function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
const mockContainer = {} as Explorer; const mockContainer = {} as Explorer;
mockContainer.isDatabaseNodeOrNoneSelected = () => {
return false;
};
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer); return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
} }

View File

@ -34,6 +34,7 @@ import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
import QueryTablesTab from "../Tabs/QueryTablesTab"; import QueryTablesTab from "../Tabs/QueryTablesTab";
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2"; import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import ConflictId from "./ConflictId"; import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId"; import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
@ -223,7 +224,7 @@ export default class Collection implements ViewModels.Collection {
} }
public expandCollapseCollection() { public expandCollapseCollection() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Collection node", description: "Collection node",
@ -276,7 +277,7 @@ export default class Collection implements ViewModels.Collection {
} }
public onDocumentDBDocumentsClick() { public onDocumentDBDocumentsClick() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Documents node", description: "Documents node",
@ -321,7 +322,7 @@ export default class Collection implements ViewModels.Collection {
} }
public onConflictsClick() { public onConflictsClick() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Conflicts node", description: "Conflicts node",
@ -366,7 +367,7 @@ export default class Collection implements ViewModels.Collection {
} }
public onTableEntitiesClick() { public onTableEntitiesClick() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.QueryTables); this.selectedSubnodeKind(ViewModels.CollectionTabKind.QueryTables);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Entities node", description: "Entities node",
@ -419,7 +420,7 @@ export default class Collection implements ViewModels.Collection {
} }
public onGraphDocumentsClick() { public onGraphDocumentsClick() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Documents node", description: "Documents node",
@ -470,7 +471,7 @@ export default class Collection implements ViewModels.Collection {
} }
public onMongoDBDocumentsClick = () => { public onMongoDBDocumentsClick = () => {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Documents node", description: "Documents node",
@ -514,7 +515,7 @@ export default class Collection implements ViewModels.Collection {
}; };
public onSchemaAnalyzerClick = async () => { public onSchemaAnalyzerClick = async () => {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer); this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default; const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
@ -557,7 +558,7 @@ export default class Collection implements ViewModels.Collection {
}; };
public onSettingsClick = async (): Promise<void> => { public onSettingsClick = async (): Promise<void> => {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Settings node", description: "Settings node",
@ -744,21 +745,21 @@ export default class Collection implements ViewModels.Collection {
public createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure { public createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure {
const node = new StoredProcedure(this.container, this, data); const node = new StoredProcedure(this.container, this, data);
this.container.selectedNode(node); useSelectedNode.getState().setSelectedNode(node);
this.children.push(node); this.children.push(node);
return node; return node;
} }
public createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction { public createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction {
const node = new UserDefinedFunction(this.container, this, data); const node = new UserDefinedFunction(this.container, this, data);
this.container.selectedNode(node); useSelectedNode.getState().setSelectedNode(node);
this.children.push(node); this.children.push(node);
return node; return node;
} }
public createTriggerNode(data: TriggerDefinition & Resource): Trigger { public createTriggerNode(data: TriggerDefinition & Resource): Trigger {
const node = new Trigger(this.container, this, data); const node = new Trigger(this.container, this, data);
this.container.selectedNode(node); useSelectedNode.getState().setSelectedNode(node);
this.children.push(node); this.children.push(node);
return node; return node;
} }

View File

@ -18,6 +18,7 @@ import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2"; import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import Collection from "./Collection"; import Collection from "./Collection";
export default class Database implements ViewModels.Database { export default class Database implements ViewModels.Database {
@ -53,7 +54,7 @@ export default class Database implements ViewModels.Database {
} }
public onSettingsClick = () => { public onSettingsClick = () => {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings); this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Settings node", description: "Settings node",
@ -121,25 +122,6 @@ export default class Database implements ViewModels.Database {
} }
}; };
public isDatabaseNodeSelected(): boolean {
return (
!this.isDatabaseExpanded() &&
this.container.selectedNode &&
this.container.selectedNode() &&
this.container.selectedNode().nodeKind === "Database" &&
this.container.selectedNode().id() === this.id()
);
}
public selectDatabase() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Database node",
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public async expandDatabase() { public async expandDatabase() {
if (this.isDatabaseExpanded()) { if (this.isDatabaseExpanded()) {
return; return;

View File

@ -10,6 +10,7 @@ import DocumentsTab from "../Tabs/DocumentsTab";
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab"; import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import DocumentId from "./DocumentId"; import DocumentId from "./DocumentId";
export default class ResourceTokenCollection implements ViewModels.CollectionBase { export default class ResourceTokenCollection implements ViewModels.CollectionBase {
@ -104,7 +105,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
} }
public onDocumentDBDocumentsClick() { public onDocumentDBDocumentsClick() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Documents node", description: "Documents node",

View File

@ -1,114 +1,101 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter"; import { useSelectedNode } from "../useSelectedNode";
describe("ResourceTreeAdapter", () => { describe("useSelectedNode.getState()", () => {
const mockContainer = (): Explorer => const mockTab = {
(({
selectedNode: ko.observable<ViewModels.TreeNode>({
nodeKind: "nodeKind",
rid: "rid",
id: ko.observable<string>("id"),
}),
tabsManager: {
activeTab: ko.observable<TabsBase>({
tabKind: ViewModels.CollectionTabKind.Documents, tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase), } as TabsBase;
},
isNotebookEnabled: ko.observable<boolean>(true),
databases: ko.observable<ViewModels.Database[]>([]),
} as unknown) as Explorer);
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths // TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
describe("isDataNodeSelected", () => { describe("isDataNodeSelected", () => {
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
it("it should not select if no selected node", () => { it("it should not select if no selected node", () => {
const explorer = mockContainer(); const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
explorer.selectedNode(undefined);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy(); expect(isDataNodeSelected).toBeFalsy();
}); });
it("it should not select incorrect subnodekinds", () => { it("it should not select incorrect subnodekinds", () => {
const resourceTreeAdapter = new ResourceTreeAdapter(mockContainer()); useSelectedNode.getState().setSelectedNode({
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined); nodeKind: "nodeKind",
rid: "rid",
id: ko.observable<string>("id"),
});
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy(); expect(isDataNodeSelected).toBeFalsy();
}); });
it("it should not select if no active tab", () => { it("it should not select if no active tab", () => {
const explorer = mockContainer(); useSelectedNode.getState().setSelectedNode({
explorer.tabsManager.activeTab(undefined); nodeKind: "nodeKind",
const resourceTreeAdapter = new ResourceTreeAdapter(explorer); rid: "rid",
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined); id: ko.observable<string>("id"),
});
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(undefined, "foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy(); expect(isDataNodeSelected).toBeFalsy();
}); });
it("should select if correct database node regardless of subnodekinds", () => { it("should select if correct database node regardless of subnodekinds", () => {
const subNodeKind = ViewModels.CollectionTabKind.Documents; const subNodeKind = ViewModels.CollectionTabKind.Documents;
const explorer = mockContainer(); useSelectedNode.getState().setSelectedNode({
explorer.selectedNode(({
nodeKind: "Database", nodeKind: "Database",
rid: "dbrid", rid: "dbrid",
id: ko.observable<string>("dbid"), id: ko.observable<string>("dbid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind), selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode); } as ViewModels.TreeNode);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer); const isDataNodeSelected = useSelectedNode
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", undefined, [ .getState()
ViewModels.CollectionTabKind.Documents, .isDataNodeSelected(mockTab, "dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
]);
expect(isDataNodeSelected).toBeTruthy(); expect(isDataNodeSelected).toBeTruthy();
}); });
it("should select correct collection node (documents or graph node)", () => { it("should select correct collection node (documents or graph node)", () => {
let subNodeKind = ViewModels.CollectionTabKind.Documents; let subNodeKind = ViewModels.CollectionTabKind.Documents;
const explorer = mockContainer(); let activeTab = {
explorer.tabsManager.activeTab({
tabKind: subNodeKind, tabKind: subNodeKind,
} as TabsBase); } as TabsBase;
explorer.selectedNode(({ useSelectedNode.getState().setSelectedNode({
nodeKind: "Collection", nodeKind: "Collection",
rid: "collrid", rid: "collrid",
databaseId: "dbid", databaseId: "dbid",
id: ko.observable<string>("collid"), id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind), selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode); } as ViewModels.TreeNode);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer); let isDataNodeSelected = useSelectedNode
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]); .getState()
.isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
expect(isDataNodeSelected).toBeTruthy(); expect(isDataNodeSelected).toBeTruthy();
subNodeKind = ViewModels.CollectionTabKind.Graph; subNodeKind = ViewModels.CollectionTabKind.Graph;
explorer.tabsManager.activeTab({ activeTab = {
tabKind: subNodeKind, tabKind: subNodeKind,
} as TabsBase); } as TabsBase;
explorer.selectedNode(({ useSelectedNode.getState().setSelectedNode({
nodeKind: "Collection", nodeKind: "Collection",
rid: "collrid", rid: "collrid",
databaseId: "dbid", databaseId: "dbid",
id: ko.observable<string>("collid"), id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind), selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode); } as ViewModels.TreeNode);
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]); isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
expect(isDataNodeSelected).toBeTruthy(); expect(isDataNodeSelected).toBeTruthy();
}); });
it("should not select incorrect collection node (e.g. Settings)", () => { it("should not select incorrect collection node (e.g. Settings)", () => {
const explorer = mockContainer(); useSelectedNode.getState().setSelectedNode({
explorer.selectedNode(({
nodeKind: "Collection", nodeKind: "Collection",
rid: "collrid", rid: "collrid",
databaseId: "dbid", databaseId: "dbid",
id: ko.observable<string>("collid"), id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents), selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents),
} as unknown) as ViewModels.TreeNode); } as ViewModels.TreeNode);
explorer.tabsManager.activeTab({ const activeTab = {
tabKind: ViewModels.CollectionTabKind.Documents, tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase); } as TabsBase;
const resourceTreeAdapter = new ResourceTreeAdapter(explorer); const isDataNodeSelected = useSelectedNode
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [ .getState()
ViewModels.CollectionTabKind.Settings, .isDataNodeSelected(activeTab, "dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
]);
expect(isDataNodeSelected).toBeFalsy(); expect(isDataNodeSelected).toBeFalsy();
}); });
}); });

View File

@ -1,5 +1,4 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import * as ko from "knockout";
import React from "react"; import React from "react";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@ -208,13 +207,6 @@ const schema: DataModels.ISchema = {
], ],
}; };
const createMockContainer = (): Explorer => {
const mockContainer = new Explorer();
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
return mockContainer;
};
const createMockCollection = (): ViewModels.Collection => { const createMockCollection = (): ViewModels.Collection => {
const mockCollection = {} as DataModels.Collection; const mockCollection = {} as DataModels.Collection;
mockCollection._rid = "fakeRid"; mockCollection._rid = "fakeRid";
@ -223,17 +215,13 @@ const createMockCollection = (): ViewModels.Collection => {
mockCollection.analyticalStorageTtl = 0; mockCollection.analyticalStorageTtl = 0;
mockCollection.schema = schema; mockCollection.schema = schema;
const mockCollectionVM: ViewModels.Collection = new Collection( const mockCollectionVM: ViewModels.Collection = new Collection(new Explorer(), "fakeDatabaseId", mockCollection);
createMockContainer(),
"fakeDatabaseId",
mockCollection
);
return mockCollectionVM; return mockCollectionVM;
}; };
describe("Resource tree for schema", () => { describe("Resource tree for schema", () => {
const mockContainer: Explorer = createMockContainer(); const mockContainer = new Explorer();
const resourceTree = new ResourceTreeAdapter(mockContainer); const resourceTree = new ResourceTreeAdapter(mockContainer);
it("should render", () => { it("should render", () => {

View File

@ -33,6 +33,7 @@ import { NotebookContentItem, NotebookContentItemType } from "../Notebook/Notebo
import { NotebookUtil } from "../Notebook/NotebookUtil"; import { NotebookUtil } from "../Notebook/NotebookUtil";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction"; import UserDefinedFunction from "./UserDefinedFunction";
@ -54,7 +55,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
public constructor(private container: Explorer) { public constructor(private container: Explorer) {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); useSelectedNode.subscribe(() => this.triggerRender());
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender()); this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender()); this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender());
@ -183,7 +184,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
isExpanded: false, isExpanded: false,
className: "databaseHeader", className: "databaseHeader",
children: [], children: [],
isSelected: () => this.isDataNodeSelected(database.id()), isSelected: () =>
useSelectedNode.getState().isDataNodeSelected(this.container.tabsManager.activeTab(), database.id()),
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()), contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
onClick: async (isExpanded) => { onClick: async (isExpanded) => {
// Rewritten version of expandCollapseDatabase(): // Rewritten version of expandCollapseDatabase():
@ -196,18 +198,22 @@ export class ResourceTreeAdapter implements ReactAdapter {
await database.expandDatabase(); await database.expandDatabase();
} }
databaseNode.isLoading = false; databaseNode.isLoading = false;
database.selectDatabase(); useSelectedNode.getState().setSelectedNode(database);
useCommandBar.getState().setContextButtons([]); useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id()); this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
}, },
onContextMenuOpen: () => this.container.selectedNode(database), onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
}; };
if (database.isDatabaseShared()) { if (database.isDatabaseShared()) {
databaseNode.children.push({ databaseNode.children.push({
label: "Scale", label: "Scale",
isSelected: () => isSelected: () =>
this.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), database.id(), undefined, [
ViewModels.CollectionTabKind.DatabaseSettings,
]),
onClick: database.onSettingsClick.bind(database), onClick: database.onSettingsClick.bind(database),
}); });
} }
@ -253,7 +259,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection); mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
}, },
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Documents, ViewModels.CollectionTabKind.Documents,
ViewModels.CollectionTabKind.Graph, ViewModels.CollectionTabKind.Graph,
]), ]),
@ -265,7 +273,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Schema (Preview)", label: "Schema (Preview)",
onClick: collection.onSchemaAnalyzerClick.bind(collection), onClick: collection.onSchemaAnalyzerClick.bind(collection),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.SchemaAnalyzer, ViewModels.CollectionTabKind.SchemaAnalyzer,
]), ]),
}); });
@ -276,7 +286,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings", label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings",
onClick: collection.onSettingsClick.bind(collection), onClick: collection.onSettingsClick.bind(collection),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Settings,
]),
}); });
} }
@ -302,7 +316,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Conflicts", label: "Conflicts",
onClick: collection.onConflictsClick.bind(collection), onClick: collection.onConflictsClick.bind(collection),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Conflicts,
]),
}); });
} }
@ -315,7 +333,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection), contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
onClick: () => { onClick: () => {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]); useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab( this.container.tabsManager.refreshActiveTab(
(tab: TabsBase) => (tab: TabsBase) =>
@ -329,8 +347,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
collection.loadTriggers(); collection.loadTriggers();
} }
}, },
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()), isSelected: () =>
onContextMenuOpen: () => this.container.selectedNode(collection), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
}; };
} }
@ -341,7 +362,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: sp.id(), label: sp.id(),
onClick: sp.open.bind(sp), onClick: sp.open.bind(sp),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.StoredProcedures, ViewModels.CollectionTabKind.StoredProcedures,
]), ]),
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp), contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
@ -363,7 +386,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: udf.id(), label: udf.id(),
onClick: udf.open.bind(udf), onClick: udf.open.bind(udf),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.UserDefinedFunctions, ViewModels.CollectionTabKind.UserDefinedFunctions,
]), ]),
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems( contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
@ -388,7 +413,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: trigger.id(), label: trigger.id(),
onClick: trigger.open.bind(trigger), onClick: trigger.open.bind(trigger),
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Triggers,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger), contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
})), })),
onClick: () => { onClick: () => {
@ -818,44 +847,4 @@ export class ResourceTreeAdapter implements ReactAdapter {
public triggerRender() { public triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now())); window.requestAnimationFrame(() => this.parameters(Date.now()));
} }
/**
* public for testing purposes
* @param databaseId
* @param collectionId
* @param subnodeKinds
*/
public isDataNodeSelected(
databaseId: string,
collectionId?: string,
subnodeKinds?: ViewModels.CollectionTabKind[]
): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) {
return false;
}
const selectedNode = this.container.selectedNode();
const isNodeSelected = collectionId
? (selectedNode as ViewModels.Collection).databaseId === databaseId && selectedNode.id() === collectionId
: selectedNode.id() === databaseId;
if (!isNodeSelected) {
return false;
}
if (subnodeKinds === undefined || !Array.isArray(subnodeKinds)) {
return true;
}
const activeTab = this.container.tabsManager.activeTab();
const selectedSubnodeKind = collectionId
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
return (
activeTab &&
subnodeKinds.includes(activeTab.tabKind) &&
selectedSubnodeKind !== undefined &&
subnodeKinds.includes(selectedSubnodeKind)
);
}
} }

View File

@ -11,7 +11,6 @@ import { ResourceTreeAdapterForResourceToken } from "./ResourceTreeAdapterForRes
const createMockContainer = (): Explorer => { const createMockContainer = (): Explorer => {
let mockContainer = {} as Explorer; let mockContainer = {} as Explorer;
mockContainer.resourceTokenCollection = createMockCollection(mockContainer); mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
return mockContainer; return mockContainer;
}; };

View File

@ -9,6 +9,7 @@ import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../Notebook/NotebookContentItem";
import { useSelectedNode } from "../useSelectedNode";
export class ResourceTreeAdapterForResourceToken implements ReactAdapter { export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
@ -18,7 +19,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.container.resourceTokenCollection.subscribe(() => this.triggerRender()); this.container.resourceTokenCollection.subscribe(() => this.triggerRender());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); useSelectedNode.subscribe(() => this.triggerRender());
this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender()); this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
this.triggerRender(); this.triggerRender();
@ -48,7 +49,11 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection); mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
}, },
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents), useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Documents,
]),
}); });
const collectionNode: TreeNode = { const collectionNode: TreeNode = {
@ -59,13 +64,16 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
className: "collectionHeader", className: "collectionHeader",
onClick: () => { onClick: () => {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]); useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab( this.container.tabsManager.refreshActiveTab(
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId (tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
); );
}, },
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()), isSelected: () =>
useSelectedNode
.getState()
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
}; };
return { return {
@ -75,35 +83,6 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
}; };
} }
public isDataNodeSelected(
databaseId: string,
collectionId?: string,
subnodeKind?: ViewModels.CollectionTabKind
): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) {
return false;
}
const selectedNode = this.container.selectedNode();
const isNodeSelected = collectionId
? (selectedNode as ViewModels.Collection).databaseId === databaseId && selectedNode.id() === collectionId
: selectedNode.id() === databaseId;
if (!isNodeSelected) {
return false;
}
if (!subnodeKind) {
return true;
}
const activeTab = this.container.tabsManager.activeTab();
const selectedSubnodeKind = collectionId
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
return activeTab && activeTab.tabKind === subnodeKind && selectedSubnodeKind === subnodeKind;
}
public triggerRender() { public triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now())); window.requestAnimationFrame(() => this.parameters(Date.now()));
} }

View File

@ -11,6 +11,7 @@ import Explorer from "../Explorer";
import { getErrorMessage } from "../Tables/Utilities"; import { getErrorMessage } from "../Tables/Utilities";
import { NewStoredProcedureTab } from "../Tabs/StoredProcedureTab/StoredProcedureTab"; import { NewStoredProcedureTab } from "../Tabs/StoredProcedureTab/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useSelectedNode } from "../useSelectedNode";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
function sample(prefix) { function sample(prefix) {
@ -87,7 +88,7 @@ export default class StoredProcedure {
} }
public select() { public select() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Stored procedure node", description: "Stored procedure node",

View File

@ -7,6 +7,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import TriggerTab from "../Tabs/TriggerTab"; import TriggerTab from "../Tabs/TriggerTab";
import { useSelectedNode } from "../useSelectedNode";
export default class Trigger { export default class Trigger {
public nodeKind: string; public nodeKind: string;
@ -32,7 +33,7 @@ export default class Trigger {
} }
public select() { public select() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Trigger node", description: "Trigger node",

View File

@ -7,6 +7,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab"; import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
import { useSelectedNode } from "../useSelectedNode";
export default class UserDefinedFunction { export default class UserDefinedFunction {
public nodeKind: string; public nodeKind: string;
@ -82,7 +83,7 @@ export default class UserDefinedFunction {
}; };
public select() { public select() {
this.container.selectedNode(this); useSelectedNode.getState().setSelectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "UDF item node", description: "UDF item node",

View File

@ -0,0 +1,84 @@
import _ from "underscore";
import create, { UseStore } from "zustand";
import * as ViewModels from "../Contracts/ViewModels";
import TabsBase from "./Tabs/TabsBase";
import { useDatabases } from "./useDatabases";
export interface SelectedNodeState {
selectedNode: ViewModels.TreeNode;
setSelectedNode: (node: ViewModels.TreeNode) => void;
isDatabaseNodeOrNoneSelected: () => boolean;
findSelectedDatabase: () => ViewModels.Database;
findSelectedCollection: () => ViewModels.Collection;
isDataNodeSelected: (
activeTab: TabsBase,
databaseId: string,
collectionId?: string,
subnodeKinds?: ViewModels.CollectionTabKind[]
) => boolean;
}
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
selectedNode: undefined,
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
isDatabaseNodeOrNoneSelected: (): boolean => {
const selectedNode = get().selectedNode;
return !selectedNode || selectedNode.nodeKind === "Database";
},
findSelectedDatabase: (): ViewModels.Database => {
const selectedNode = get().selectedNode;
if (!selectedNode) {
return undefined;
}
if (selectedNode.nodeKind === "Database") {
return _.find(
useDatabases.getState().databases,
(database: ViewModels.Database) => database.id() === selectedNode.id()
);
}
if (selectedNode.nodeKind === "Collection") {
return selectedNode.database;
}
return selectedNode.collection?.database;
},
findSelectedCollection: (): ViewModels.Collection => {
const selectedNode = get().selectedNode;
return (selectedNode.nodeKind === "Collection" ? selectedNode : selectedNode.collection) as ViewModels.Collection;
},
isDataNodeSelected: (
activeTab: TabsBase,
databaseId: string,
collectionId?: string,
subnodeKinds?: ViewModels.CollectionTabKind[]
): boolean => {
const selectedNode = get().selectedNode;
if (!selectedNode) {
return false;
}
const isNodeSelected = collectionId
? (selectedNode as ViewModels.Collection).databaseId === databaseId && selectedNode.id() === collectionId
: selectedNode.id() === databaseId;
if (!isNodeSelected) {
return false;
}
if (subnodeKinds === undefined || !Array.isArray(subnodeKinds)) {
return true;
}
const selectedSubnodeKind = collectionId
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
return (
activeTab &&
subnodeKinds.includes(activeTab.tabKind) &&
selectedSubnodeKind !== undefined &&
subnodeKinds.includes(selectedSubnodeKind)
);
},
}));