From e24942083a31faa44df953f91f01b483af3abd77 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 17 Jul 2026 22:40:36 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(sidebar):=20=E9=A1=B6?= =?UTF-8?q?=E9=83=A8=E6=96=B0=E5=BB=BA=E6=9F=A5=E8=AF=A2=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #665 --- .../Sidebar.locate-toolbar.test.tsx | 9 ++-- frontend/src/components/Sidebar.tsx | 16 ++++++- .../sidebarV2Utils.active-query.test.ts | 43 +++++++++++++++++++ frontend/src/components/sidebarV2Utils.ts | 17 ++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/sidebarV2Utils.active-query.test.ts diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index 3d261be7..30ea09cb 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -1314,7 +1314,7 @@ describe('Sidebar locate toolbar', () => { expect(css).not.toContain('.gn-v2-active-connection-trigger:hover'); }); - it('shows a prominent v2 new query action before connection creation without reusing the plus icon', () => { + it('opens the v2 header new-query action in the selected database before connection creation', () => { mocks.state.connections = [{ id: 'conn-local', name: '开发240', @@ -1349,8 +1349,11 @@ describe('Sidebar locate toolbar', () => { expect(markup.indexOf('data-gonavi-new-query-action="true"')).toBeLessThan(markup.indexOf('data-gonavi-create-connection-action="true"')); expect(newQueryActionSource).toContain('icon={}'); expect(newQueryActionSource).not.toContain('icon={}'); - expect(newQueryActionSource).toContain("handleV2ConnectionContextMenuAction(getConnectionNodeForAction(activeConnection), 'new-query')"); - expect(newQueryActionSource).not.toContain("handleV2ConnectionContextMenuAction(activeConnection, 'new-query')"); + expect(newQueryActionSource).toContain('const selectedDatabase = resolveV2SelectedDatabaseName({'); + expect(newQueryActionSource).toContain('activeConnectionId: activeConnection.id,'); + expect(newQueryActionSource).toContain('activeContextConnectionId: activeContext?.connectionId,'); + expect(newQueryActionSource).toContain("handleV2DatabaseContextMenuAction(getDatabaseNodeRef(activeConnection, selectedDatabase), 'new-query');"); + expect(newQueryActionSource).toContain("handleV2ConnectionContextMenuAction(getConnectionNodeForAction(activeConnection), 'new-query');"); expect(newQueryActionSource).toContain('disabled={!activeConnection}'); }); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index c126550b..134d2330 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -189,6 +189,7 @@ import { resolveSidebarDatabaseTreePruneKeys, resolveSidebarNodeConnectionId, resolveV2ActiveConnectionId, + resolveV2SelectedDatabaseName, resolveV2CommandSearchPersistentFilter, shouldClearSidebarNodeChildrenOnCollapse, shouldSkipSidebarLoadOnExpandWhileDragging, @@ -2031,6 +2032,7 @@ const Sidebar: React.FC<{ const getDatabaseNodeRef = (connRef: any, dbName: string) => { const latestConn = connections.find(c => c.id === connRef.id); return { + title: dbName, key: `${connRef.id}-${dbName}`, dataRef: { ...(latestConn || connRef), dbName } }; @@ -3004,9 +3006,19 @@ const Sidebar: React.FC<{ data-gonavi-new-query-action="true" disabled={!activeConnection} onClick={() => { - if (activeConnection) { - handleV2ConnectionContextMenuAction(getConnectionNodeForAction(activeConnection), 'new-query'); + if (!activeConnection) { + return; } + const selectedDatabase = resolveV2SelectedDatabaseName({ + activeConnectionId: activeConnection.id, + activeContextConnectionId: activeContext?.connectionId, + activeContextDbName: activeContext?.dbName, + }); + if (selectedDatabase) { + handleV2DatabaseContextMenuAction(getDatabaseNodeRef(activeConnection, selectedDatabase), 'new-query'); + return; + } + handleV2ConnectionContextMenuAction(getConnectionNodeForAction(activeConnection), 'new-query'); }} > {t('sidebar.menu.new_query')} diff --git a/frontend/src/components/sidebarV2Utils.active-query.test.ts b/frontend/src/components/sidebarV2Utils.active-query.test.ts new file mode 100644 index 00000000..839b6720 --- /dev/null +++ b/frontend/src/components/sidebarV2Utils.active-query.test.ts @@ -0,0 +1,43 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +import { resolveV2SelectedDatabaseName } from './sidebarV2Utils'; + +describe('resolveV2SelectedDatabaseName', () => { + it('keeps a selected database only when it belongs to the active connection', () => { + expect(resolveV2SelectedDatabaseName({ + activeConnectionId: 'conn-local', + activeContextConnectionId: 'conn-local', + activeContextDbName: 'reporting', + })).toBe('reporting'); + + expect(resolveV2SelectedDatabaseName({ + activeConnectionId: 'conn-local', + activeContextConnectionId: 'conn-other', + activeContextDbName: 'analytics', + })).toBe(''); + }); + + it('does not bind a connection-level query to an empty selected database', () => { + expect(resolveV2SelectedDatabaseName({ + activeConnectionId: 'conn-local', + activeContextConnectionId: 'conn-local', + activeContextDbName: ' ', + })).toBe(''); + }); + + it('uses the selected-database resolver before falling back to the connection action', () => { + const sidebarSource = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8'); + const databaseNodeStart = sidebarSource.indexOf('const getDatabaseNodeRef = (connRef: any, dbName: string) => {'); + const databaseNodeEnd = sidebarSource.indexOf('const extractObjectName =', databaseNodeStart); + const headerStart = sidebarSource.indexOf('
'); + const headerEnd = sidebarSource.indexOf('', headerStart); + const databaseNodeSource = sidebarSource.slice(databaseNodeStart, databaseNodeEnd); + const headerSource = sidebarSource.slice(headerStart, headerEnd); + + expect(databaseNodeSource).toContain('title: dbName,'); + expect(headerSource).toContain('const selectedDatabase = resolveV2SelectedDatabaseName({'); + expect(headerSource).toContain("handleV2DatabaseContextMenuAction(getDatabaseNodeRef(activeConnection, selectedDatabase), 'new-query');"); + expect(headerSource).toContain("handleV2ConnectionContextMenuAction(getConnectionNodeForAction(activeConnection), 'new-query');"); + }); +}); diff --git a/frontend/src/components/sidebarV2Utils.ts b/frontend/src/components/sidebarV2Utils.ts index d69b8f20..fae0fc06 100644 --- a/frontend/src/components/sidebarV2Utils.ts +++ b/frontend/src/components/sidebarV2Utils.ts @@ -1056,6 +1056,23 @@ export const resolveV2ActiveConnectionId = ({ || ''; }; +export const resolveV2SelectedDatabaseName = ({ + activeConnectionId, + activeContextConnectionId, + activeContextDbName, +}: { + activeConnectionId?: unknown; + activeContextConnectionId?: unknown; + activeContextDbName?: unknown; +}): string => { + const connectionId = String(activeConnectionId || '').trim(); + const contextConnectionId = String(activeContextConnectionId || '').trim(); + if (!connectionId || connectionId !== contextConnectionId) { + return ''; + } + return String(activeContextDbName || '').trim(); +}; + export const resolveSidebarDatabaseTreePruneKeys = ({ treeData, expandedKeys,