mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 15:06:55 +00:00
Fix resource tree node selection bug (#170)
* Fix bug with resource tree selection for graphs * Reformat and type fixes
This commit is contained in:
parent
0a24a0b73e
commit
d471cff77c
112
src/Explorer/Tree/ResourceTreeAdapter.test.ts
Normal file
112
src/Explorer/Tree/ResourceTreeAdapter.test.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import Explorer from "../Explorer";
|
||||||
|
import * as ko from "knockout";
|
||||||
|
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
|
||||||
|
describe("ResourceTreeAdapter", () => {
|
||||||
|
const mockContainer = (): Explorer =>
|
||||||
|
(({
|
||||||
|
selectedNode: ko.observable<ViewModels.TreeNode>({
|
||||||
|
nodeKind: "nodeKind",
|
||||||
|
rid: "rid",
|
||||||
|
id: ko.observable<string>("id")
|
||||||
|
}),
|
||||||
|
tabsManager: {
|
||||||
|
activeTab: ko.observable<TabsBase>({
|
||||||
|
tabKind: ViewModels.CollectionTabKind.Documents
|
||||||
|
} as TabsBase)
|
||||||
|
},
|
||||||
|
isNotebookEnabled: ko.observable<boolean>(true),
|
||||||
|
nonSystemDatabases: 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
|
||||||
|
describe("isDataNodeSelected", () => {
|
||||||
|
it("it should not select if no selected node", () => {
|
||||||
|
const explorer = mockContainer();
|
||||||
|
explorer.selectedNode(undefined);
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||||
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it should not select incorrect subnodekinds", () => {
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(mockContainer());
|
||||||
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||||
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it should not select if no active tab", () => {
|
||||||
|
const explorer = mockContainer();
|
||||||
|
explorer.tabsManager.activeTab(undefined);
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||||
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select if correct database node regardless of subnodekinds", () => {
|
||||||
|
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||||
|
const explorer = mockContainer();
|
||||||
|
explorer.selectedNode(({
|
||||||
|
nodeKind: "Database",
|
||||||
|
rid: "dbrid",
|
||||||
|
id: ko.observable<string>("id"),
|
||||||
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
|
} as unknown) as ViewModels.TreeNode);
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbrid", "Database", [
|
||||||
|
ViewModels.CollectionTabKind.Documents
|
||||||
|
]);
|
||||||
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select correct collection node (documents or graph node)", () => {
|
||||||
|
let subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||||
|
const explorer = mockContainer();
|
||||||
|
explorer.tabsManager.activeTab({
|
||||||
|
tabKind: subNodeKind
|
||||||
|
} as TabsBase);
|
||||||
|
explorer.selectedNode(({
|
||||||
|
nodeKind: "Collection",
|
||||||
|
rid: "collrid",
|
||||||
|
id: ko.observable<string>("id"),
|
||||||
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
|
} as unknown) as ViewModels.TreeNode);
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
|
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
||||||
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
|
|
||||||
|
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||||
|
explorer.tabsManager.activeTab({
|
||||||
|
tabKind: subNodeKind
|
||||||
|
} as TabsBase);
|
||||||
|
explorer.selectedNode(({
|
||||||
|
nodeKind: "Collection",
|
||||||
|
rid: "collrid",
|
||||||
|
id: ko.observable<string>("id"),
|
||||||
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
|
} as unknown) as ViewModels.TreeNode);
|
||||||
|
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
||||||
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not select incorrect collection node (e.g. Settings)", () => {
|
||||||
|
const explorer = mockContainer();
|
||||||
|
explorer.selectedNode(({
|
||||||
|
nodeKind: "Collection",
|
||||||
|
rid: "collrid",
|
||||||
|
id: ko.observable<string>("id"),
|
||||||
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
|
||||||
|
} as unknown) as ViewModels.TreeNode);
|
||||||
|
explorer.tabsManager.activeTab({
|
||||||
|
tabKind: ViewModels.CollectionTabKind.Documents
|
||||||
|
} as TabsBase);
|
||||||
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [
|
||||||
|
ViewModels.CollectionTabKind.Settings
|
||||||
|
]);
|
||||||
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -191,7 +191,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
databaseNode.children.push({
|
databaseNode.children.push({
|
||||||
label: "Scale",
|
label: "Scale",
|
||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
this.isDataNodeSelected(database.rid, "Database", ViewModels.CollectionTabKind.DatabaseSettings),
|
this.isDataNodeSelected(database.rid, "Database", [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||||
onClick: database.onSettingsClick.bind(database)
|
onClick: database.onSettingsClick.bind(database)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -235,14 +235,18 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
data: collection.rid
|
data: collection.rid
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Documents),
|
isSelected: () =>
|
||||||
|
this.isDataNodeSelected(collection.rid, "Collection", [
|
||||||
|
ViewModels.CollectionTabKind.Documents,
|
||||||
|
ViewModels.CollectionTabKind.Graph
|
||||||
|
]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
|
||||||
});
|
});
|
||||||
|
|
||||||
children.push({
|
children.push({
|
||||||
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
|
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
|
||||||
onClick: collection.onSettingsClick.bind(collection),
|
onClick: collection.onSettingsClick.bind(collection),
|
||||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Settings)
|
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Settings])
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
||||||
@ -264,7 +268,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
children.push({
|
children.push({
|
||||||
label: "Conflicts",
|
label: "Conflicts",
|
||||||
onClick: collection.onConflictsClick.bind(collection),
|
onClick: collection.onConflictsClick.bind(collection),
|
||||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Conflicts)
|
isSelected: () =>
|
||||||
|
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Conflicts])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +307,7 @@ 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.rid, "Collection", ViewModels.CollectionTabKind.StoredProcedures),
|
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.StoredProcedures]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -321,7 +326,7 @@ 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.rid, "Collection", ViewModels.CollectionTabKind.UserDefinedFunctions),
|
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.UserDefinedFunctions]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -339,7 +344,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
children: collection.triggers().map((trigger: Trigger) => ({
|
children: collection.triggers().map((trigger: Trigger) => ({
|
||||||
label: trigger.id(),
|
label: trigger.id(),
|
||||||
onClick: trigger.open.bind(trigger),
|
onClick: trigger.open.bind(trigger),
|
||||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Triggers),
|
isSelected: () =>
|
||||||
|
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Triggers]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -697,13 +703,19 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
|
/**
|
||||||
|
* public for testing purposes
|
||||||
|
* @param rid
|
||||||
|
* @param nodeKind
|
||||||
|
* @param subnodeKinds
|
||||||
|
*/
|
||||||
|
public isDataNodeSelected(rid: string, nodeKind: string, subnodeKinds: ViewModels.CollectionTabKind[]): boolean {
|
||||||
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const selectedNode = this.container.selectedNode();
|
const selectedNode = this.container.selectedNode();
|
||||||
|
|
||||||
if (subnodeKind === undefined) {
|
if (subnodeKinds === undefined || !Array.isArray(subnodeKinds)) {
|
||||||
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
|
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
|
||||||
} else {
|
} else {
|
||||||
const activeTab = this.container.tabsManager.activeTab();
|
const activeTab = this.container.tabsManager.activeTab();
|
||||||
@ -716,10 +728,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
activeTab &&
|
activeTab &&
|
||||||
activeTab.tabKind === subnodeKind &&
|
subnodeKinds.includes(activeTab.tabKind) &&
|
||||||
selectedNode.rid === rid &&
|
selectedNode.rid === rid &&
|
||||||
selectedSubnodeKind !== undefined &&
|
selectedSubnodeKind !== undefined &&
|
||||||
selectedSubnodeKind === subnodeKind
|
subnodeKinds.includes(selectedSubnodeKind)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user