mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-31 09:09:46 +08:00
🐛 fix(sidebar): 隐藏达梦等数据源不支持的数据库管理入口
- 新增数据库级 DDL 能力判定,统一收敛新建库、重命名库、删库菜单显示 - 修正 Sidebar V1/V2 右键菜单,避免达梦和 Oracle-like 数据源暴露误导入口 - 补充能力与菜单回归测试,覆盖达梦、Oracle 和 OceanBase Oracle 协议 Refs #496
This commit is contained in:
@@ -762,6 +762,31 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(markup).toContain('删除连接');
|
||||
});
|
||||
|
||||
it('omits unsupported database management actions for Oracle-like connection and database menus', () => {
|
||||
const connectionMarkup = renderToStaticMarkup(
|
||||
<V2ConnectionContextMenuView
|
||||
connectionName="dm-prod"
|
||||
hostSummary="10.0.0.10:5236"
|
||||
driverLabel="dameng"
|
||||
supportsCreateDatabase={false}
|
||||
/>,
|
||||
);
|
||||
const databaseMarkup = renderToStaticMarkup(
|
||||
<V2DatabaseContextMenuView
|
||||
dbName="SYSDBA"
|
||||
dialect="dm"
|
||||
supportsRenameDatabase={false}
|
||||
supportsDropDatabase={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(connectionMarkup).not.toContain('新建数据库');
|
||||
expect(databaseMarkup).not.toContain('重命名数据库');
|
||||
expect(databaseMarkup).not.toContain('删除数据库 · DROP');
|
||||
expect(databaseMarkup).toContain('刷新对象树');
|
||||
expect(databaseMarkup).toContain('关闭数据库');
|
||||
});
|
||||
|
||||
it('renders the v2 table group menu with sort state', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<V2TableGroupContextMenuView
|
||||
|
||||
@@ -55,6 +55,7 @@ import { getTableDataDangerActionMeta, supportsTableTruncateAction, type TableDa
|
||||
import { useAutoFetchVisibility } from '../utils/autoFetchVisibility';
|
||||
import FindInDatabaseModal from './FindInDatabaseModal';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities';
|
||||
import { noAutoCapInputProps } from '../utils/inputAutoCap';
|
||||
import { normalizeSidebarViewName, resolveSidebarRuntimeDatabase } from '../utils/sidebarMetadata';
|
||||
import { buildStarRocksMaterializedViewPreviewSql } from './tableDesignerSchemaSql';
|
||||
@@ -5422,12 +5423,15 @@ const Sidebar: React.FC<{
|
||||
|
||||
const renderV2DatabaseContextMenu = (node: any) => {
|
||||
const dialect = getMetadataDialect(node.dataRef as SavedConnection);
|
||||
const capabilities = getDataSourceCapabilities((node.dataRef as SavedConnection)?.config);
|
||||
return (
|
||||
<V2DatabaseContextMenuView
|
||||
dbName={String(node.dataRef?.dbName || node.title || '')}
|
||||
dialect={dialect}
|
||||
supportsSchemaActions={isPostgresSchemaDialect(dialect)}
|
||||
supportsStarRocksActions={dialect === 'starrocks'}
|
||||
supportsRenameDatabase={capabilities.supportsRenameDatabase}
|
||||
supportsDropDatabase={capabilities.supportsDropDatabase}
|
||||
onAction={(action) => {
|
||||
setContextMenu(null);
|
||||
handleV2DatabaseContextMenuAction(node, action);
|
||||
@@ -5438,6 +5442,7 @@ const Sidebar: React.FC<{
|
||||
|
||||
const renderV2ConnectionContextMenu = (node: any) => {
|
||||
const conn = node.dataRef as SavedConnection;
|
||||
const capabilities = getDataSourceCapabilities(conn?.config);
|
||||
const currentTagId = connectionTags.find((tag) => tag.connectionIds.includes(String(conn.id || node.key)))?.id || '';
|
||||
return (
|
||||
<V2ConnectionContextMenuView
|
||||
@@ -5445,6 +5450,7 @@ const Sidebar: React.FC<{
|
||||
hostSummary={resolveConnectionHostSummary(conn?.config)}
|
||||
driverLabel={resolveConnectionIconType(conn)}
|
||||
isRedis={conn?.config?.type === 'redis'}
|
||||
supportsCreateDatabase={capabilities.supportsCreateDatabase}
|
||||
tags={connectionTags.map((tag) => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
@@ -6056,8 +6062,9 @@ const Sidebar: React.FC<{
|
||||
});
|
||||
|
||||
// Regular database connection menu
|
||||
const connectionCapabilities = getDataSourceCapabilities((node.dataRef as SavedConnection)?.config);
|
||||
return [
|
||||
{
|
||||
...(connectionCapabilities.supportsCreateDatabase ? [{
|
||||
key: 'new-db',
|
||||
label: '新建数据库',
|
||||
icon: <DatabaseOutlined />,
|
||||
@@ -6065,7 +6072,7 @@ const Sidebar: React.FC<{
|
||||
setTargetConnection(node);
|
||||
setIsCreateDbModalOpen(true);
|
||||
}
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
key: 'refresh',
|
||||
label: '刷新',
|
||||
@@ -6235,8 +6242,11 @@ const Sidebar: React.FC<{
|
||||
}
|
||||
];
|
||||
} else if (node.type === 'database') {
|
||||
const isStarRocks = getMetadataDialect(node.dataRef as SavedConnection) === 'starrocks';
|
||||
const supportsSchemaActions = isPostgresSchemaDialect(getMetadataDialect(node.dataRef as SavedConnection));
|
||||
const databaseConn = node.dataRef as SavedConnection;
|
||||
const dialect = getMetadataDialect(databaseConn);
|
||||
const capabilities = getDataSourceCapabilities(databaseConn?.config);
|
||||
const isStarRocks = dialect === 'starrocks';
|
||||
const supportsSchemaActions = isPostgresSchemaDialect(dialect);
|
||||
return [
|
||||
{
|
||||
key: 'new-table',
|
||||
@@ -6266,13 +6276,13 @@ const Sidebar: React.FC<{
|
||||
onClick: () => openCreateStarRocksExternalCatalog(node)
|
||||
},
|
||||
] : []),
|
||||
{
|
||||
...(capabilities.supportsRenameDatabase ? [{
|
||||
key: 'rename-db',
|
||||
label: '重命名数据库',
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'rename-db')
|
||||
},
|
||||
{
|
||||
}] : []),
|
||||
...(capabilities.supportsDropDatabase ? [{
|
||||
key: 'danger-zone',
|
||||
label: '危险操作',
|
||||
icon: <WarningOutlined />,
|
||||
@@ -6285,7 +6295,7 @@ const Sidebar: React.FC<{
|
||||
onClick: () => handleV2DatabaseContextMenuAction(node, 'drop-db')
|
||||
}
|
||||
]
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
key: 'refresh',
|
||||
label: '刷新',
|
||||
|
||||
@@ -304,12 +304,16 @@ export const V2DatabaseContextMenuView: React.FC<{
|
||||
dialect?: string;
|
||||
supportsSchemaActions?: boolean;
|
||||
supportsStarRocksActions?: boolean;
|
||||
supportsRenameDatabase?: boolean;
|
||||
supportsDropDatabase?: boolean;
|
||||
onAction?: (action: V2DatabaseContextMenuActionKey) => void;
|
||||
}> = ({
|
||||
dbName,
|
||||
dialect,
|
||||
supportsSchemaActions = false,
|
||||
supportsStarRocksActions = false,
|
||||
supportsRenameDatabase = true,
|
||||
supportsDropDatabase = true,
|
||||
onAction,
|
||||
}) => {
|
||||
const renderItems = (items: V2TableContextMenuItemConfig[]) => renderV2ContextMenuItems(
|
||||
@@ -346,7 +350,7 @@ export const V2DatabaseContextMenuView: React.FC<{
|
||||
|
||||
<div className="gn-v2-context-menu-section-title">维护</div>
|
||||
{renderItems([
|
||||
{ action: 'rename-db', icon: <EditOutlined />, title: '重命名数据库', kbd: 'F2' },
|
||||
...(supportsRenameDatabase ? [{ action: 'rename-db', icon: <EditOutlined />, title: '重命名数据库', kbd: 'F2' }] : []),
|
||||
{ action: 'refresh', icon: <ReloadOutlined />, title: '刷新对象树' },
|
||||
{ action: 'disconnect-db', icon: <DisconnectOutlined />, title: '关闭数据库' },
|
||||
])}
|
||||
@@ -358,7 +362,7 @@ export const V2DatabaseContextMenuView: React.FC<{
|
||||
])}
|
||||
|
||||
<div className="gn-v2-context-menu-divider" />
|
||||
{renderItems([
|
||||
{supportsDropDatabase && renderItems([
|
||||
{ action: 'drop-db', icon: <DeleteOutlined />, title: '删除数据库 · DROP', tone: 'danger', kbd: '⌫' },
|
||||
])}
|
||||
</div>
|
||||
@@ -431,6 +435,7 @@ export const V2ConnectionContextMenuView: React.FC<{
|
||||
hostSummary?: string;
|
||||
driverLabel?: string;
|
||||
isRedis?: boolean;
|
||||
supportsCreateDatabase?: boolean;
|
||||
tags?: V2ConnectionContextMenuTagItem[];
|
||||
onAction?: (action: V2ConnectionContextMenuActionKey) => void;
|
||||
}> = ({
|
||||
@@ -438,6 +443,7 @@ export const V2ConnectionContextMenuView: React.FC<{
|
||||
hostSummary,
|
||||
driverLabel,
|
||||
isRedis = false,
|
||||
supportsCreateDatabase = true,
|
||||
tags = [],
|
||||
onAction,
|
||||
}) => {
|
||||
@@ -466,7 +472,7 @@ export const V2ConnectionContextMenuView: React.FC<{
|
||||
{ action: 'new-command', icon: <ConsoleSqlOutlined />, title: '新建命令窗口', featured: true },
|
||||
{ action: 'open-monitor', icon: <DashboardOutlined />, title: 'Redis 实例监控' },
|
||||
]) : renderItems([
|
||||
{ action: 'new-db', icon: <DatabaseOutlined />, title: '新建数据库', kbd: '⌘N', featured: true },
|
||||
...(supportsCreateDatabase ? [{ action: 'new-db' as const, icon: <DatabaseOutlined />, title: '新建数据库', kbd: '⌘N', featured: true }] : []),
|
||||
{ action: 'refresh', icon: <ReloadOutlined />, title: '刷新连接', kbd: '⌘R' },
|
||||
{ action: 'new-query', icon: <ConsoleSqlOutlined />, title: '新建查询' },
|
||||
{ action: 'open-sql-file', icon: <FileAddOutlined />, title: '运行外部 SQL 文件' },
|
||||
|
||||
@@ -76,4 +76,28 @@ describe('dataSourceCapabilities', () => {
|
||||
supportsApproximateTableCount: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('hides database-level DDL actions for Dameng and Oracle-like datasources', () => {
|
||||
expect(getDataSourceCapabilities({ type: 'dameng' })).toMatchObject({
|
||||
type: 'dameng',
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
});
|
||||
expect(getDataSourceCapabilities({ type: 'oracle' })).toMatchObject({
|
||||
type: 'oracle',
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
});
|
||||
expect(getDataSourceCapabilities({
|
||||
type: 'oceanbase',
|
||||
oceanBaseProtocol: 'oracle',
|
||||
})).toMatchObject({
|
||||
type: 'oracle',
|
||||
supportsCreateDatabase: false,
|
||||
supportsRenameDatabase: false,
|
||||
supportsDropDatabase: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,12 +99,55 @@ export type DataSourceCapabilities = {
|
||||
supportsQueryEditor: boolean;
|
||||
supportsSqlQueryExport: boolean;
|
||||
supportsCopyInsert: boolean;
|
||||
supportsCreateDatabase: boolean;
|
||||
supportsRenameDatabase: boolean;
|
||||
supportsDropDatabase: boolean;
|
||||
forceReadOnlyQueryResult: boolean;
|
||||
preferManualTotalCount: boolean;
|
||||
supportsApproximateTableCount: boolean;
|
||||
supportsApproximateTotalPages: boolean;
|
||||
};
|
||||
|
||||
const CREATE_DATABASE_TYPES = new Set([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'starrocks',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'sqlserver',
|
||||
'tdengine',
|
||||
'clickhouse',
|
||||
]);
|
||||
|
||||
const RENAME_DATABASE_TYPES = new Set([
|
||||
'diros',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
]);
|
||||
|
||||
const DROP_DATABASE_TYPES = new Set([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'starrocks',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'tdengine',
|
||||
'clickhouse',
|
||||
]);
|
||||
|
||||
export const getDataSourceCapabilities = (config: ConnectionLike): DataSourceCapabilities => {
|
||||
const type = resolveDataSourceType(config);
|
||||
return {
|
||||
@@ -112,6 +155,9 @@ export const getDataSourceCapabilities = (config: ConnectionLike): DataSourceCap
|
||||
supportsQueryEditor: !QUERY_EDITOR_DISABLED_TYPES.has(type),
|
||||
supportsSqlQueryExport: SQL_QUERY_EXPORT_TYPES.has(type),
|
||||
supportsCopyInsert: COPY_INSERT_TYPES.has(type),
|
||||
supportsCreateDatabase: CREATE_DATABASE_TYPES.has(type),
|
||||
supportsRenameDatabase: RENAME_DATABASE_TYPES.has(type),
|
||||
supportsDropDatabase: DROP_DATABASE_TYPES.has(type),
|
||||
forceReadOnlyQueryResult: FORCE_READ_ONLY_QUERY_TYPES.has(type),
|
||||
preferManualTotalCount: MANUAL_TOTAL_COUNT_TYPES.has(type),
|
||||
supportsApproximateTableCount: APPROXIMATE_TABLE_COUNT_TYPES.has(type),
|
||||
|
||||
Reference in New Issue
Block a user