mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-03 09:21:25 +08:00
🐛 fix(sql-log): 统一 V2 SQL 日志入口
- V2 左侧入口改为打开当前工作区内嵌 SQL 日志,legacy 继续使用底部全局面板 - 表数据页新增 sqlLog 视图并复用嵌入式 LogPanel,避免无 SQL 编辑器时无法查看日志 - 移除 V2 侧栏底部重复 SQL 日志按钮,保留慢查询入口并补充回归测试
This commit is contained in:
@@ -117,22 +117,37 @@ describe('tool center menu entries', () => {
|
||||
expect(appSource).toContain('const handleOpenToolsModal = useCallback(');
|
||||
expect(appSource).toContain('const handleOpenSettingsModal = useCallback(');
|
||||
expect(appSource).toContain('const handleToggleLogPanel = useCallback(');
|
||||
expect(appSource).toContain('new CustomEvent');
|
||||
expect(appSource).toContain("'gonavi:show-sql-execution-log'");
|
||||
expect(appSource).toContain("detail: { mode: 'open' }");
|
||||
expect(appSource).toContain('toggleAppLogPanel();');
|
||||
expect(appSource).toContain('const handleFocusSidebarSearch = useCallback(');
|
||||
expect(appSource).toContain('const antdTheme = useMemo(() => ({');
|
||||
expect(appSource).toContain('theme={antdTheme}');
|
||||
expect(appSource).toContain('const sqlLogCount = useStore(state => state.sqlLogs.length);');
|
||||
expect(appSource).toContain('onOpenTools={handleOpenToolsModal}');
|
||||
expect(appSource).toContain('onOpenSettings={handleOpenSettingsModal}');
|
||||
expect(appSource).toContain('onToggleLogPanel={handleToggleLogPanel}');
|
||||
expect(appSource).toContain('onFocusCommandSearch={handleFocusSidebarSearch}');
|
||||
expect(appSource).toContain('sqlLogCount={sqlLogCount}');
|
||||
expect(appSource).not.toContain('onOpenTools={() => setIsToolsModalOpen(true)}');
|
||||
expect(appSource).not.toContain('onOpenSettings={() => setIsSettingsModalOpen(true)}');
|
||||
expect(appSource).not.toContain('onToggleLogPanel={() => setIsLogPanelOpen((prev) => !prev)}');
|
||||
expect(appSource).not.toContain('sqlLogCount={sqlLogCount}');
|
||||
expect(appSource).not.toContain('theme={{');
|
||||
expect(appSource).not.toContain('const sqlLogs = useStore(state => state.sqlLogs);');
|
||||
});
|
||||
|
||||
it('renders the shared SQL log panel only for legacy layouts', () => {
|
||||
const logPanelIndex = appSource.indexOf('<LogPanel', appSource.indexOf('<Content'));
|
||||
const logPanelGuardIndex = appSource.lastIndexOf('{isLogPanelOpen && (', logPanelIndex);
|
||||
const legacyOnlyGuardIndex = appSource.lastIndexOf('{!isV2Ui && isLogPanelOpen && (', logPanelIndex);
|
||||
|
||||
expect(logPanelIndex).toBeGreaterThan(-1);
|
||||
expect(logPanelGuardIndex).toBe(-1);
|
||||
expect(legacyOnlyGuardIndex).toBeGreaterThan(-1);
|
||||
expect(appSource).toContain('onClose={handleCloseLogPanel}');
|
||||
expect(appSource).toContain('onResizeStart={handleLogResizeStart}');
|
||||
});
|
||||
|
||||
it('lets the v2 Sidebar own the entire left layout instead of stacking legacy controls above it', () => {
|
||||
const siderIndex = appSource.indexOf("className={isV2Ui ? 'gn-v2-app-sider' : undefined}");
|
||||
const legacyGuardIndex = appSource.indexOf('{!isV2Ui && (', siderIndex);
|
||||
|
||||
@@ -391,7 +391,6 @@ function App() {
|
||||
const aiPanelVisible = useStore(state => state.aiPanelVisible);
|
||||
const toggleAIPanel = useStore(state => state.toggleAIPanel);
|
||||
const setAIPanelVisible = useStore(state => state.setAIPanelVisible);
|
||||
const sqlLogCount = useStore(state => state.sqlLogs.length);
|
||||
const globalProxyInvalidHintShownRef = React.useRef(false);
|
||||
const windowDiagSequenceRef = React.useRef(0);
|
||||
const windowDiagLastSignatureRef = React.useRef('');
|
||||
@@ -2139,23 +2138,23 @@ function App() {
|
||||
|
||||
|
||||
const {
|
||||
handleCloseLogPanel: handleCloseLegacyLogPanel,
|
||||
handleCloseLogPanel: handleCloseAppLogPanel,
|
||||
handleLogResizeStart,
|
||||
handleToggleLogPanel: toggleLegacyLogPanel,
|
||||
handleToggleLogPanel: toggleAppLogPanel,
|
||||
isLogPanelOpen,
|
||||
logGhostRef,
|
||||
logPanelHeight,
|
||||
} = useAppLogPanelResize();
|
||||
const handleToggleLogPanel = useCallback(() => {
|
||||
if (isV2Ui) {
|
||||
window.dispatchEvent(new CustomEvent('gonavi:show-sql-execution-log'));
|
||||
window.dispatchEvent(new CustomEvent('gonavi:show-sql-execution-log', { detail: { mode: 'open' } }));
|
||||
return;
|
||||
}
|
||||
toggleLegacyLogPanel();
|
||||
}, [isV2Ui, toggleLegacyLogPanel]);
|
||||
toggleAppLogPanel();
|
||||
}, [isV2Ui, toggleAppLogPanel]);
|
||||
const handleCloseLogPanel = useCallback(() => {
|
||||
handleCloseLegacyLogPanel();
|
||||
}, [handleCloseLegacyLogPanel]);
|
||||
handleCloseAppLogPanel();
|
||||
}, [handleCloseAppLogPanel]);
|
||||
|
||||
const handleCreateConnection = useCallback(() => {
|
||||
setSecurityUpdateRepairSource(null);
|
||||
@@ -2944,7 +2943,6 @@ function App() {
|
||||
onOpenSettings={handleOpenSettingsModal}
|
||||
onToggleAI={toggleAIPanel}
|
||||
onToggleLogPanel={handleToggleLogPanel}
|
||||
sqlLogCount={sqlLogCount}
|
||||
uiVersion={appearance.uiVersion}
|
||||
onFocusCommandSearch={handleFocusSidebarSearch}
|
||||
/>
|
||||
|
||||
@@ -34,6 +34,12 @@ const readDataGridSource = () => [
|
||||
'./DataGridCore.tsx',
|
||||
'./DataGridShell.tsx',
|
||||
].map((file) => readFileSync(new URL(file, import.meta.url), 'utf8')).join('\n');
|
||||
const readDataViewerSource = (): string =>
|
||||
readFileSync(new URL('./DataViewer.tsx', import.meta.url), 'utf8');
|
||||
const readDataGridSecondaryActionsSource = (): string =>
|
||||
readFileSync(new URL('./DataGridSecondaryActions.tsx', import.meta.url), 'utf8');
|
||||
const readDataGridShellSource = (): string =>
|
||||
readFileSync(new URL('./DataGridShell.tsx', import.meta.url), 'utf8');
|
||||
|
||||
const mockStoreState = vi.hoisted(() => ({
|
||||
languagePreference: 'system' as LanguagePreference,
|
||||
@@ -172,6 +178,7 @@ describe('DataGrid layout', () => {
|
||||
expect(markup).toContain('data-grid-column-quick-find-action="true"');
|
||||
expect(markup).toContain('字段显示');
|
||||
expect(markup).toContain('跳列');
|
||||
expect(markup).toContain('日志');
|
||||
expect(markup).toContain(zhObjectDesignLabel);
|
||||
expect(markup).toContain('data-grid-page-find="true"');
|
||||
expect(markup).toContain('data-grid-page-find-prev="true"');
|
||||
@@ -190,6 +197,27 @@ describe('DataGrid layout', () => {
|
||||
expect(markup).toContain('当前页查找...');
|
||||
});
|
||||
|
||||
it('opens the embedded SQL log view from the shared V2 SQL log event in table data tabs', () => {
|
||||
const source = readDataGridSource();
|
||||
const dataViewerSource = readDataViewerSource();
|
||||
const secondaryActionsSource = readDataGridSecondaryActionsSource();
|
||||
const shellSource = readDataGridShellSource();
|
||||
|
||||
expect(dataViewerSource).toContain('isActive={isActive}');
|
||||
expect(dataViewerSource).toContain('enableSqlLogEvent');
|
||||
expect(source).toContain("isActive = true");
|
||||
expect(source).toContain("enableSqlLogEvent = false");
|
||||
expect(source).toContain("'gonavi:show-sql-execution-log'");
|
||||
expect(source).toContain("if (!enableSqlLogEvent || !isV2Ui || !isActive) return;");
|
||||
expect(source).toContain("handleViewModeChange('sqlLog');");
|
||||
expect(source).toContain("'sqlLog'");
|
||||
expect(shellSource).toContain('import LogPanel from');
|
||||
expect(shellSource).toContain("viewMode === 'sqlLog'");
|
||||
expect(shellSource).toContain('<LogPanel variant="embedded" />');
|
||||
expect(secondaryActionsSource).toContain("key: 'sqlLog'");
|
||||
expect(secondaryActionsSource).toContain("translate('log_panel.short_title')");
|
||||
});
|
||||
|
||||
it('localizes DataGrid error boundary, column drag affordances, and legacy row context menu labels through i18n keys', () => {
|
||||
const source = readDataGridSource();
|
||||
const expectedKeys = [
|
||||
|
||||
@@ -286,7 +286,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
resultExportAllSql,
|
||||
onReload, onSort, onPageChange, pagination, onRequestTotalCount, onCancelTotalCount, sortInfoExternal, showFilter, onToggleFilter, exportSqlWithFilter, onApplyFilter, appliedFilterConditions, quickWhereCondition,
|
||||
onApplyQuickWhereCondition,
|
||||
scrollSnapshot, onScrollSnapshotChange, toolbarExtraActions, showRowNumberColumn = false
|
||||
scrollSnapshot, onScrollSnapshotChange, toolbarExtraActions, showRowNumberColumn = false, isActive = true, enableSqlLogEvent = false
|
||||
}) => {
|
||||
const connections = useStore(state => state.connections);
|
||||
const addTab = useStore(state => state.addTab);
|
||||
@@ -1351,13 +1351,23 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
if (String(detail.tableName || '') !== String(tableName || '')) return;
|
||||
const nextMode = String(detail.viewMode || '').trim();
|
||||
if (!nextMode) return;
|
||||
if (!['table', 'json', 'text', 'fields', 'ddl', 'er'].includes(nextMode)) return;
|
||||
if (!['table', 'json', 'text', 'fields', 'ddl', 'er', 'sqlLog'].includes(nextMode)) return;
|
||||
handleViewModeChange(nextMode as GridViewMode);
|
||||
};
|
||||
|
||||
window.addEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
|
||||
return () => window.removeEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
|
||||
}, [canOpenObjectDesigner, connectionId, dbName, handleViewModeChange, tableName]);
|
||||
}, [connectionId, dbName, handleViewModeChange, tableName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableSqlLogEvent || !isV2Ui || !isActive) return;
|
||||
const handleOpenSqlExecutionLog = () => {
|
||||
handleViewModeChange('sqlLog');
|
||||
};
|
||||
|
||||
window.addEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
|
||||
return () => window.removeEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
|
||||
}, [enableSqlLogEvent, handleViewModeChange, isActive, isV2Ui]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTableSurfaceActive || !isV2Ui || !cellContextMenu.visible) return;
|
||||
|
||||
@@ -1334,6 +1334,8 @@ interface DataGridProps {
|
||||
scrollSnapshot?: { top: number; left: number };
|
||||
onScrollSnapshotChange?: (snapshot: { top: number; left: number }) => void;
|
||||
toolbarExtraActions?: React.ReactNode;
|
||||
isActive?: boolean;
|
||||
enableSqlLogEvent?: boolean;
|
||||
}
|
||||
|
||||
type GridFilterCondition = FilterCondition & {
|
||||
@@ -1344,7 +1346,7 @@ type GridFilterCondition = FilterCondition & {
|
||||
value2?: string;
|
||||
};
|
||||
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
|
||||
type DdlViewLayoutMode = 'bottom' | 'side';
|
||||
type DataGridExportScope = 'selected' | 'page' | 'all' | 'filteredAll';
|
||||
type VirtualEditingCellState = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Segmented } from 'antd';
|
||||
import { t as defaultTranslate, type I18nParams } from '../i18n';
|
||||
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
|
||||
|
||||
export type DataGridResultViewTranslate = (key: string, params?: I18nParams) => string;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Button, Popover } from 'antd';
|
||||
import {
|
||||
AimOutlined,
|
||||
BugOutlined,
|
||||
ConsoleSqlOutlined,
|
||||
EditOutlined,
|
||||
FileTextOutlined,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { t as defaultTranslate, type I18nParams } from '../i18n';
|
||||
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
|
||||
|
||||
export type DataGridSecondaryActionsTranslate = (key: string, params?: I18nParams) => string;
|
||||
|
||||
@@ -69,6 +70,7 @@ const DataGridSecondaryActions: React.FC<DataGridSecondaryActionsProps> = ({
|
||||
{ key: 'fields', label: fieldsActionLabel, icon: fieldsActionIcon },
|
||||
{ key: 'ddl', label: translate('data_grid.secondary.view_ddl'), icon: <ConsoleSqlOutlined />, disabled: !canViewDdl },
|
||||
{ key: 'er', label: translate('data_grid.secondary.er_diagram'), icon: <LinkOutlined /> },
|
||||
{ key: 'sqlLog', label: translate('log_panel.short_title'), icon: <BugOutlined /> },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,6 +9,7 @@ import DataGridModals from './DataGridModals';
|
||||
import DataGridPreviewPanel from './DataGridPreviewPanel';
|
||||
import DataGridSecondaryActions from './DataGridSecondaryActions';
|
||||
import DataGridToolbarFrame from './DataGridToolbarFrame';
|
||||
import LogPanel from './LogPanel';
|
||||
import { DataGridJsonView, DataGridTextView } from './DataGridRecordViews';
|
||||
import { DataGridV2DdlSideWorkspace, DataGridV2DdlView } from './DataGridV2DdlWorkspace';
|
||||
import { DataGridV2ErView, DataGridV2FieldsView } from './DataGridV2MetadataViews';
|
||||
@@ -789,6 +790,8 @@ const renderDataTableView = () => (
|
||||
onOpenTable={onOpenErTable}
|
||||
translate={translateDataGrid}
|
||||
/>
|
||||
) : isV2Ui && viewMode === 'sqlLog' ? (
|
||||
<LogPanel variant="embedded" />
|
||||
) : viewMode === 'json' ? (
|
||||
<DataGridJsonView
|
||||
darkMode={darkMode}
|
||||
|
||||
@@ -1229,6 +1229,8 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({
|
||||
exportSqlWithFilter={exportSqlWithFilter || undefined}
|
||||
scrollSnapshot={scrollSnapshotRef.current}
|
||||
onScrollSnapshotChange={handleTableScrollSnapshotChange}
|
||||
isActive={isActive}
|
||||
enableSqlLogEvent
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -989,7 +989,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('opens the embedded sql execution log tab from the shared log toggle event in v2', async () => {
|
||||
it('opens the embedded sql execution log tab from the shared log event in v2', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
storeState.sqlLogs = [{
|
||||
id: 'log-1',
|
||||
@@ -1043,6 +1043,54 @@ describe('QueryEditor external SQL save', () => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('keeps the embedded sql execution log tab open for explicit open events in v2', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
storeState.sqlLogs = [{
|
||||
id: 'log-1',
|
||||
timestamp: Date.now(),
|
||||
sql: 'select 1',
|
||||
status: 'success',
|
||||
duration: 12,
|
||||
}];
|
||||
|
||||
const windowListeners: Record<string, ((event?: any) => void)[]> = {};
|
||||
vi.stubGlobal('window', {
|
||||
addEventListener: vi.fn((type: string, listener: (event?: any) => void) => {
|
||||
windowListeners[type] ||= [];
|
||||
windowListeners[type].push(listener);
|
||||
}),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
}),
|
||||
cancelAnimationFrame: vi.fn(),
|
||||
innerHeight: 900,
|
||||
});
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<QueryEditor tab={createTab()} />);
|
||||
});
|
||||
|
||||
const openEvent = new CustomEvent('gonavi:show-sql-execution-log', { detail: { mode: 'open' } });
|
||||
await act(async () => {
|
||||
windowListeners['gonavi:show-sql-execution-log']?.forEach((listener) => listener(openEvent));
|
||||
});
|
||||
expect(textContent(renderer.toJSON())).toContain('SQL 执行日志');
|
||||
|
||||
await act(async () => {
|
||||
windowListeners['gonavi:show-sql-execution-log']?.forEach((listener) => listener(openEvent));
|
||||
});
|
||||
expect(textContent(renderer.toJSON())).toContain('SQL 执行日志');
|
||||
expect(storeState.updateQueryTabDraft).toHaveBeenLastCalledWith('tab-1', {
|
||||
resultPanelVisible: true,
|
||||
});
|
||||
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('shows execution failures inside the embedded sql log tab in v2', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
backendApp.DBQueryMulti.mockResolvedValueOnce({
|
||||
|
||||
@@ -15,6 +15,7 @@ import QueryEditor, {
|
||||
resolveQueryEditorNavigationDecorations,
|
||||
resolveQueryEditorNavigationTarget,
|
||||
} from './QueryEditor';
|
||||
import QueryEditorResultsPanel from './QueryEditorResultsPanel';
|
||||
|
||||
const storeState = vi.hoisted(() => ({
|
||||
connections: [
|
||||
@@ -2645,20 +2646,59 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(source).toContain("if (isEditableElement(event.target) && !inEditorPane) {");
|
||||
});
|
||||
|
||||
it('embeds the sql execution log as a result tab instead of a standalone workspace panel in v2', () => {
|
||||
it('keeps the embedded sql execution log limited to v2 query editor result tabs', () => {
|
||||
const panelSource = readFileSync(new URL('./QueryEditorResultsPanel.tsx', import.meta.url), 'utf8');
|
||||
const editorSource = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
|
||||
|
||||
expect(panelSource).toContain('QUERY_EDITOR_SQL_LOG_TAB_KEY');
|
||||
expect(panelSource).toContain('const shouldShowSqlLogTab = isV2Ui && (sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY);');
|
||||
expect(panelSource).toContain('<LogPanel');
|
||||
expect(panelSource).toContain('variant="embedded"');
|
||||
expect(panelSource).toContain('executionError={executionError}');
|
||||
expect(panelSource).toContain("t('log_panel.short_title')");
|
||||
expect(panelSource).toContain('[logTabItem, ...resultTabItems]');
|
||||
expect(editorSource).toContain("window.addEventListener('gonavi:show-sql-execution-log'");
|
||||
expect(editorSource).toContain("event instanceof CustomEvent && event.detail?.mode === 'open'");
|
||||
expect(editorSource).toContain('setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY)');
|
||||
});
|
||||
|
||||
it('does not render the embedded sql execution log tab in legacy UI', () => {
|
||||
const renderResultsPanel = (isV2Ui: boolean) => create(
|
||||
<QueryEditorResultsPanel
|
||||
resultSets={[]}
|
||||
activeResultKey=""
|
||||
loading={false}
|
||||
executionError=""
|
||||
sqlLogCount={1}
|
||||
darkMode={false}
|
||||
isV2Ui={isV2Ui}
|
||||
currentDb="main"
|
||||
currentConnectionId="conn-1"
|
||||
toggleShortcutLabel=""
|
||||
onActiveResultKeyChange={vi.fn()}
|
||||
onHide={vi.fn()}
|
||||
onCloseResult={vi.fn()}
|
||||
onCloseOtherResultTabs={vi.fn()}
|
||||
onCloseResultTabsToLeft={vi.fn()}
|
||||
onCloseResultTabsToRight={vi.fn()}
|
||||
onCloseAllResultTabs={vi.fn()}
|
||||
onReloadResult={vi.fn()}
|
||||
onResultPageChange={vi.fn()}
|
||||
onDiagnoseExecutionError={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const legacyRenderer = renderResultsPanel(false);
|
||||
expect(legacyRenderer.root.findAll((node) => node.props?.['data-log-panel'] === 'true')).toHaveLength(0);
|
||||
expect(legacyRenderer.root.findAll((node) => node.props?.['data-tab-key'] === '__gonavi_sql_execution_log__')).toHaveLength(0);
|
||||
legacyRenderer.unmount();
|
||||
|
||||
const v2Renderer = renderResultsPanel(true);
|
||||
expect(v2Renderer.root.findAll((node) => node.props?.['data-log-panel'] === 'true')).toHaveLength(1);
|
||||
expect(v2Renderer.root.findAll((node) => node.props?.['data-tab-key'] === '__gonavi_sql_execution_log__')).toHaveLength(1);
|
||||
v2Renderer.unmount();
|
||||
});
|
||||
|
||||
it('keeps the v2 query editor toolbar grouped and compact', () => {
|
||||
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
|
||||
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
|
||||
|
||||
@@ -385,11 +385,11 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
return nextVisible;
|
||||
});
|
||||
}, [tab.id, updateQueryTabDraft]);
|
||||
const handleShowSqlExecutionLog = useCallback(() => {
|
||||
const handleShowSqlExecutionLog = useCallback((mode: 'open' | 'toggle' = 'toggle') => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
if (isResultPanelVisible && activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY) {
|
||||
if (mode !== 'open' && isResultPanelVisible && activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY) {
|
||||
updateResultPanelVisibility(false);
|
||||
return;
|
||||
}
|
||||
@@ -4467,8 +4467,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}, [isActive, handleQuickSave]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOpenSqlExecutionLog = () => {
|
||||
handleShowSqlExecutionLog();
|
||||
const handleOpenSqlExecutionLog = (event: Event) => {
|
||||
const mode = event instanceof CustomEvent && event.detail?.mode === 'open' ? 'open' : 'toggle';
|
||||
handleShowSqlExecutionLog(mode);
|
||||
};
|
||||
|
||||
window.addEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
|
||||
|
||||
@@ -81,7 +81,7 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
|
||||
}) => {
|
||||
const i18n = useOptionalI18n();
|
||||
const t = i18n?.t ?? defaultTranslate;
|
||||
const shouldShowSqlLogTab = sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY;
|
||||
const shouldShowSqlLogTab = isV2Ui && (sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY);
|
||||
const logTabCountLabel = sqlLogCount > 999 ? '999+' : String(sqlLogCount);
|
||||
const resolvedResultSetKey = activeResultKey && resultSets.some((rs) => rs.key === activeResultKey)
|
||||
? activeResultKey
|
||||
|
||||
@@ -697,8 +697,8 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(source).not.toContain("justifyContent: 'space-between', borderTop: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, borderBottom: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.015)' }}>");
|
||||
});
|
||||
|
||||
it('renders the v2 sidebar rail, command search hint, filter tabs and log footer', () => {
|
||||
const markup = renderSidebarMarkup({ uiVersion: 'v2', sqlLogCount: 2341, onCreateConnection: mocks.noop });
|
||||
it('renders the v2 sidebar rail, command search hint, filter tabs and slow-query footer', () => {
|
||||
const markup = renderSidebarMarkup({ uiVersion: 'v2', onCreateConnection: mocks.noop });
|
||||
const source = readSidebarSource();
|
||||
|
||||
expect(markup).toContain('gn-v2-sidebar-redesign');
|
||||
@@ -734,8 +734,10 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(source).toContain('onClick={() => setV2ExplorerFilter(item.key)}');
|
||||
expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}');
|
||||
expect(markup).toContain('gn-v2-sidebar-log-footer');
|
||||
expect(markup).toContain('SQL 执行日志');
|
||||
expect(markup).toContain('2,341');
|
||||
expect(markup).toContain('gn-v2-sidebar-slow-query-button');
|
||||
expect(markup).not.toContain('gn-v2-sidebar-log-button');
|
||||
expect(markup).not.toContain('SQL 执行日志');
|
||||
expect(markup).not.toContain('2,341');
|
||||
expect(markup).not.toContain('gn-v2-rail-action-group');
|
||||
expect(source).toContain('className="gn-v2-rail-primary-actions"');
|
||||
expect(markup).toContain('data-sidebar-create-group-action="true"');
|
||||
|
||||
@@ -99,8 +99,7 @@ import { Tree, message, Dropdown, MenuProps, Input, Button, Form, Popover, Toolt
|
||||
AimOutlined,
|
||||
MoreOutlined,
|
||||
ToolOutlined,
|
||||
SettingOutlined,
|
||||
BarsOutlined
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
buildSidebarRootConnectionToken,
|
||||
@@ -369,7 +368,6 @@ const Sidebar: React.FC<{
|
||||
onOpenSettings?: () => void;
|
||||
onToggleAI?: () => void;
|
||||
onToggleLogPanel?: () => void;
|
||||
sqlLogCount?: number;
|
||||
uiVersion?: 'legacy' | 'v2';
|
||||
onFocusCommandSearch?: () => void;
|
||||
}> = React.memo(({
|
||||
@@ -379,7 +377,6 @@ const Sidebar: React.FC<{
|
||||
onOpenSettings,
|
||||
onToggleAI,
|
||||
onToggleLogPanel,
|
||||
sqlLogCount = 0,
|
||||
uiVersion,
|
||||
onFocusCommandSearch,
|
||||
}) => {
|
||||
@@ -2854,11 +2851,6 @@ const Sidebar: React.FC<{
|
||||
|
||||
{isV2Ui && (
|
||||
<div className="gn-v2-sidebar-log-footer">
|
||||
<button type="button" className="gn-v2-sidebar-log-button" onClick={onToggleLogPanel}>
|
||||
<BarsOutlined />
|
||||
<span>{t('app.sidebar.sql_execution_log')}</span>
|
||||
<small>{sqlLogCount.toLocaleString()}</small>
|
||||
</button>
|
||||
<SlowQueryRailButton
|
||||
className="gn-v2-sidebar-slow-query-button"
|
||||
tooltipPlacement="top"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t as catalogTranslate } from '../i18n/catalog';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { formatDdlForDisplay } from '../utils/ddlFormat';
|
||||
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
|
||||
type DdlViewLayoutMode = 'bottom' | 'side';
|
||||
type TranslateParams = Record<string, string | number | boolean | null | undefined>;
|
||||
|
||||
@@ -118,15 +118,19 @@ export const useDataGridDdlView = ({
|
||||
}, [canViewDdl, currentConnConfig, dbName, dbType, isV2Ui, messageApi, tableName, translateMessage]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isV2Ui || (viewMode !== 'fields' && viewMode !== 'ddl' && viewMode !== 'er')) return;
|
||||
if (isV2Ui || (viewMode !== 'fields' && viewMode !== 'ddl' && viewMode !== 'er' && viewMode !== 'sqlLog')) return;
|
||||
setViewMode('table');
|
||||
}, [isV2Ui, viewMode]);
|
||||
|
||||
const handleViewModeChange = React.useCallback((nextMode: GridViewMode) => {
|
||||
if ((nextMode === 'fields' || nextMode === 'ddl' || nextMode === 'er') && !isV2Ui) {
|
||||
if ((nextMode === 'fields' || nextMode === 'ddl' || nextMode === 'er' || nextMode === 'sqlLog') && !isV2Ui) {
|
||||
setViewMode('table');
|
||||
return;
|
||||
}
|
||||
if (nextMode === 'sqlLog') {
|
||||
setViewMode('sqlLog');
|
||||
return;
|
||||
}
|
||||
if (nextMode === 'ddl') {
|
||||
void handleOpenTableDdl({ asView: true });
|
||||
setViewMode('ddl');
|
||||
|
||||
@@ -2950,35 +2950,6 @@ body[data-ui-version="v2"] .gn-v2-sidebar-log-footer {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-sidebar-log-button {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
height: 28px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--gn-fg-2);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 10px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-sidebar-log-button:hover {
|
||||
background: var(--gn-bg-hover);
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-sidebar-log-button small {
|
||||
margin-left: auto;
|
||||
color: var(--gn-fg-4);
|
||||
font-family: var(--gn-font-mono);
|
||||
font-size: 10.5px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-sidebar-slow-query-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
Reference in New Issue
Block a user