mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
fix(sidebar): discover object groups in collapsed databases
Closes #9174
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/lib/sidebar/sidebarSearchTree";
|
||||
import { createSidebarLabelMatcher } from "@/lib/sidebar/sidebarSearch";
|
||||
import { collectSidebarRegexIndexScopes, resolveSidebarRemoteSearchQuery, resolveSidebarSearchDispatchMode } from "@/lib/sidebar/sidebarRegexSearchIndex";
|
||||
import { needsSidebarObjectGroupDiscovery } from "@/lib/sidebar/sidebarSearchDiscovery";
|
||||
import { createSidebarSearchExpansionState } from "@/lib/sidebar/sidebarSearchExpansionState";
|
||||
import { createSidebarSearchLoadingTracker } from "@/lib/sidebar/sidebarSearchLoadingTracker";
|
||||
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
@@ -405,7 +406,7 @@ function collectExpandedObjectSearchTargets(node: TreeNode, tasks: SidebarSearch
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (refreshedNodeIds && isSidebarSearchContainer(node) && !node.children?.length && (!scheduledNodeIds || !scheduledNodeIds.has(node.id))) {
|
||||
if (refreshedNodeIds && isSidebarSearchContainer(node) && needsSidebarObjectGroupDiscovery(node, searchableObjectGroupTypes) && (!scheduledNodeIds || !scheduledNodeIds.has(node.id))) {
|
||||
scheduledNodeIds?.add(node.id);
|
||||
const wasCollapsed = node.isExpanded !== true;
|
||||
searchExpansionState.markFiltered(node.id, wasCollapsed);
|
||||
|
||||
+5
-1
@@ -7,7 +7,11 @@ const source = readFileSync(new URL("../ConnectionTree.vue", import.meta.url), "
|
||||
describe("ConnectionTree global search loading", () => {
|
||||
it("discovers collapsed database and schema containers before searching object groups", () => {
|
||||
expect(source).toContain("function isSidebarSearchContainer(node: TreeNode)");
|
||||
expect(source).toMatch(/isSidebarSearchContainer\(node\) && !node\.children\?\.length/);
|
||||
// A collapsed database already holds the saved-SQL root, so it has children
|
||||
// even when its object groups were never fetched; the search has to look for
|
||||
// a searchable group instead of an empty child list.
|
||||
expect(source).toMatch(/isSidebarSearchContainer\(node\) && needsSidebarObjectGroupDiscovery\(node, searchableObjectGroupTypes\)/);
|
||||
expect(source).not.toMatch(/isSidebarSearchContainer\(node\) && !node\.children\?\.length/);
|
||||
expect(source).toContain("store.loadTreeNodeChildren(node, { force: true, expectedSidebarSearchQuery: store.sidebarSearchQuery })");
|
||||
expect(source).toContain("searchExpansionState.markFiltered(node.id, wasCollapsed)");
|
||||
expect(source).toMatch(/if \(refreshedNodeIds && node\.children\) \{[\s\S]*?searchableObjectGroupTypes\.has\(child\.type\)/);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
import { needsSidebarObjectGroupDiscovery } from "@/lib/sidebar/sidebarSearchDiscovery";
|
||||
|
||||
const objectGroupTypes: ReadonlySet<TreeNodeType> = new Set<TreeNodeType>(["group-tables", "group-views"]);
|
||||
|
||||
function node(partial: Partial<TreeNode> & Pick<TreeNode, "id" | "type">): TreeNode {
|
||||
return { label: partial.id, ...partial };
|
||||
}
|
||||
|
||||
describe("needsSidebarObjectGroupDiscovery", () => {
|
||||
it("asks for discovery while a container only holds its saved SQL root", () => {
|
||||
// Reproduces #9174: the database node already has a child (the saved SQL
|
||||
// root) before its object groups are fetched, so an empty-child check would
|
||||
// skip it and its tables would never join the search.
|
||||
const database = node({
|
||||
id: "mysql/dbx",
|
||||
type: "database",
|
||||
connectionId: "c1",
|
||||
database: "dbx",
|
||||
children: [node({ id: "saved-sql-root", type: "saved-sql-root", connectionId: "c1", database: "dbx" })],
|
||||
});
|
||||
expect(needsSidebarObjectGroupDiscovery(database, objectGroupTypes)).toBe(true);
|
||||
});
|
||||
|
||||
it("asks for discovery when the container has no children at all", () => {
|
||||
const schema = node({ id: "pg/public", type: "schema", connectionId: "c1", database: "postgres" });
|
||||
expect(needsSidebarObjectGroupDiscovery(schema, objectGroupTypes)).toBe(true);
|
||||
expect(needsSidebarObjectGroupDiscovery({ ...schema, children: [] }, objectGroupTypes)).toBe(true);
|
||||
});
|
||||
|
||||
it("stops asking once a searchable object group is loaded", () => {
|
||||
const database = node({
|
||||
id: "mysql/dbx",
|
||||
type: "database",
|
||||
connectionId: "c1",
|
||||
database: "dbx",
|
||||
children: [node({ id: "saved-sql-root", type: "saved-sql-root", connectionId: "c1", database: "dbx" }), node({ id: "group-views", type: "group-views", connectionId: "c1", database: "dbx" })],
|
||||
});
|
||||
expect(needsSidebarObjectGroupDiscovery(database, objectGroupTypes)).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores non-group children such as tables already listed under the container", () => {
|
||||
const database = node({
|
||||
id: "mysql/dbx",
|
||||
type: "database",
|
||||
connectionId: "c1",
|
||||
database: "dbx",
|
||||
children: [node({ id: "dbx.cells_probe", type: "table", connectionId: "c1", database: "dbx" }), node({ id: "user-admin", type: "user-admin" })],
|
||||
});
|
||||
expect(needsSidebarObjectGroupDiscovery(database, objectGroupTypes)).toBe(true);
|
||||
});
|
||||
|
||||
it("ignores object groups owned by another connection", () => {
|
||||
const database = node({
|
||||
id: "mysql/dbx",
|
||||
type: "database",
|
||||
connectionId: "c1",
|
||||
database: "dbx",
|
||||
children: [node({ id: "group-tables", type: "group-tables" })],
|
||||
});
|
||||
expect(needsSidebarObjectGroupDiscovery(database, objectGroupTypes)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
|
||||
/**
|
||||
* Whether a sidebar search still has to fetch a container's on-demand object
|
||||
* groups before it can look for matches below it.
|
||||
*
|
||||
* A collapsed container is not necessarily childless: every database keeps its
|
||||
* saved-SQL root (`tree.queries`) as a child even before the object groups are
|
||||
* fetched, so "no children" is not a reliable discovery signal. Checking for a
|
||||
* searchable group instead keeps databases whose groups were never loaded out
|
||||
* of the search, which made a table that exists in several databases appear
|
||||
* under only one of them.
|
||||
*/
|
||||
export function needsSidebarObjectGroupDiscovery(node: TreeNode, objectGroupTypes: ReadonlySet<TreeNodeType>): boolean {
|
||||
return !node.children?.some((child) => child.connectionId != null && objectGroupTypes.has(child.type));
|
||||
}
|
||||
Reference in New Issue
Block a user