mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-11 15:32:24 +08:00
25
frontend/src/components/SidebarDatabaseMenu.order.test.ts
Normal file
25
frontend/src/components/SidebarDatabaseMenu.order.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const source = readFileSync(new URL('./sidebar/sidebarLegacyNodeMenu.tsx', import.meta.url), 'utf8');
|
||||
|
||||
describe('Sidebar legacy database menu order', () => {
|
||||
it('keeps new query and run-sql ahead of close database', () => {
|
||||
const databaseMenuStart = source.indexOf("} else if (node.type === 'database') {");
|
||||
const databaseMenuEnd = source.indexOf("} else if (node.type === 'view') {", databaseMenuStart);
|
||||
|
||||
expect(databaseMenuStart).toBeGreaterThanOrEqual(0);
|
||||
expect(databaseMenuEnd).toBeGreaterThan(databaseMenuStart);
|
||||
|
||||
const databaseMenuSource = source.slice(databaseMenuStart, databaseMenuEnd);
|
||||
const newQueryIndex = databaseMenuSource.indexOf("key: 'new-query'");
|
||||
const runSqlIndex = databaseMenuSource.indexOf("key: 'run-sql'");
|
||||
const disconnectIndex = databaseMenuSource.indexOf("key: 'disconnect-db'");
|
||||
|
||||
expect(newQueryIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(runSqlIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(disconnectIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(newQueryIndex).toBeLessThan(disconnectIndex);
|
||||
expect(runSqlIndex).toBeLessThan(disconnectIndex);
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,10 @@ const normalizedVisibleTableSectionsSource = visibleTableSectionsSource.replace(
|
||||
|
||||
const renderOverviewSectionTitleSource = source.slice(
|
||||
source.indexOf('const renderOverviewSectionTitle = (section: OverviewTableSection) => {'),
|
||||
source.indexOf('const renderTableOverviewMetaBadges = useCallback((table: TableStatRow, compact = false) => {'),
|
||||
);
|
||||
const metaBadgesSource = source.slice(
|
||||
source.indexOf('const renderTableOverviewMetaBadges = useCallback((table: TableStatRow, compact = false) => {'),
|
||||
source.indexOf('const renderCardTableContent = (table: TableStatRow) => ('),
|
||||
);
|
||||
const normalizedRenderOverviewSectionTitleSource = renderOverviewSectionTitleSource.replace(/\s+/g, ' ').trim();
|
||||
@@ -59,6 +63,8 @@ const aiPromptSource = source.slice(
|
||||
);
|
||||
|
||||
const requiredTableOperationKeys = [
|
||||
'table_overview.metric.created_at',
|
||||
'table_overview.metric.updated_at',
|
||||
'table_overview.tab.design_table_title',
|
||||
'table_overview.tab.table_structure_title',
|
||||
'table_overview.message.load_tables_failed',
|
||||
@@ -121,18 +127,26 @@ describe('TableOverview i18n', () => {
|
||||
expect(cardSource).not.toContain('title="行数"');
|
||||
expect(cardSource).not.toContain('title="数据大小"');
|
||||
expect(cardSource).not.toContain('title="引擎"');
|
||||
expect(cardSource).not.toContain('最近修改');
|
||||
expect(cardSource).not.toContain('创建时间');
|
||||
expect(cardSource).toContain("title={t('table_overview.sort.rows')}");
|
||||
expect(cardSource).toContain("title={t('table_overview.metric.data_size')}");
|
||||
expect(cardSource).toContain("title={t('table_overview.metric.engine')}");
|
||||
expect(cardSource).toContain('{renderTableOverviewMetaBadges(table)}');
|
||||
expect(metaBadgesSource).toContain("t('table_overview.metric.updated_at')");
|
||||
expect(metaBadgesSource).toContain("t('table_overview.metric.created_at')");
|
||||
|
||||
expect(listSource).not.toContain('`${table.engine} 表`');
|
||||
expect(listSource).not.toContain("'双击打开数据,右键查看更多操作'");
|
||||
expect(listSource).not.toContain('最近修改');
|
||||
expect(listSource).not.toContain('创建时间');
|
||||
expect(listSource).not.toContain("<div style={{ color: textMuted }}>行数</div>");
|
||||
expect(listSource).not.toContain("<div style={{ color: textMuted }}>数据大小</div>");
|
||||
expect(listSource).not.toContain("<div style={{ color: textMuted }}>索引大小</div>");
|
||||
expect(listSource).not.toContain("<div style={{ color: textMuted }}>相对大小</div>");
|
||||
expect(listSource).toContain("t('table_overview.row.engine_table', { engine: table.engine })");
|
||||
expect(listSource).toContain("t('table_overview.row.open_hint')");
|
||||
expect(listSource).toContain('{renderTableOverviewMetaBadges(table, true)}');
|
||||
expect(listSource).toContain("t('table_overview.sort.rows')");
|
||||
expect(listSource).toContain("t('table_overview.metric.data_size')");
|
||||
expect(listSource).toContain("t('table_overview.metric.index_size')");
|
||||
@@ -239,7 +253,6 @@ describe('TableOverview i18n', () => {
|
||||
"t('table_overview.message.rename_table_failed'",
|
||||
"t('table_overview.menu.copy_table_name')",
|
||||
"t('table_overview.menu.table_structure')",
|
||||
"t('table_overview.menu.export_xlsx')",
|
||||
].forEach(text => {
|
||||
expect(tableOperationSource).toContain(text);
|
||||
});
|
||||
|
||||
@@ -142,6 +142,21 @@ describe('TableOverview tdengine compatibility', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
storeState.appearance = { uiVersion: 'legacy', tableDoubleClickAction: 'open-data' };
|
||||
storeState.connections = [
|
||||
{
|
||||
id: 'conn-1',
|
||||
config: {
|
||||
type: 'tdengine',
|
||||
host: '127.0.0.1',
|
||||
port: 6041,
|
||||
user: 'root',
|
||||
password: 'taosdata',
|
||||
database: 'metrics',
|
||||
useSSH: false,
|
||||
ssh: { host: '', port: 22, user: '', password: '', keyPath: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
backendApp.DBGetTables.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
@@ -249,4 +264,56 @@ describe('TableOverview tdengine compatibility', () => {
|
||||
tableName: 'd001',
|
||||
}));
|
||||
});
|
||||
|
||||
it('renders comment and temporal metadata in the legacy table list when available', async () => {
|
||||
storeState.connections = [
|
||||
{
|
||||
id: 'conn-1',
|
||||
config: {
|
||||
type: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'secret',
|
||||
database: 'app_db',
|
||||
useSSH: false,
|
||||
ssh: { host: '', port: 22, user: '', password: '', keyPath: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
TABLE_NAME: 'orders',
|
||||
TABLE_COMMENT: '订单表',
|
||||
TABLE_ROWS: 128,
|
||||
DATA_LENGTH: 2048,
|
||||
INDEX_LENGTH: 1024,
|
||||
ENGINE: 'InnoDB',
|
||||
CREATE_TIME: '2026-05-01 09:00:00',
|
||||
UPDATE_TIME: '2026-06-02 10:30:00',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<TableOverview tab={{
|
||||
id: 'tab-1',
|
||||
title: '表概览 - app_db',
|
||||
type: 'table-overview',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'app_db',
|
||||
} as any} />);
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(backendApp.DBQuery).toHaveBeenCalled();
|
||||
const renderedText = collectText(renderer!.toJSON());
|
||||
expect(renderedText).toContain('orders');
|
||||
expect(renderedText).toContain('订单表');
|
||||
expect(renderedText).toContain('2026-06-02 10:30:00');
|
||||
expect(renderedText).toContain('2026-05-01 09:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1012,6 +1012,58 @@ const TableOverview: React.FC<TableOverviewProps> = ({ tab }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderTableOverviewMetaBadges = useCallback((table: TableStatRow, compact = false) => {
|
||||
const items = [
|
||||
...(table.updateTime ? [{
|
||||
key: 'updated',
|
||||
label: t('table_overview.metric.updated_at'),
|
||||
value: table.updateTime,
|
||||
}] : []),
|
||||
...(table.createTime ? [{
|
||||
key: 'created',
|
||||
label: t('table_overview.metric.created_at'),
|
||||
value: table.createTime,
|
||||
}] : []),
|
||||
];
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
marginTop: compact ? 8 : 0,
|
||||
marginBottom: compact ? 0 : 10,
|
||||
}}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<Tooltip key={item.key} title={`${item.label}: ${item.value}`} mouseEnterDelay={0.4}>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
maxWidth: '100%',
|
||||
padding: compact ? '1px 7px' : '2px 8px',
|
||||
borderRadius: 999,
|
||||
background: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
|
||||
color: textSecondary,
|
||||
fontSize: compact ? 10 : 11,
|
||||
lineHeight: compact ? '16px' : '18px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<span style={{ opacity: 0.72 }}>{item.label}</span>
|
||||
<span style={{ color: textPrimary, fontVariantNumeric: 'tabular-nums' }}>{item.value}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}, [darkMode, t, textPrimary, textSecondary]);
|
||||
|
||||
const renderCardTableContent = (table: TableStatRow) => (
|
||||
<div
|
||||
className={isV2Ui ? 'gn-v2-table-card' : undefined}
|
||||
@@ -1044,6 +1096,7 @@ const TableOverview: React.FC<TableOverviewProps> = ({ tab }) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{renderTableOverviewMetaBadges(table)}
|
||||
<div className={isV2Ui ? 'gn-v2-table-card-meta' : undefined} style={{ display: 'flex', gap: 16, fontSize: 12, color: textMuted }}>
|
||||
<span title={t('table_overview.sort.rows')} style={{ minWidth: 52 }}>📊 {formatRows(table.rows)}</span>
|
||||
<span title={t('table_overview.metric.data_size')} style={{ minWidth: 72 }}>💾 {formatSize(table.dataSize)}</span>
|
||||
@@ -1150,6 +1203,7 @@ const TableOverview: React.FC<TableOverviewProps> = ({ tab }) => {
|
||||
{rowSecondary}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{renderTableOverviewMetaBadges(table, true)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 12, flexWrap: 'wrap', fontSize: 12 }}>
|
||||
<div style={{ minWidth: 96, textAlign: 'right' }}>
|
||||
|
||||
@@ -649,6 +649,19 @@ export const buildSidebarLegacyNodeMenuItems = (
|
||||
onClick: () => openCreateStarRocksExternalCatalog(node)
|
||||
},
|
||||
] : []),
|
||||
{
|
||||
key: 'new-query',
|
||||
label: t('sidebar.menu.new_query'),
|
||||
icon: <ConsoleSqlOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'new-query')
|
||||
},
|
||||
{
|
||||
key: 'run-sql',
|
||||
label: t('sidebar.sql_file_exec.title'),
|
||||
icon: <FileAddOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'run-sql')
|
||||
},
|
||||
{ type: 'divider' },
|
||||
...(capabilities.supportsRenameDatabase ? [{
|
||||
key: 'rename-db',
|
||||
label: t('sidebar.menu.rename_database'),
|
||||
@@ -693,19 +706,7 @@ export const buildSidebarLegacyNodeMenuItems = (
|
||||
label: t('sidebar.menu.close_database'),
|
||||
icon: <DisconnectOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'disconnect-db')
|
||||
},
|
||||
{
|
||||
key: 'new-query',
|
||||
label: t('sidebar.menu.new_query'),
|
||||
icon: <ConsoleSqlOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'new-query')
|
||||
},
|
||||
{
|
||||
key: 'run-sql',
|
||||
label: t('sidebar.sql_file_exec.title'),
|
||||
icon: <FileAddOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'run-sql')
|
||||
}
|
||||
}
|
||||
];
|
||||
} else if (node.type === 'view') {
|
||||
return [
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}} erfolgreich",
|
||||
"table_overview.message.unknown_error": "Unbekannter Fehler",
|
||||
"table_overview.message.unpinned": "Anheften der Tabelle aufgehoben",
|
||||
"table_overview.metric.created_at": "Erstellt",
|
||||
"table_overview.metric.data_size": "Datengröße",
|
||||
"table_overview.metric.engine": "Engine",
|
||||
"table_overview.metric.index_size": "Indexgröße",
|
||||
"table_overview.metric.relative_size": "Relative Größe",
|
||||
"table_overview.metric.updated_at": "Zuletzt geändert",
|
||||
"table_overview.modal.delete_table.content": "Tabelle \"{{table}}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"table_overview.modal.delete_table.title": "Tabelle löschen",
|
||||
"table_overview.modal.rename_table.placeholder": "Neuen Tabellennamen eingeben",
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}} succeeded",
|
||||
"table_overview.message.unknown_error": "Unknown error",
|
||||
"table_overview.message.unpinned": "Table unpinned",
|
||||
"table_overview.metric.created_at": "Created",
|
||||
"table_overview.metric.data_size": "Data size",
|
||||
"table_overview.metric.engine": "Engine",
|
||||
"table_overview.metric.index_size": "Index size",
|
||||
"table_overview.metric.relative_size": "Relative size",
|
||||
"table_overview.metric.updated_at": "Last updated",
|
||||
"table_overview.modal.delete_table.content": "Delete table \"{{table}}\"? This action cannot be undone.",
|
||||
"table_overview.modal.delete_table.title": "Delete Table",
|
||||
"table_overview.modal.rename_table.placeholder": "Enter a new table name",
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}} が成功しました",
|
||||
"table_overview.message.unknown_error": "不明なエラー",
|
||||
"table_overview.message.unpinned": "テーブルのピン留めを解除しました",
|
||||
"table_overview.metric.created_at": "作成日時",
|
||||
"table_overview.metric.data_size": "データサイズ",
|
||||
"table_overview.metric.engine": "エンジン",
|
||||
"table_overview.metric.index_size": "インデックスサイズ",
|
||||
"table_overview.metric.relative_size": "相対サイズ",
|
||||
"table_overview.metric.updated_at": "最終更新",
|
||||
"table_overview.modal.delete_table.content": "テーブル \"{{table}}\" を削除しますか?この操作は元に戻せません。",
|
||||
"table_overview.modal.delete_table.title": "テーブルを削除",
|
||||
"table_overview.modal.rename_table.placeholder": "新しいテーブル名を入力",
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}} выполнено",
|
||||
"table_overview.message.unknown_error": "Неизвестная ошибка",
|
||||
"table_overview.message.unpinned": "Закрепление таблицы снято",
|
||||
"table_overview.metric.created_at": "Создано",
|
||||
"table_overview.metric.data_size": "Размер данных",
|
||||
"table_overview.metric.engine": "Движок",
|
||||
"table_overview.metric.index_size": "Размер индекса",
|
||||
"table_overview.metric.relative_size": "Относительный размер",
|
||||
"table_overview.metric.updated_at": "Последнее изменение",
|
||||
"table_overview.modal.delete_table.content": "Удалить таблицу \"{{table}}\"? Это действие нельзя отменить.",
|
||||
"table_overview.modal.delete_table.title": "Удалить таблицу",
|
||||
"table_overview.modal.rename_table.placeholder": "Введите новое имя таблицы",
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}}成功",
|
||||
"table_overview.message.unknown_error": "未知错误",
|
||||
"table_overview.message.unpinned": "已取消置顶",
|
||||
"table_overview.metric.created_at": "创建时间",
|
||||
"table_overview.metric.data_size": "数据大小",
|
||||
"table_overview.metric.engine": "引擎",
|
||||
"table_overview.metric.index_size": "索引大小",
|
||||
"table_overview.metric.relative_size": "相对大小",
|
||||
"table_overview.metric.updated_at": "最近修改",
|
||||
"table_overview.modal.delete_table.content": "删除表 \"{{table}}\"?此操作无法撤销。",
|
||||
"table_overview.modal.delete_table.title": "删除表",
|
||||
"table_overview.modal.rename_table.placeholder": "输入新表名",
|
||||
|
||||
@@ -7629,10 +7629,12 @@
|
||||
"table_overview.message.table_data_action_success": "{{action}}成功",
|
||||
"table_overview.message.unknown_error": "未知錯誤",
|
||||
"table_overview.message.unpinned": "已取消置頂",
|
||||
"table_overview.metric.created_at": "建立時間",
|
||||
"table_overview.metric.data_size": "資料大小",
|
||||
"table_overview.metric.engine": "引擎",
|
||||
"table_overview.metric.index_size": "索引大小",
|
||||
"table_overview.metric.relative_size": "相對大小",
|
||||
"table_overview.metric.updated_at": "最近修改",
|
||||
"table_overview.modal.delete_table.content": "刪除資料表 \"{{table}}\"?此操作無法復原。",
|
||||
"table_overview.modal.delete_table.title": "刪除資料表",
|
||||
"table_overview.modal.rename_table.placeholder": "輸入新的資料表名稱",
|
||||
|
||||
Reference in New Issue
Block a user