fix(quick-open): index grouped database objects

This commit is contained in:
t8y2
2026-07-11 22:45:04 +08:00
parent bfdd7e5b1a
commit ead9a05c3c
4 changed files with 145 additions and 16 deletions
+6
View File
@@ -1394,6 +1394,12 @@ async function handleQuickOpenSelect(item: any) {
}
}
return;
} else if (item.type === "schema") {
const dbNode = findTreeNodeById(connectionStore.treeNodes, `${item.connectionId}:${item.database}`);
if (dbNode && !dbNode.isExpanded) await connectionStore.loadSchemas(item.connectionId, item.database);
const schemaNode = findTreeNodeById(connectionStore.treeNodes, `${item.connectionId}:${item.database}:${item.schema}`);
if (schemaNode && !schemaNode.isExpanded) await connectionStore.loadTables(item.connectionId, item.database, item.schema);
return;
} else if (item.type === "table" || item.type === "view" || item.type === "materialized_view") {
// Open the table/view in a data tab
await openTableTarget({
@@ -89,6 +89,8 @@ function getTypeLabel(type: string): string {
return t("common.connection");
case "database":
return t("common.database");
case "schema":
return t("common.schema");
case "table":
return t("common.table");
case "view":
@@ -1,5 +1,5 @@
import { describe, expect, it, beforeEach, vi } from "vitest";
import { useQuickOpen, type QuickOpenItem } from "@/composables/useQuickOpen";
import { describe, expect, it, vi } from "vitest";
import { useQuickOpen } from "@/composables/useQuickOpen";
import { useConnectionStore } from "@/stores/connectionStore";
vi.mock("@/stores/connectionStore", () => ({
@@ -110,6 +110,102 @@ describe("useQuickOpen", () => {
});
describe("filtering and searching", () => {
it("indexes database objects under connection groups", () => {
const mockStore = {
connections: [{ id: "conn1", name: "Grouped PG", db_type: "postgres" }],
treeNodes: [
{
id: "group1",
type: "connection-group",
label: "Production",
children: [
{
id: "conn1",
connectionId: "conn1",
type: "connection",
label: "Grouped PG",
children: [
{
id: "conn1:postgres",
connectionId: "conn1",
type: "database",
database: "postgres",
label: "postgres",
children: [
{
id: "conn1:postgres:public",
connectionId: "conn1",
type: "schema",
database: "postgres",
schema: "public",
label: "public",
children: [
{
id: "conn1:postgres:public:users",
connectionId: "conn1",
type: "table",
database: "postgres",
schema: "public",
label: "users",
},
],
},
],
},
],
},
],
},
],
};
vi.mocked(useConnectionStore).mockReturnValue(mockStore as any);
const { filteredItems, setQuery } = useQuickOpen();
setQuery("users");
expect(filteredItems.value.map((item) => item.label)).toContain("users");
});
it("includes schemas as quick open results", () => {
const mockStore = {
connections: [{ id: "conn1", name: "PG", db_type: "postgres" }],
treeNodes: [
{
id: "conn1",
connectionId: "conn1",
type: "connection",
label: "PG",
children: [
{
id: "conn1:postgres",
connectionId: "conn1",
type: "database",
database: "postgres",
label: "postgres",
children: [
{
id: "conn1:postgres:analytics",
connectionId: "conn1",
type: "schema",
database: "postgres",
schema: "analytics",
label: "analytics",
children: [],
},
],
},
],
},
],
};
vi.mocked(useConnectionStore).mockReturnValue(mockStore as any);
const { filteredItems, setQuery } = useQuickOpen();
setQuery("analytics");
expect(filteredItems.value).toEqual(expect.arrayContaining([expect.objectContaining({ type: "schema", label: "analytics" })]));
});
it("should filter items based on search query", () => {
const mockStore = {
connections: [
+39 -14
View File
@@ -4,7 +4,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
export interface QuickOpenItem {
id: string;
type: "connection" | "database" | "table" | "view" | "materialized_view" | "procedure" | "function" | "sequence" | "package" | "package-body";
type: "connection" | "database" | "schema" | "table" | "view" | "materialized_view" | "procedure" | "function" | "sequence" | "package" | "package-body";
label: string;
description?: string;
connectionId: string;
@@ -87,7 +87,9 @@ export function useQuickOpen() {
// Add databases and tables from tree nodes
// Filter tree nodes by connection
for (const conn of connections) {
const connectionTreeNodes = treeNodes.filter((node) => node.connectionId === conn.id);
// Connections may live under sidebar groups, so locate their tree recursively.
const connectionTreeNode = findConnectionTreeNode(treeNodes, conn.id);
const connectionTreeNodes = connectionTreeNode?.children || treeNodes.filter((node) => node.connectionId === conn.id);
if (connectionTreeNodes.length === 0) continue;
// Process tree nodes to extract databases and tables
@@ -121,9 +123,20 @@ export function useQuickOpen() {
});
}
// Schema nodes - skip them but process their children
if (node.type === "schema" && node.children) {
processDatabaseTreeNodes(node.children, conn, items);
// Schema nodes are navigable results and also contain database objects.
if (node.type === "schema" && node.database && node.schema) {
items.push({
id: `schema-${conn.id}-${node.database}-${node.schema}`,
type: "schema",
label: node.label || node.schema,
description: `${conn.name} / ${node.database}`,
connectionId: conn.id,
database: node.database,
schema: node.schema,
connectionName: conn.name,
searchText: `${conn.name} ${node.database} ${node.schema}`,
});
if (node.children) processDatabaseTreeNodes(node.children, conn, items);
continue;
}
@@ -262,6 +275,17 @@ export function useQuickOpen() {
}
}
function findConnectionTreeNode(nodes: any[], connectionId: string): any | undefined {
for (const node of nodes) {
if (node.type === "connection" && node.connectionId === connectionId) return node;
if (node.children) {
const match = findConnectionTreeNode(node.children, connectionId);
if (match) return match;
}
}
return undefined;
}
const filteredItems = computed((): MatchedItem[] => {
if (!searchQuery.value.trim()) {
return allItems.value.map((item) => ({
@@ -293,16 +317,17 @@ export function useQuickOpen() {
const typeOrder = {
connection: 0,
database: 1,
table: 2,
view: 3,
materialized_view: 4,
procedure: 5,
function: 6,
sequence: 7,
package: 8,
"package-body": 9,
schema: 2,
table: 3,
view: 4,
materialized_view: 5,
procedure: 6,
function: 7,
sequence: 8,
package: 9,
"package-body": 10,
};
return (typeOrder[a.type] ?? 10) - (typeOrder[b.type] ?? 10);
return typeOrder[a.type] - typeOrder[b.type];
});
return matched;