From ead9a05c3c4eab4f9bcdacc96a14e9199fc700e2 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sat, 11 Jul 2026 22:45:04 +0800 Subject: [PATCH] fix(quick-open): index grouped database objects --- apps/desktop/src/App.vue | 6 ++ .../components/quick-open/QuickOpenDialog.vue | 2 + .../__tests__/useQuickOpen.spec.ts | 100 +++++++++++++++++- apps/desktop/src/composables/useQuickOpen.ts | 53 +++++++--- 4 files changed, 145 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 9e98e81869..96257cb41c 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -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({ diff --git a/apps/desktop/src/components/quick-open/QuickOpenDialog.vue b/apps/desktop/src/components/quick-open/QuickOpenDialog.vue index 7bd380ce00..e69d4b93a5 100644 --- a/apps/desktop/src/components/quick-open/QuickOpenDialog.vue +++ b/apps/desktop/src/components/quick-open/QuickOpenDialog.vue @@ -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": diff --git a/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts b/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts index 690ceea9ba..a889944765 100644 --- a/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts +++ b/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts @@ -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: [ diff --git a/apps/desktop/src/composables/useQuickOpen.ts b/apps/desktop/src/composables/useQuickOpen.ts index 395d86883d..3e7ac0c897 100644 --- a/apps/desktop/src/composables/useQuickOpen.ts +++ b/apps/desktop/src/composables/useQuickOpen.ts @@ -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;