feat(query-editor): 收敛 SQL 分析工作台与结果区日志体验

- 新增 SQL 分析工作台,统一承载慢 SQL 和 SQL 诊断视图
- 将 SQL 执行日志收进结果区首个日志标签并在失败时展示错误摘要
- 调整侧边栏入口、标签展示、多语言文案与定向前端测试覆盖
This commit is contained in:
Syngnat
2026-06-20 14:09:58 +08:00
parent 04019135a0
commit c8c8497a2f
26 changed files with 1539 additions and 539 deletions

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest'
import { buildSqlAnalysisWorkbenchTab, resolveSqlAnalysisWorkbenchTabId } from './sqlAnalysisTab'
describe('sqlAnalysisTab', () => {
it('builds a stable workbench tab per connection and database', () => {
expect(resolveSqlAnalysisWorkbenchTabId('conn-1', 'analytics')).toBe('sql-analysis-conn-1-analytics')
expect(resolveSqlAnalysisWorkbenchTabId('conn-1')).toBe('sql-analysis-conn-1-default')
})
it('keeps diagnose requests on the sql-analysis tab with optional seeded sql', () => {
const tab = buildSqlAnalysisWorkbenchTab({
connectionId: 'conn-1',
dbName: 'analytics',
query: 'select * from orders',
view: 'diagnose',
requestKey: 'diagnose-1',
})
expect(tab).toMatchObject({
id: 'sql-analysis-conn-1-analytics',
title: 'SQL 分析 · analytics',
type: 'sql-analysis',
connectionId: 'conn-1',
dbName: 'analytics',
query: 'select * from orders',
sqlAnalysisView: 'diagnose',
sqlAnalysisRequestKey: 'diagnose-1',
})
})
it('does not clear existing sql when opening the slow-query view without a seeded query', () => {
const tab = buildSqlAnalysisWorkbenchTab({
connectionId: 'conn-1',
view: 'slow-query',
requestKey: 'slow-1',
})
expect(tab.query).toBeUndefined()
expect(tab.sqlAnalysisView).toBe('slow-query')
expect(tab.sqlAnalysisRequestKey).toBe('slow-1')
})
})

View File

@@ -0,0 +1,42 @@
import type { TabData } from '../types'
export type SqlAnalysisView = 'diagnose' | 'slow-query'
type BuildSqlAnalysisWorkbenchTabInput = {
connectionId: string
dbName?: string
title?: string
query?: string
view?: SqlAnalysisView
requestKey?: string
}
export const resolveSqlAnalysisWorkbenchTabId = (
connectionId: string,
dbName?: string,
): string => {
const normalizedConnectionId = String(connectionId || '').trim() || 'none'
const normalizedDbName = String(dbName || '').trim() || 'default'
return `sql-analysis-${normalizedConnectionId}-${normalizedDbName}`
}
export const buildSqlAnalysisWorkbenchTab = (
input: BuildSqlAnalysisWorkbenchTabInput,
): TabData => {
const connectionId = String(input.connectionId || '').trim()
const dbName = String(input.dbName || '').trim()
const view = input.view === 'slow-query' ? 'slow-query' : 'diagnose'
const title = String(input.title || (dbName ? `SQL 分析 · ${dbName}` : 'SQL 分析')).trim()
const query = typeof input.query === 'string' ? input.query : ''
return {
id: resolveSqlAnalysisWorkbenchTabId(connectionId, dbName || undefined),
title: title || (dbName ? `SQL 分析 · ${dbName}` : 'SQL 分析'),
type: 'sql-analysis',
connectionId,
...(dbName ? { dbName } : {}),
...(query.trim() ? { query } : {}),
sqlAnalysisView: view,
sqlAnalysisRequestKey: input.requestKey || `${view}-${Date.now()}`,
}
}

View File

@@ -464,6 +464,7 @@ export const getTabDisplayKindLabel = (tab: TabData): string => {
if (tab.type === 'design') return 'DESIGN';
if (tab.type === 'table-overview') return 'DB';
if (tab.type === 'table-export') return 'EXPORT';
if (tab.type === 'sql-analysis') return 'ANALYZE';
if (tab.type.startsWith('redis')) return 'REDIS';
if (tab.type.startsWith('jvm')) return 'JVM';
if (tab.type === 'trigger') return 'TRG';