mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-29 09:48:32 +08:00
✨ feat(sidebar): 支持表元数据显示配置
- 新增侧栏表元数据字段配置并兼容旧版表备注开关 - 设置中心新增侧栏表元数据入口并移除树上重复显示开关 - 批量补齐表大小、创建时间、修改时间等元数据加载与渲染 - 统一表元数据格式化逻辑并修复 V2 左树横向滚动裁切 - 补充设置中心、store、侧栏元数据相关回归测试与多语言文案
This commit is contained in:
@@ -1306,6 +1306,34 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(wideWidth).toBeUndefined();
|
||||
});
|
||||
|
||||
it('includes table metadata suffixes when estimating v2 tree horizontal scroll width', () => {
|
||||
setCurrentLanguage('zh-CN');
|
||||
|
||||
const tableNode = [{
|
||||
title: 'orders',
|
||||
key: 'table-orders',
|
||||
type: 'table',
|
||||
dataRef: {
|
||||
tableComment: '订单归档明细按月分区',
|
||||
rowCount: 2_450_000,
|
||||
tableSize: 157_286_400,
|
||||
createdAt: '2026-07-01 08:30:00',
|
||||
updatedAt: '2026-07-02 09:45:00',
|
||||
},
|
||||
}];
|
||||
const viewportWidth = 360;
|
||||
const titleOnlyWidth = estimateV2TreeHorizontalScrollWidth(tableNode as any, viewportWidth, []);
|
||||
const metadataWidth = estimateV2TreeHorizontalScrollWidth(
|
||||
tableNode as any,
|
||||
viewportWidth,
|
||||
['comment', 'rows', 'size', 'createdAt', 'updatedAt'],
|
||||
);
|
||||
|
||||
expect(titleOnlyWidth).toBeUndefined();
|
||||
expect(metadataWidth).toBeGreaterThan(viewportWidth);
|
||||
expect(metadataWidth).toBeGreaterThan(titleOnlyWidth ?? viewportWidth);
|
||||
});
|
||||
|
||||
it('does not repeat the active connection as an object-tree root in v2', () => {
|
||||
mocks.state.connections = [{
|
||||
id: 'conn-local',
|
||||
@@ -2926,7 +2954,6 @@ describe('Sidebar locate toolbar', () => {
|
||||
dbName="mkefu_ai_dev"
|
||||
count={15}
|
||||
currentSort="frequency"
|
||||
showTableComments
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -2938,8 +2965,6 @@ describe('Sidebar locate toolbar', () => {
|
||||
sort: t('sidebar.v2_table_group_menu.sort_frequency'),
|
||||
}));
|
||||
expect(markup).toContain(t('sidebar.menu.create_table'));
|
||||
expect(markup).toContain(t('sidebar.v2_table_group_menu.display_section'));
|
||||
expect(markup).toContain(t('sidebar.v2_table_group_menu.show_table_comments'));
|
||||
expect(markup).toContain(t('data_grid.context_menu.sort_section'));
|
||||
expect(markup).toContain(t('sidebar.menu.sort_by_name'));
|
||||
expect(markup).toContain(t('sidebar.menu.sort_by_frequency'));
|
||||
@@ -2955,7 +2980,6 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(end).toBeGreaterThan(start);
|
||||
const tableGroupCallSource = sidebarSource.slice(start, end);
|
||||
expect(tableGroupCallSource).toContain('<V2TableGroupContextMenuView');
|
||||
expect(tableGroupCallSource).toContain('showTableComments={showSidebarTableComment}');
|
||||
expect(tableGroupCallSource).not.toContain('title=');
|
||||
['? ? tables', '表 · tables'].forEach((rawSnippet) => {
|
||||
expect(tableGroupCallSource).not.toContain(rawSnippet);
|
||||
@@ -3020,6 +3044,10 @@ describe('Sidebar locate toolbar', () => {
|
||||
dbName: 'main',
|
||||
tableName: 'users',
|
||||
tableComment: '用户表',
|
||||
rowCount: 7,
|
||||
tableSize: 4096,
|
||||
createdAt: '2026-07-02 10:11:12',
|
||||
updatedAt: '2026-07-03 11:12:13',
|
||||
},
|
||||
};
|
||||
const baseOptions = {
|
||||
@@ -3036,18 +3064,23 @@ describe('Sidebar locate toolbar', () => {
|
||||
|
||||
const hiddenSuffixMarkup = renderToStaticMarkup(renderSidebarV2TreeTitle({
|
||||
...baseOptions,
|
||||
showSidebarTableComment: false,
|
||||
sidebarTableMetadataFields: ['rows'],
|
||||
}));
|
||||
expect(hiddenSuffixMarkup).not.toContain('gn-v2-tree-table-comment');
|
||||
|
||||
const visibleSuffixMarkup = renderToStaticMarkup(renderSidebarV2TreeTitle({
|
||||
...baseOptions,
|
||||
showSidebarTableComment: true,
|
||||
sidebarTableMetadataFields: ['comment', 'rows', 'size', 'createdAt', 'updatedAt'],
|
||||
}));
|
||||
expect(visibleSuffixMarkup).toContain('gn-v2-tree-table-comment');
|
||||
expect(visibleSuffixMarkup).toContain('用户表');
|
||||
expect(visibleSuffixMarkup).toContain(t('sidebar.v2_table_group_menu.metadata_value.rows', { count: '7' }));
|
||||
expect(visibleSuffixMarkup).toContain('4 KB');
|
||||
expect(visibleSuffixMarkup).toContain(t('sidebar.v2_table_group_menu.metadata_value.created_at', { time: '2026-07-02 10:11' }));
|
||||
expect(visibleSuffixMarkup).toContain(t('sidebar.v2_table_group_menu.metadata_value.updated_at', { time: '2026-07-03 11:12' }));
|
||||
|
||||
const treeTitleSource = readSourceFile('./sidebar/SidebarTreeTitle.tsx');
|
||||
const sidebarHelpersSource = readSourceFile('./sidebar/sidebarHelpers.ts');
|
||||
expect(treeTitleSource).toContain('data-sidebar-table-hover-info="true"');
|
||||
expect(treeTitleSource).toContain('rootClassName="gn-v2-tab-hover-tooltip gn-v2-sidebar-table-hover-tooltip"');
|
||||
expect(treeTitleSource).toContain('title={tableHoverInfo ? undefined : effectiveHoverTitle}');
|
||||
@@ -3059,6 +3092,10 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(treeTitleSource).toContain("t('tab_manager.kind_badge.table')");
|
||||
expect(treeTitleSource).toContain("t('tab_manager.hover.kind.table')");
|
||||
expect(treeTitleSource).toContain("t('table_designer.action.table_comment')");
|
||||
expect(sidebarHelpersSource).toContain("t('sidebar.v2_table_group_menu.metadata_value.rows'");
|
||||
expect(treeTitleSource).toContain("t('sidebar.v2_table_group_menu.display_table_size')");
|
||||
expect(treeTitleSource).toContain("t('sidebar.v2_table_group_menu.display_create_time')");
|
||||
expect(treeTitleSource).toContain("t('sidebar.v2_table_group_menu.display_update_time')");
|
||||
expect(treeTitleSource).toContain('mouseEnterDelay={1.2}');
|
||||
|
||||
const css = readV2ThemeCss();
|
||||
@@ -3076,13 +3113,24 @@ describe('Sidebar locate toolbar', () => {
|
||||
const oracleSql = buildSidebarTableStatusSQL({ config: { type: 'oracle' } } as any, 'APP');
|
||||
|
||||
expect(mysqlSql).toContain('TABLE_COMMENT AS table_comment');
|
||||
expect(mysqlSql).toContain('AS table_size');
|
||||
expect(mysqlSql).toContain('CREATE_TIME AS create_time');
|
||||
expect(pgSql).toContain("obj_description(c.oid, 'pg_class') AS table_comment");
|
||||
expect(pgSql).toContain('pg_total_relation_size(c.oid) AS table_size');
|
||||
expect(sqlServerSql).toContain('ep.value AS table_comment');
|
||||
expect(sqlServerSql).toContain('t.create_date AS create_time');
|
||||
expect(oracleSql).toContain('comments AS table_comment');
|
||||
expect(oracleSql).toContain('o.last_ddl_time AS update_time');
|
||||
|
||||
const loaderSource = readSourceFile('./sidebar/useSidebarTreeLoaders.tsx');
|
||||
expect(loaderSource).toContain('tableCommentMap');
|
||||
expect(loaderSource).toContain('tableComment: entry.tableComment');
|
||||
expect(loaderSource).toContain('tableMetadataMap');
|
||||
expect(loaderSource).toContain('resolvedMetadata?.tableComment');
|
||||
expect(loaderSource).toContain('tableSize: resolvedMetadata?.tableSize');
|
||||
expect(loaderSource).toContain('createdAt: resolvedMetadata?.createdAt');
|
||||
expect(loaderSource).toContain('updatedAt: resolvedMetadata?.updatedAt');
|
||||
expect(loaderSource).toContain('tableSize: entry.tableSize');
|
||||
expect(loaderSource).toContain('createdAt: entry.createdAt');
|
||||
expect(loaderSource).toContain('updatedAt: entry.updatedAt');
|
||||
});
|
||||
|
||||
it('listens for table overview pin changes to refresh the matching sidebar database node', () => {
|
||||
|
||||
@@ -142,6 +142,7 @@ import {
|
||||
import { useExportProgressDialog } from './ExportProgressModal';
|
||||
import { getShortcutPlatform, resolveShortcutDisplay } from '../utils/shortcuts';
|
||||
import { buildExternalSQLRootNode, type ExternalSQLTreeNode } from '../utils/externalSqlTree';
|
||||
import { resolveSidebarTableMetadataFields } from '../utils/sidebarTableMetadata';
|
||||
import { t } from '../i18n';
|
||||
import MessagePublishModal from './MessagePublishModal';
|
||||
import {
|
||||
@@ -481,7 +482,13 @@ const Sidebar: React.FC<{
|
||||
const darkMode = theme === 'dark';
|
||||
const resolvedAppearance = resolveAppearanceValues(appearance);
|
||||
const opacity = normalizeOpacityForPlatform(resolvedAppearance.opacity);
|
||||
const showSidebarTableComment = queryOptions?.showSidebarTableComment === true;
|
||||
const sidebarTableMetadataFields = useMemo(
|
||||
() => resolveSidebarTableMetadataFields(
|
||||
queryOptions?.sidebarTableMetadataFields,
|
||||
queryOptions?.showSidebarTableComment === true,
|
||||
),
|
||||
[queryOptions?.showSidebarTableComment, queryOptions?.sidebarTableMetadataFields],
|
||||
);
|
||||
const { exportProgressModal, runExportWithProgress } = useExportProgressDialog();
|
||||
const disableLocalBackdropFilter = isMacLikePlatform();
|
||||
const autoFetchVisible = useAutoFetchVisibility();
|
||||
@@ -2102,8 +2109,6 @@ const Sidebar: React.FC<{
|
||||
moveConnectionToTag,
|
||||
setSidebarTablePinned,
|
||||
setTableSortPreference,
|
||||
setQueryOptions,
|
||||
showSidebarTableComment,
|
||||
replaceTreeNodeChildren,
|
||||
loadDatabases,
|
||||
loadTables,
|
||||
@@ -2166,6 +2171,7 @@ const Sidebar: React.FC<{
|
||||
v2CommandSearchValue,
|
||||
setV2CommandActiveIndex,
|
||||
v2ExplorerFilter,
|
||||
sidebarTableMetadataFields,
|
||||
treeData,
|
||||
treeViewportWidth,
|
||||
treeHeight,
|
||||
@@ -2231,7 +2237,6 @@ const Sidebar: React.FC<{
|
||||
v2TreeMetrics,
|
||||
tableSortPreference,
|
||||
pinnedSidebarTables,
|
||||
showSidebarTableComment,
|
||||
getConnectionNodeForAction,
|
||||
buildRuntimeConfig,
|
||||
extractObjectName,
|
||||
@@ -2256,7 +2261,7 @@ const Sidebar: React.FC<{
|
||||
hoverTitle,
|
||||
statusBadge,
|
||||
getV2TreeMetaText,
|
||||
showSidebarTableComment,
|
||||
sidebarTableMetadataFields,
|
||||
toggleSidebarTablePinned,
|
||||
snapshotTreeSelectionBeforeDrag,
|
||||
restoreTreeSelectionAfterDrag,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
CheckSquareOutlined,
|
||||
CloudOutlined,
|
||||
ClearOutlined,
|
||||
ClockCircleOutlined,
|
||||
ColumnWidthOutlined,
|
||||
DashboardOutlined,
|
||||
EyeInvisibleOutlined,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { getCurrentLanguage, t } from '../i18n';
|
||||
import { getPrimaryShortcutDisplayLabel, type ShortcutPlatform } from '../utils/shortcuts';
|
||||
import { formatSidebarTableSize } from './sidebar/sidebarHelpers';
|
||||
|
||||
export type V2TableContextMenuActionKey =
|
||||
| 'pin-table'
|
||||
@@ -88,11 +90,8 @@ export const formatV2TableContextMenuRows = (count?: number): string => {
|
||||
};
|
||||
|
||||
export const formatV2TableContextMenuSize = (bytes?: number): string => {
|
||||
if (bytes === undefined || bytes === null || !Number.isFinite(bytes) || bytes < 0) return '—';
|
||||
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
||||
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
const formatted = bytes === undefined ? '' : formatSidebarTableSize(bytes);
|
||||
return formatted || '—';
|
||||
};
|
||||
|
||||
const resolveV2TableContextMenuMeta = (stats?: V2TableContextMenuStats): string => {
|
||||
@@ -264,7 +263,6 @@ export const V2TableContextMenuView: React.FC<{
|
||||
|
||||
export type V2TableGroupContextMenuActionKey =
|
||||
| 'new-table'
|
||||
| 'toggle-table-comments'
|
||||
| 'sort-by-name'
|
||||
| 'sort-by-frequency';
|
||||
|
||||
@@ -274,7 +272,6 @@ export const V2TableGroupContextMenuView: React.FC<{
|
||||
dbName?: string;
|
||||
count?: number;
|
||||
currentSort?: 'name' | 'frequency';
|
||||
showTableComments?: boolean;
|
||||
onAction?: (action: V2TableGroupContextMenuActionKey) => void;
|
||||
}> = ({
|
||||
title,
|
||||
@@ -282,7 +279,6 @@ export const V2TableGroupContextMenuView: React.FC<{
|
||||
dbName,
|
||||
count,
|
||||
currentSort = 'name',
|
||||
showTableComments = false,
|
||||
onAction,
|
||||
}) => {
|
||||
const sortLabel = currentSort === 'frequency'
|
||||
@@ -313,17 +309,6 @@ export const V2TableGroupContextMenuView: React.FC<{
|
||||
{ action: 'new-table', icon: <TableOutlined />, title: t('sidebar.menu.create_table'), kbd: primaryShortcut('N', shortcutPlatform), featured: true },
|
||||
])}
|
||||
|
||||
<div className="gn-v2-context-menu-section-title">{t('sidebar.v2_table_group_menu.display_section')}</div>
|
||||
{renderItems([
|
||||
{
|
||||
action: 'toggle-table-comments',
|
||||
icon: showTableComments ? <CheckSquareOutlined /> : <FileTextOutlined />,
|
||||
title: t('sidebar.v2_table_group_menu.show_table_comments'),
|
||||
kbd: showTableComments ? t('data_grid.context_menu.current_marker') : undefined,
|
||||
selected: showTableComments,
|
||||
},
|
||||
])}
|
||||
|
||||
<div className="gn-v2-context-menu-section-title">{t('data_grid.context_menu.sort_section')}</div>
|
||||
{renderItems([
|
||||
{ action: 'sort-by-name', icon: currentSort === 'name' ? <CheckSquareOutlined /> : <ReloadOutlined />, title: t('sidebar.menu.sort_by_name'), kbd: currentSort === 'name' ? t('data_grid.context_menu.current_marker') : undefined, selected: currentSort === 'name' },
|
||||
|
||||
@@ -3,17 +3,28 @@ import { Tooltip } from 'antd';
|
||||
import { StarFilled, StarOutlined } from '@ant-design/icons';
|
||||
import { t } from '../../i18n';
|
||||
import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from '../../utils/sidebarSqlDrag';
|
||||
import {
|
||||
type SidebarTableMetadataField,
|
||||
type SidebarTableMetadataSnapshot,
|
||||
} from '../../utils/sidebarTableMetadata';
|
||||
import { sanitizeRedisDbAlias } from '../../utils/redisDbAlias';
|
||||
import { resolveConnectionHostSummary } from '../../utils/tabDisplay';
|
||||
import { resolveSidebarObjectDragText } from '../sidebarCoreUtils';
|
||||
import { resolveV2ObjectGroupTitle } from './sidebarHelpers';
|
||||
import {
|
||||
buildSidebarTableMetadataDisplayItems,
|
||||
buildSidebarTableMetadataSnapshot,
|
||||
formatSidebarRowCount,
|
||||
formatSidebarTableSize,
|
||||
formatSidebarTableTimestamp,
|
||||
resolveV2ObjectGroupTitle,
|
||||
} from './sidebarHelpers';
|
||||
|
||||
type SidebarV2TreeTitleOptions = {
|
||||
node: any;
|
||||
hoverTitle: string;
|
||||
statusBadge: React.ReactNode;
|
||||
getV2TreeMetaText: (node: any) => string;
|
||||
showSidebarTableComment: boolean;
|
||||
sidebarTableMetadataFields: SidebarTableMetadataField[];
|
||||
toggleSidebarTablePinned: (node: any) => void;
|
||||
snapshotTreeSelectionBeforeDrag: () => void;
|
||||
restoreTreeSelectionAfterDrag: () => void;
|
||||
@@ -42,7 +53,7 @@ const clearSidebarTableNativeHoverTitle = (event: React.SyntheticEvent<HTMLEleme
|
||||
const renderSidebarTableHoverInfo = (
|
||||
node: any,
|
||||
displayTitle: string,
|
||||
tableComment: string,
|
||||
metadata: SidebarTableMetadataSnapshot,
|
||||
): React.ReactNode => {
|
||||
const dataRef = node?.dataRef || {};
|
||||
const tableName = String(dataRef.tableName || displayTitle || node?.title || '').trim();
|
||||
@@ -57,7 +68,11 @@ const renderSidebarTableHoverInfo = (
|
||||
[t('tab_manager.hover.label.database'), dbName || t('tab_manager.hover.fallback.database_not_specified')],
|
||||
['Schema', schemaName],
|
||||
[t('tab_manager.hover.label.object'), tableName],
|
||||
[t('table_designer.action.table_comment'), tableComment],
|
||||
[t('table_designer.action.table_comment'), metadata.tableComment || ''],
|
||||
[t('sidebar.v2_table_group_menu.display_table_rows'), metadata.rowCount !== undefined ? formatSidebarRowCount(metadata.rowCount) : ''],
|
||||
[t('sidebar.v2_table_group_menu.display_table_size'), metadata.tableSize !== undefined ? formatSidebarTableSize(metadata.tableSize) : ''],
|
||||
[t('sidebar.v2_table_group_menu.display_create_time'), metadata.createdAt ? formatSidebarTableTimestamp(metadata.createdAt) : ''],
|
||||
[t('sidebar.v2_table_group_menu.display_update_time'), metadata.updatedAt ? formatSidebarTableTimestamp(metadata.updatedAt) : ''],
|
||||
].filter(([, value]) => Boolean(value));
|
||||
|
||||
return (
|
||||
@@ -100,7 +115,7 @@ export const renderSidebarV2TreeTitle = ({
|
||||
hoverTitle,
|
||||
statusBadge,
|
||||
getV2TreeMetaText,
|
||||
showSidebarTableComment,
|
||||
sidebarTableMetadataFields,
|
||||
toggleSidebarTablePinned,
|
||||
snapshotTreeSelectionBeforeDrag,
|
||||
restoreTreeSelectionAfterDrag,
|
||||
@@ -130,15 +145,17 @@ export const renderSidebarV2TreeTitle = ({
|
||||
}
|
||||
return rawTitle;
|
||||
})();
|
||||
const tableComment = node.type === 'table'
|
||||
? String(node?.dataRef?.tableComment || '').trim()
|
||||
: '';
|
||||
const tableCommentSuffix = showSidebarTableComment && tableComment ? tableComment : '';
|
||||
const tableMetadata = node.type === 'table'
|
||||
? buildSidebarTableMetadataSnapshot(node?.dataRef)
|
||||
: null;
|
||||
const tableMetadataItems = tableMetadata
|
||||
? buildSidebarTableMetadataDisplayItems(sidebarTableMetadataFields, tableMetadata)
|
||||
: [];
|
||||
const effectiveHoverTitle = hoverTitle;
|
||||
const tableHoverInfo = node.type === 'table'
|
||||
? renderSidebarTableHoverInfo(node, displayTitle, tableComment)
|
||||
? renderSidebarTableHoverInfo(node, displayTitle, tableMetadata ?? {})
|
||||
: null;
|
||||
const metaText = getV2TreeMetaText(node);
|
||||
const metaText = node.type === 'table' ? '' : getV2TreeMetaText(node);
|
||||
const redisDbAlias = node.type === 'redis-db'
|
||||
? sanitizeRedisDbAlias(node?.dataRef?.redisDbAlias)
|
||||
: '';
|
||||
@@ -248,9 +265,9 @@ export const renderSidebarV2TreeTitle = ({
|
||||
</>
|
||||
) : displayTitle}
|
||||
</span>
|
||||
{tableCommentSuffix && (
|
||||
<span className="gn-v2-tree-table-comment">{tableCommentSuffix}</span>
|
||||
)}
|
||||
{tableMetadataItems.map((item) => (
|
||||
<span key={item.key} className={item.className}>{item.text}</span>
|
||||
))}
|
||||
{metaText && <span className="gn-v2-tree-count">{metaText}</span>}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
// - 共享常量和类型集中管理,便于跨文件复用
|
||||
|
||||
import { t } from '../../i18n';
|
||||
import type {
|
||||
SidebarTableMetadataField,
|
||||
SidebarTableMetadataSnapshot,
|
||||
} from '../../utils/sidebarTableMetadata';
|
||||
|
||||
// === 共享常量 ===
|
||||
|
||||
@@ -36,6 +40,122 @@ export const formatSidebarRowCount = (count: number): string => {
|
||||
return String(Math.round(count));
|
||||
};
|
||||
|
||||
/**
|
||||
* formatSidebarTableSize 把字节数格式化为适合侧栏展示的短文本。
|
||||
*/
|
||||
export const formatSidebarTableSize = (bytes: number): string => {
|
||||
if (!Number.isFinite(bytes) || bytes < 0) return '';
|
||||
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
||||
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
};
|
||||
|
||||
const padSidebarTimestampPart = (value: number): string => String(value).padStart(2, '0');
|
||||
|
||||
/**
|
||||
* formatSidebarTableTimestamp 把数据库返回的时间值统一压缩成 `YYYY-MM-DD HH:mm`。
|
||||
* 若值无法被 Date 可靠解析,则尽量保留原始文本的可读部分。
|
||||
*/
|
||||
export const formatSidebarTableTimestamp = (value: unknown): string => {
|
||||
const text = String(value ?? '').trim();
|
||||
if (!text) return '';
|
||||
const normalizedText = text.replace('T', ' ').replace(/\.\d+$/, '');
|
||||
const parsed = new Date(text);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return normalizedText.length > 16 ? normalizedText.slice(0, 16) : normalizedText;
|
||||
}
|
||||
return [
|
||||
parsed.getFullYear(),
|
||||
'-',
|
||||
padSidebarTimestampPart(parsed.getMonth() + 1),
|
||||
'-',
|
||||
padSidebarTimestampPart(parsed.getDate()),
|
||||
' ',
|
||||
padSidebarTimestampPart(parsed.getHours()),
|
||||
':',
|
||||
padSidebarTimestampPart(parsed.getMinutes()),
|
||||
].join('');
|
||||
};
|
||||
|
||||
export interface SidebarTableMetadataDisplayItem {
|
||||
key: SidebarTableMetadataField;
|
||||
text: string;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export const buildSidebarTableMetadataSnapshot = (
|
||||
value: Partial<SidebarTableMetadataSnapshot> | null | undefined,
|
||||
): SidebarTableMetadataSnapshot => {
|
||||
const rowCount = Number(value?.rowCount);
|
||||
const tableSize = Number(value?.tableSize);
|
||||
const tableComment = String(value?.tableComment || '').trim();
|
||||
const createdAt = String(value?.createdAt || '').trim();
|
||||
const updatedAt = String(value?.updatedAt || '').trim();
|
||||
return {
|
||||
...(tableComment ? { tableComment } : {}),
|
||||
...(Number.isFinite(rowCount) && rowCount >= 0 ? { rowCount } : {}),
|
||||
...(Number.isFinite(tableSize) && tableSize >= 0 ? { tableSize } : {}),
|
||||
...(createdAt ? { createdAt } : {}),
|
||||
...(updatedAt ? { updatedAt } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
export const buildSidebarTableMetadataDisplayItems = (
|
||||
metadataFields: SidebarTableMetadataField[],
|
||||
snapshot: SidebarTableMetadataSnapshot,
|
||||
): SidebarTableMetadataDisplayItem[] => {
|
||||
const items: SidebarTableMetadataDisplayItem[] = [];
|
||||
if (metadataFields.includes('comment') && snapshot.tableComment) {
|
||||
items.push({
|
||||
key: 'comment',
|
||||
text: snapshot.tableComment,
|
||||
className: 'gn-v2-tree-table-comment',
|
||||
});
|
||||
}
|
||||
if (metadataFields.includes('rows') && snapshot.rowCount !== undefined) {
|
||||
const count = formatSidebarRowCount(snapshot.rowCount);
|
||||
if (count) {
|
||||
items.push({
|
||||
key: 'rows',
|
||||
text: t('sidebar.v2_table_group_menu.metadata_value.rows', { count }),
|
||||
className: 'gn-v2-tree-count',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (metadataFields.includes('size') && snapshot.tableSize !== undefined) {
|
||||
const size = formatSidebarTableSize(snapshot.tableSize);
|
||||
if (size) {
|
||||
items.push({
|
||||
key: 'size',
|
||||
text: t('sidebar.v2_table_group_menu.metadata_value.size', { size }),
|
||||
className: 'gn-v2-tree-count',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (metadataFields.includes('createdAt') && snapshot.createdAt) {
|
||||
const time = formatSidebarTableTimestamp(snapshot.createdAt);
|
||||
if (time) {
|
||||
items.push({
|
||||
key: 'createdAt',
|
||||
text: t('sidebar.v2_table_group_menu.metadata_value.created_at', { time }),
|
||||
className: 'gn-v2-tree-count',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (metadataFields.includes('updatedAt') && snapshot.updatedAt) {
|
||||
const time = formatSidebarTableTimestamp(snapshot.updatedAt);
|
||||
if (time) {
|
||||
items.push({
|
||||
key: 'updatedAt',
|
||||
text: t('sidebar.v2_table_group_menu.metadata_value.updated_at', { time }),
|
||||
className: 'gn-v2-tree-count',
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* hasSidebarLazyChildren 判断树节点的 children 是否已加载(用于按需展开)。
|
||||
*/
|
||||
|
||||
@@ -262,7 +262,9 @@ const buildSidebarTableStatusSQL = (
|
||||
case "mysql":
|
||||
case "starrocks":
|
||||
return [
|
||||
"SELECT TABLE_NAME AS table_name, TABLE_COMMENT AS table_comment, TABLE_ROWS AS table_rows",
|
||||
"SELECT TABLE_NAME AS table_name, TABLE_COMMENT AS table_comment, TABLE_ROWS AS table_rows,",
|
||||
"COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS table_size,",
|
||||
"CREATE_TIME AS create_time, UPDATE_TIME AS update_time",
|
||||
"FROM information_schema.tables",
|
||||
`WHERE table_schema = '${safeDbName}'`,
|
||||
"AND table_type = 'BASE TABLE'",
|
||||
@@ -275,7 +277,8 @@ const buildSidebarTableStatusSQL = (
|
||||
case "opengauss":
|
||||
case "gaussdb":
|
||||
return [
|
||||
"SELECT n.nspname || '.' || c.relname AS table_name, obj_description(c.oid, 'pg_class') AS table_comment, c.reltuples::bigint AS table_rows",
|
||||
"SELECT n.nspname || '.' || c.relname AS table_name, obj_description(c.oid, 'pg_class') AS table_comment, c.reltuples::bigint AS table_rows,",
|
||||
"pg_total_relation_size(c.oid) AS table_size, NULL::text AS create_time, NULL::text AS update_time",
|
||||
"FROM pg_class c",
|
||||
"JOIN pg_namespace n ON n.oid = c.relnamespace",
|
||||
"WHERE c.relkind = 'r'",
|
||||
@@ -286,19 +289,22 @@ const buildSidebarTableStatusSQL = (
|
||||
case "sqlserver": {
|
||||
const safeDb = quoteSqlServerIdentifier(dbName);
|
||||
return [
|
||||
"SELECT s.name + '.' + t.name AS table_name, ep.value AS table_comment, SUM(p.rows) AS table_rows",
|
||||
"SELECT s.name + '.' + t.name AS table_name, ep.value AS table_comment, SUM(p.rows) AS table_rows,",
|
||||
"SUM(a.total_pages) * 8 * 1024 AS table_size, t.create_date AS create_time, t.modify_date AS update_time",
|
||||
`FROM ${safeDb}.sys.tables t`,
|
||||
`JOIN ${safeDb}.sys.schemas s ON t.schema_id = s.schema_id`,
|
||||
`LEFT JOIN ${safeDb}.sys.extended_properties ep ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description'`,
|
||||
`LEFT JOIN ${safeDb}.sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0, 1)`,
|
||||
`LEFT JOIN ${safeDb}.sys.allocation_units a ON p.partition_id = a.container_id`,
|
||||
"WHERE t.type = 'U'",
|
||||
"GROUP BY s.name, t.name, ep.value",
|
||||
"GROUP BY s.name, t.name, ep.value, t.create_date, t.modify_date",
|
||||
"ORDER BY s.name, t.name",
|
||||
].join("\n");
|
||||
}
|
||||
case "clickhouse":
|
||||
return [
|
||||
"SELECT name AS table_name, comment AS table_comment, total_rows AS table_rows",
|
||||
"SELECT name AS table_name, comment AS table_comment, total_rows AS table_rows,",
|
||||
"total_bytes AS table_size, NULL AS create_time, metadata_modification_time AS update_time",
|
||||
"FROM system.tables",
|
||||
`WHERE database = '${safeDbName}'`,
|
||||
"AND engine NOT IN ('View', 'MaterializedView')",
|
||||
@@ -308,10 +314,15 @@ const buildSidebarTableStatusSQL = (
|
||||
case "dm": {
|
||||
const owner = escapeSQLLiteral(dbName).toUpperCase();
|
||||
return [
|
||||
"SELECT owner AS schema_name, table_name, comments AS table_comment, num_rows AS table_rows",
|
||||
"FROM all_tab_comments JOIN all_tables USING (table_name, owner)",
|
||||
`WHERE owner = '${owner}'`,
|
||||
"ORDER BY table_name",
|
||||
"SELECT c.owner AS schema_name, c.table_name, c.comments AS table_comment, t.num_rows AS table_rows,",
|
||||
"COALESCE(SUM(s.bytes), 0) AS table_size, o.created AS create_time, o.last_ddl_time AS update_time",
|
||||
"FROM all_tab_comments c",
|
||||
"JOIN all_tables t ON t.owner = c.owner AND t.table_name = c.table_name",
|
||||
"LEFT JOIN all_objects o ON o.owner = t.owner AND o.object_name = t.table_name AND o.object_type = 'TABLE'",
|
||||
"LEFT JOIN all_segments s ON s.owner = t.owner AND s.segment_name = t.table_name AND s.segment_type = 'TABLE'",
|
||||
`WHERE c.owner = '${owner}'`,
|
||||
"GROUP BY c.owner, c.table_name, c.comments, t.num_rows, o.created, o.last_ddl_time",
|
||||
"ORDER BY c.table_name",
|
||||
].join("\n");
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useStore } from '../../store';
|
||||
import type { SavedConnection } from '../../types';
|
||||
import { getCurrentLanguage, t } from '../../i18n';
|
||||
import { resolveShortcutDisplay } from '../../utils/shortcuts';
|
||||
import type { SidebarTableMetadataField } from '../../utils/sidebarTableMetadata';
|
||||
import { resolveConnectionHostSummary, resolveConnectionHostTokens } from '../../utils/tabDisplay';
|
||||
import { resolveConnectionAccentColor, resolveConnectionIconType } from '../../utils/connectionVisual';
|
||||
import { getDbIcon } from '../DatabaseIcons';
|
||||
@@ -71,6 +72,7 @@ type SidebarSearchModelArgs = {
|
||||
v2CommandSearchValue: string;
|
||||
setV2CommandActiveIndex: Dispatch<SetStateAction<number>>;
|
||||
v2ExplorerFilter: V2ExplorerFilter;
|
||||
sidebarTableMetadataFields: SidebarTableMetadataField[];
|
||||
treeData: TreeNode[];
|
||||
treeViewportWidth: number;
|
||||
treeHeight: number;
|
||||
@@ -109,6 +111,7 @@ export const useSidebarSearchModel = ({
|
||||
v2CommandSearchValue,
|
||||
setV2CommandActiveIndex,
|
||||
v2ExplorerFilter,
|
||||
sidebarTableMetadataFields,
|
||||
treeData,
|
||||
treeViewportWidth,
|
||||
treeHeight,
|
||||
@@ -608,8 +611,12 @@ export const useSidebarSearchModel = ({
|
||||
return filterV2ExplorerTreeByKind(activeConnectionTreeData, v2ExplorerFilter);
|
||||
}, [activeConnectionTreeData, displayTreeData, v2ExplorerFilter]);
|
||||
const v2TreeHorizontalScrollWidth = useMemo(
|
||||
() => estimateV2TreeHorizontalScrollWidth(v2VisibleTreeData, treeViewportWidth),
|
||||
[treeViewportWidth, v2VisibleTreeData],
|
||||
() => estimateV2TreeHorizontalScrollWidth(
|
||||
v2VisibleTreeData,
|
||||
treeViewportWidth,
|
||||
sidebarTableMetadataFields,
|
||||
),
|
||||
[sidebarTableMetadataFields, treeViewportWidth, v2VisibleTreeData],
|
||||
);
|
||||
const effectiveTreeHeight = treeHeight;
|
||||
const v2TreeMetrics = useMemo(() => {
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
type SidebarTreeNode as TreeNode,
|
||||
} from '../sidebarV2Utils';
|
||||
import { DBGetDatabases, DBGetTables, DBQuery, GetDriverStatusList, JVMProbeCapabilities } from '../../../wailsjs/go/app/App';
|
||||
import type { SidebarTableMetadataSnapshot } from '../../utils/sidebarTableMetadata';
|
||||
|
||||
type DriverStatusSnapshot = {
|
||||
type: string;
|
||||
@@ -495,9 +496,7 @@ export const useSidebarTreeLoaders = ({
|
||||
const tableStatsResult = tableStatusSql
|
||||
? await DBQuery(buildRpcConnectionConfig(config) as any, conn.dbName, tableStatusSql).catch(() => ({ success: false, data: [] as any[] }))
|
||||
: { success: false, data: [] as any[] };
|
||||
const tableRowCountMap = new Map<string, number>();
|
||||
const tableCommentMap = new Map<string, string>();
|
||||
const tableSchemaMap = new Map<string, string>();
|
||||
const tableMetadataMap = new Map<string, SidebarTableMetadataSnapshot & { schemaName?: string }>();
|
||||
const buildTableMetadataKeys = (rawTableName: string, rawSchemaName = ''): string[] => {
|
||||
const tableName = String(rawTableName || '').trim();
|
||||
if (!tableName) return [];
|
||||
@@ -510,11 +509,33 @@ export const useSidebarTreeLoaders = ({
|
||||
if (qualifiedName) keys.add(qualifiedName.toLowerCase());
|
||||
return Array.from(keys);
|
||||
};
|
||||
const putTableComment = (rawTableName: string, rawComment: string, rawSchemaName = '') => {
|
||||
const comment = String(rawComment || '').trim();
|
||||
if (!comment) return;
|
||||
const readNumericMetadataValue = (row: Record<string, any>, keys: string[]): number | undefined => {
|
||||
const rawValue = getCaseInsensitiveValue(row, keys);
|
||||
if (rawValue === undefined || rawValue === null || rawValue === '') return undefined;
|
||||
const numericValue = Number(String(rawValue).replace(/,/g, ''));
|
||||
return Number.isFinite(numericValue) ? numericValue : undefined;
|
||||
};
|
||||
const normalizeMetadataTimestamp = (rawValue: unknown): string | undefined => {
|
||||
if (rawValue === undefined || rawValue === null) return undefined;
|
||||
const normalized = String(rawValue).trim();
|
||||
return normalized ? normalized : undefined;
|
||||
};
|
||||
const mergeTableMetadata = (
|
||||
rawTableName: string,
|
||||
patch: SidebarTableMetadataSnapshot & { schemaName?: string },
|
||||
rawSchemaName = '',
|
||||
) => {
|
||||
buildTableMetadataKeys(rawTableName, rawSchemaName).forEach((metadataKey) => {
|
||||
tableCommentMap.set(metadataKey, comment);
|
||||
const current = tableMetadataMap.get(metadataKey) || {};
|
||||
tableMetadataMap.set(metadataKey, {
|
||||
...current,
|
||||
...(patch.schemaName ? { schemaName: patch.schemaName } : {}),
|
||||
...(patch.tableComment ? { tableComment: patch.tableComment } : {}),
|
||||
...(patch.rowCount !== undefined ? { rowCount: patch.rowCount } : {}),
|
||||
...(patch.tableSize !== undefined ? { tableSize: patch.tableSize } : {}),
|
||||
...(patch.createdAt ? { createdAt: patch.createdAt } : {}),
|
||||
...(patch.updatedAt ? { updatedAt: patch.updatedAt } : {}),
|
||||
});
|
||||
});
|
||||
};
|
||||
if (tableStatsResult?.success && Array.isArray(tableStatsResult.data)) {
|
||||
@@ -526,12 +547,7 @@ export const useSidebarTreeLoaders = ({
|
||||
).trim();
|
||||
if (!rawTableName) return;
|
||||
const rawSchemaName = getCaseInsensitiveValue(row, ['schema_name', 'SCHEMA_NAME', 'owner', 'OWNER']);
|
||||
buildTableMetadataKeys(rawTableName, rawSchemaName).forEach((metadataKey) => {
|
||||
if (rawSchemaName) {
|
||||
tableSchemaMap.set(metadataKey, rawSchemaName);
|
||||
}
|
||||
});
|
||||
putTableComment(rawTableName, getCaseInsensitiveValue(row, [
|
||||
const tableComment = String(getCaseInsensitiveValue(row, [
|
||||
'table_comment',
|
||||
'TABLE_COMMENT',
|
||||
'comment',
|
||||
@@ -539,21 +555,54 @@ export const useSidebarTreeLoaders = ({
|
||||
'comments',
|
||||
'COMMENTS',
|
||||
'MS_Description',
|
||||
]), rawSchemaName);
|
||||
]) || '').trim();
|
||||
const rowCount = parseMetadataRowCount(row);
|
||||
if (rowCount === undefined) return;
|
||||
buildTableMetadataKeys(rawTableName, rawSchemaName).forEach((metadataKey) => {
|
||||
tableRowCountMap.set(metadataKey, rowCount);
|
||||
});
|
||||
const tableSize = readNumericMetadataValue(row, [
|
||||
'table_size',
|
||||
'TABLE_SIZE',
|
||||
'data_length',
|
||||
'DATA_LENGTH',
|
||||
'total_bytes',
|
||||
'TOTAL_BYTES',
|
||||
]);
|
||||
const createdAt = normalizeMetadataTimestamp(getCaseInsensitiveValue(row, [
|
||||
'create_time',
|
||||
'CREATE_TIME',
|
||||
'created_at',
|
||||
'CREATED_AT',
|
||||
'create_date',
|
||||
'CREATE_DATE',
|
||||
]));
|
||||
const updatedAt = normalizeMetadataTimestamp(getCaseInsensitiveValue(row, [
|
||||
'update_time',
|
||||
'UPDATE_TIME',
|
||||
'updated_at',
|
||||
'UPDATED_AT',
|
||||
'modify_date',
|
||||
'MODIFY_DATE',
|
||||
'last_ddl_time',
|
||||
'LAST_DDL_TIME',
|
||||
]));
|
||||
mergeTableMetadata(rawTableName, {
|
||||
schemaName: rawSchemaName ? String(rawSchemaName).trim() : undefined,
|
||||
...(tableComment ? { tableComment } : {}),
|
||||
...(rowCount !== undefined ? { rowCount } : {}),
|
||||
...(tableSize !== undefined ? { tableSize } : {}),
|
||||
...(createdAt ? { createdAt } : {}),
|
||||
...(updatedAt ? { updatedAt } : {}),
|
||||
}, rawSchemaName);
|
||||
});
|
||||
}
|
||||
const tableEntries = tableRows.map((row: any) => {
|
||||
const tableName = Object.values(row)[0] as string;
|
||||
const parsed = splitQualifiedName(tableName);
|
||||
const metadataKeys = buildTableMetadataKeys(tableName);
|
||||
const resolvedMetadata = metadataKeys
|
||||
.map((metadataKey) => tableMetadataMap.get(metadataKey))
|
||||
.find((value): value is SidebarTableMetadataSnapshot & { schemaName?: string } => !!value);
|
||||
const rowSchemaName = getCaseInsensitiveValue(row, ['schema_name', 'SCHEMA_NAME', 'owner', 'OWNER']);
|
||||
const mappedSchemaName = rowSchemaName
|
||||
|| metadataKeys.map((metadataKey) => tableSchemaMap.get(metadataKey)).find((value): value is string => !!value)
|
||||
|| resolvedMetadata?.schemaName
|
||||
|| parsed.schemaName;
|
||||
const rowComment = getCaseInsensitiveValue(row, [
|
||||
'table_comment',
|
||||
@@ -567,13 +616,12 @@ export const useSidebarTreeLoaders = ({
|
||||
tableName,
|
||||
schemaName: mappedSchemaName,
|
||||
displayName: getSidebarTableDisplayName(conn, tableName),
|
||||
rowCount: metadataKeys
|
||||
.map((metadataKey) => tableRowCountMap.get(metadataKey))
|
||||
.find((value) => value !== undefined),
|
||||
rowCount: resolvedMetadata?.rowCount,
|
||||
tableSize: resolvedMetadata?.tableSize,
|
||||
createdAt: resolvedMetadata?.createdAt,
|
||||
updatedAt: resolvedMetadata?.updatedAt,
|
||||
tableComment: rowComment
|
||||
|| metadataKeys
|
||||
.map((metadataKey) => tableCommentMap.get(metadataKey))
|
||||
.find((value) => !!value)
|
||||
|| resolvedMetadata?.tableComment
|
||||
|| '',
|
||||
};
|
||||
});
|
||||
@@ -740,7 +788,16 @@ export const useSidebarTreeLoaders = ({
|
||||
|
||||
eventEntries.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()));
|
||||
|
||||
const buildTableNode = (entry: { tableName: string; schemaName: string; displayName: string; rowCount?: number; tableComment?: string }): TreeNode => {
|
||||
const buildTableNode = (entry: {
|
||||
tableName: string;
|
||||
schemaName: string;
|
||||
displayName: string;
|
||||
rowCount?: number;
|
||||
tableSize?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
tableComment?: string;
|
||||
}): TreeNode => {
|
||||
const isPinned = isV2Ui && isSidebarTablePinned(
|
||||
currentPinnedSidebarTables,
|
||||
conn.id,
|
||||
@@ -758,6 +815,9 @@ export const useSidebarTreeLoaders = ({
|
||||
tableName: entry.tableName,
|
||||
schemaName: entry.schemaName,
|
||||
rowCount: entry.rowCount,
|
||||
tableSize: entry.tableSize,
|
||||
createdAt: entry.createdAt,
|
||||
updatedAt: entry.updatedAt,
|
||||
tableComment: entry.tableComment,
|
||||
...(isPinned ? { pinnedSidebarTable: true } : {}),
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { FormInstance } from 'antd/es/form';
|
||||
import Modal from '../common/ResizableDraggableModal';
|
||||
import { t } from '../../i18n';
|
||||
import type { SavedConnection } from '../../types';
|
||||
import type { QueryOptions } from '../../store';
|
||||
import { buildRpcConnectionConfig } from '../../utils/connectionRpcConfig';
|
||||
import { resolveConnectionAccentColor, resolveConnectionIconType } from '../../utils/connectionVisual';
|
||||
import { buildTableSelectQuery } from '../../utils/objectQueryTemplates';
|
||||
@@ -56,8 +55,6 @@ type UseSidebarV2ActionHandlersArgs = {
|
||||
moveConnectionToTag: (connectionId: string, tagId: string | null) => void;
|
||||
setSidebarTablePinned: (connectionId: string, dbName: string, tableName: string, schemaName: string, pinned: boolean) => void;
|
||||
setTableSortPreference: (connectionId: string, dbName: string, sortBy: 'name' | 'frequency') => void;
|
||||
setQueryOptions: (options: Partial<QueryOptions>) => void;
|
||||
showSidebarTableComment: boolean;
|
||||
replaceTreeNodeChildren: (key: React.Key, children: TreeNode[] | undefined) => void;
|
||||
loadDatabases: (node: any) => Promise<void>;
|
||||
loadTables: (node: any) => Promise<void>;
|
||||
@@ -121,8 +118,6 @@ export const useSidebarV2ActionHandlers = ({
|
||||
moveConnectionToTag,
|
||||
setSidebarTablePinned,
|
||||
setTableSortPreference,
|
||||
setQueryOptions,
|
||||
showSidebarTableComment,
|
||||
replaceTreeNodeChildren,
|
||||
loadDatabases,
|
||||
loadTables,
|
||||
@@ -267,9 +262,6 @@ export const useSidebarV2ActionHandlers = ({
|
||||
case 'new-table':
|
||||
openNewTableDesign(node);
|
||||
return;
|
||||
case 'toggle-table-comments':
|
||||
setQueryOptions({ showSidebarTableComment: !showSidebarTableComment });
|
||||
return;
|
||||
case 'sort-by-name':
|
||||
handleTableGroupSortAction(node, 'name');
|
||||
return;
|
||||
|
||||
@@ -55,7 +55,6 @@ type SidebarV2ContextMenuOptions = {
|
||||
};
|
||||
tableSortPreference: Record<string, any>;
|
||||
pinnedSidebarTables: any[];
|
||||
showSidebarTableComment: boolean;
|
||||
getConnectionNodeForAction: (conn: SavedConnection) => TreeNode;
|
||||
buildRuntimeConfig: (conn: any, overrideDatabase?: string, clearDatabase?: boolean) => any;
|
||||
extractObjectName: (fullName: string) => string;
|
||||
@@ -83,7 +82,6 @@ export const useSidebarV2ContextMenu = ({
|
||||
v2TreeMetrics,
|
||||
tableSortPreference,
|
||||
pinnedSidebarTables,
|
||||
showSidebarTableComment,
|
||||
getConnectionNodeForAction,
|
||||
buildRuntimeConfig,
|
||||
extractObjectName,
|
||||
@@ -313,7 +311,6 @@ export const useSidebarV2ContextMenu = ({
|
||||
dbName={String(groupData.dbName || '')}
|
||||
count={Array.isArray(node.children) ? node.children.length : 0}
|
||||
currentSort={currentSort}
|
||||
showTableComments={showSidebarTableComment}
|
||||
onAction={(action) => {
|
||||
setContextMenu(null);
|
||||
handleV2TableGroupContextMenuAction(node, action);
|
||||
|
||||
@@ -7,8 +7,13 @@ import {
|
||||
resolveSidebarRootOrderTokens,
|
||||
} from '../store';
|
||||
import type { ConnectionTag, SavedConnection } from '../types';
|
||||
import type { SidebarTableMetadataField } from '../utils/sidebarTableMetadata';
|
||||
import { t } from '../i18n';
|
||||
import { t as catalogTranslate } from '../i18n/catalog';
|
||||
import {
|
||||
buildSidebarTableMetadataDisplayItems,
|
||||
buildSidebarTableMetadataSnapshot,
|
||||
} from './sidebar/sidebarHelpers';
|
||||
|
||||
type SidebarV2Translate = (key: string) => string;
|
||||
|
||||
@@ -327,12 +332,15 @@ const V2_TREE_HORIZONTAL_SCROLL_MAX_WIDTH = 2600;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_BASE_WIDTH = 88;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_INDENT_WIDTH = 24;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_AVG_CHAR_WIDTH = 8;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_ITEM_GAP_WIDTH = 5;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_COMMENT_MAX_CHARS = 32;
|
||||
const V2_TREE_HORIZONTAL_SCROLL_VIEWPORT_BUFFER = 48;
|
||||
export const V2_TREE_HORIZONTAL_SCROLL_BOTTOM_RESERVE = 32;
|
||||
|
||||
export const estimateV2TreeHorizontalScrollWidth = (
|
||||
nodes: SidebarTreeNode[],
|
||||
viewportWidth: number,
|
||||
sidebarTableMetadataFields: SidebarTableMetadataField[] = [],
|
||||
): number | undefined => {
|
||||
const safeViewportWidth = Math.max(0, Math.ceil(viewportWidth || 0));
|
||||
let estimatedContentWidth = safeViewportWidth;
|
||||
@@ -340,12 +348,30 @@ export const estimateV2TreeHorizontalScrollWidth = (
|
||||
const visit = (items: SidebarTreeNode[], depth: number) => {
|
||||
items.forEach((node) => {
|
||||
const title = String(node?.title || '');
|
||||
const metaText = node?.dataRef?.groupKey === 'tables' && Array.isArray(node.children)
|
||||
? String(node.children.length)
|
||||
: '';
|
||||
const tableMetadataItems = node?.type === 'table'
|
||||
? buildSidebarTableMetadataDisplayItems(
|
||||
sidebarTableMetadataFields,
|
||||
buildSidebarTableMetadataSnapshot(node?.dataRef),
|
||||
)
|
||||
: [];
|
||||
const metaText = tableMetadataItems.length > 0
|
||||
? tableMetadataItems
|
||||
.map((item) => item.key === 'comment'
|
||||
? item.text.slice(0, V2_TREE_HORIZONTAL_SCROLL_COMMENT_MAX_CHARS)
|
||||
: item.text)
|
||||
.join('')
|
||||
: node?.dataRef?.groupKey === 'tables' && Array.isArray(node.children)
|
||||
? String(node.children.length)
|
||||
: '';
|
||||
const metaItemCount = tableMetadataItems.length > 0
|
||||
? tableMetadataItems.length
|
||||
: metaText
|
||||
? 1
|
||||
: 0;
|
||||
const nodeWidth = V2_TREE_HORIZONTAL_SCROLL_BASE_WIDTH
|
||||
+ (depth * V2_TREE_HORIZONTAL_SCROLL_INDENT_WIDTH)
|
||||
+ ((title.length + metaText.length) * V2_TREE_HORIZONTAL_SCROLL_AVG_CHAR_WIDTH);
|
||||
+ ((title.length + metaText.length) * V2_TREE_HORIZONTAL_SCROLL_AVG_CHAR_WIDTH)
|
||||
+ (metaItemCount * V2_TREE_HORIZONTAL_SCROLL_ITEM_GAP_WIDTH);
|
||||
estimatedContentWidth = Math.max(estimatedContentWidth, nodeWidth);
|
||||
if (node.children?.length) {
|
||||
visit(node.children, depth + 1);
|
||||
|
||||
Reference in New Issue
Block a user