From b5a88642206766a0c87f53bf73b32439496372b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=86=8A?= <49681654+xzxlove@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:24:30 +0800 Subject: [PATCH] fix(sidebar): search connections by host and username --- apps/desktop/src/lib/sidebar/sidebarLayout.ts | 5 +++++ .../desktop/src/lib/sidebar/sidebarSearchTree.ts | 16 +++++++++------- apps/desktop/src/stores/connectionStore.ts | 4 ++++ apps/desktop/src/types/database.ts | 2 ++ packages/app-tests/sidebarLayout.test.ts | 10 ++++++++++ packages/app-tests/sidebarSearchTree.test.ts | 16 ++++++++++++++++ 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/lib/sidebar/sidebarLayout.ts b/apps/desktop/src/lib/sidebar/sidebarLayout.ts index acea69a4b8..d452f6d698 100644 --- a/apps/desktop/src/lib/sidebar/sidebarLayout.ts +++ b/apps/desktop/src/lib/sidebar/sidebarLayout.ts @@ -121,11 +121,16 @@ export function remapSidebarLayoutConnectionIds(layout: SidebarLayout, connectio }; } +export function connectionSidebarSearchAliases(config: Pick): string[] { + return [config.host, config.username].filter((value) => value.trim().length > 0); +} + function makeConnectionNode(config: ConnectionConfig, pinned: boolean): TreeNode { return { id: config.id, label: config.name, type: "connection", + searchAliases: connectionSidebarSearchAliases(config), connectionId: config.id, isExpanded: false, children: [], diff --git a/apps/desktop/src/lib/sidebar/sidebarSearchTree.ts b/apps/desktop/src/lib/sidebar/sidebarSearchTree.ts index 679cbc192d..6f1c6a41f7 100644 --- a/apps/desktop/src/lib/sidebar/sidebarSearchTree.ts +++ b/apps/desktop/src/lib/sidebar/sidebarSearchTree.ts @@ -4,12 +4,14 @@ import { createSidebarLabelMatcher, type SidebarLabelMatcher } from "@/lib/sideb const preserveMatchedSubtreeTypes = new Set(["connection", "database", "schema", "table", "view", "mongo-db", "mongo-collection"]); const hiddenSearchNodeTypes = new Set(["user-admin", "dameng-job-admin"]); -function bestMatch(matchLabel: SidebarLabelMatcher, label: string, comment?: string | null) { - const lm = matchLabel(label); - if (!comment) return lm; - const cm = matchLabel(comment); - if (lm && cm) return lm.score >= cm.score ? lm : cm; - return lm ?? cm; +function bestMatch(matchLabel: SidebarLabelMatcher, label: string, comment?: string | null, aliases?: readonly string[]) { + let best = matchLabel(label); + for (const candidate of [comment, ...(aliases ?? [])]) { + if (!candidate) continue; + const match = matchLabel(candidate); + if (match && (!best || match.score > best.score)) best = match; + } + return best; } function normalizedLabel(node: TreeNode): string { @@ -67,7 +69,7 @@ function filterSidebarTreeWithMatcher(nodes: TreeNode[], matchLabel: SidebarLabe const label = normalizedLabel(node); const canSelfMatch = !searchableNodeTypes || searchableNodeTypes.has(node.type); - const selfMatch = canSelfMatch ? (matchLabel ? bestMatch(matchLabel, label, node.comment) : { score: 0 }) : null; + const selfMatch = canSelfMatch ? (matchLabel ? bestMatch(matchLabel, label, node.comment, node.searchAliases) : { score: 0 }) : null; // Type-only filtering keeps matching rows and their ancestor path, but not // unrelated descendants that would make the selected type appear ignored. const preservesSubtree = !!matchLabel && !!selfMatch && preserveMatchedSubtreeTypes.has(node.type); diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 45377efb12..62c154374c 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -55,6 +55,7 @@ import { remapSidebarLayoutConnectionIds, reorderEntry as reorderEntryOp, buildConnectionGroupPathMap, + connectionSidebarSearchAliases, type DropPosition, } from "@/lib/sidebar/sidebarLayout"; import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionObject, SqlCompletionTable } from "@/lib/sql/sqlCompletion"; @@ -2882,6 +2883,7 @@ export const useConnectionStore = defineStore("connection", () => { if (existing) { existing.label = config.name; existing.type = "connection"; + existing.searchAliases = connectionSidebarSearchAliases(config); existing.connectionId = id; existing.comment = config.note || null; existing.children = existing.children || []; @@ -2890,6 +2892,7 @@ export const useConnectionStore = defineStore("connection", () => { id, label: config.name, type: "connection", + searchAliases: connectionSidebarSearchAliases(config), connectionId: id, isExpanded: false, children: [], @@ -6917,6 +6920,7 @@ export const useConnectionStore = defineStore("connection", () => { ...existing, label: node.label, comment: node.comment, + searchAliases: node.searchAliases, pinned: node.pinned, children: withSavedSqlRoot(node.connectionId!, existing.children || [], existing), }); diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 72f75034df..9f2511fff3 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -968,6 +968,8 @@ export interface TreeNode { id: string; label: string; type: TreeNodeType; + /** Additional values matched by sidebar search without rendering them. */ + searchAliases?: string[]; children?: TreeNode[]; isLoading?: boolean; isExpanded?: boolean; diff --git a/packages/app-tests/sidebarLayout.test.ts b/packages/app-tests/sidebarLayout.test.ts index 02644f1c3e..eae6c71396 100644 --- a/packages/app-tests/sidebarLayout.test.ts +++ b/packages/app-tests/sidebarLayout.test.ts @@ -164,6 +164,16 @@ test("buildTreeNodesFromLayout creates group nodes with connection children", () assert.equal(nodes[1].id, "c"); }); +test("buildTreeNodesFromLayout adds connection host and username as search aliases", () => { + const connection = conn("a", "Production reporting"); + connection.host = "192.168.0.27"; + connection.username = "report_user"; + + const nodes = buildTreeNodesFromLayout({ groups: [], order: [{ type: "connection", id: "a" }] }, [connection], new Set()); + + assert.deepEqual(nodes[0]?.searchAliases, ["192.168.0.27", "report_user"]); +}); + test("buildTreeNodesFromLayout respects collapsed groups", () => { const layout: SidebarLayout = { groups: [{ id: "g1", name: "G", collapsed: true }], diff --git a/packages/app-tests/sidebarSearchTree.test.ts b/packages/app-tests/sidebarSearchTree.test.ts index 07ecfb9571..35ec0137b2 100644 --- a/packages/app-tests/sidebarSearchTree.test.ts +++ b/packages/app-tests/sidebarSearchTree.test.ts @@ -212,6 +212,22 @@ test("preserves loaded children when the connection itself matches search", () = assert.equal(filtered[0]?.children?.[0]?.children?.[0]?.label, "products"); }); +test("matches connections by host and username search aliases", () => { + const connection: TreeNode = { + id: "conn:1", + label: "Production reporting", + type: "connection", + connectionId: "conn:1", + searchAliases: ["192.168.0.27", "report_user"], + isExpanded: false, + children: [], + }; + + assert.equal(filterSidebarTree([connection], "192.168.0", new Set())[0]?.id, connection.id); + assert.equal(filterSidebarTree([connection], "report_user", new Set())[0]?.id, connection.id); + assert.deepEqual(filterSidebarTree([connection], "unrelated", new Set()), []); +}); + test("omits synthetic connection management entries when the connection itself matches search", () => { const nodes: TreeNode[] = [ {