mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-27 00:38:47 +08:00
✨ feat(sidebar): 支持 Oracle 序列和存储包对象树
- 新增 Oracle/Dameng 序列与存储包元数据加载 - 同步对象树节点、V2 筛选、搜索、复制和拖拽支持 - 补充多语言文案和侧边栏回归测试
This commit is contained in:
@@ -642,7 +642,7 @@ describe('Sidebar locate toolbar', () => {
|
||||
const source = readSidebarSource();
|
||||
const commandSearchRunSource = source.slice(
|
||||
source.indexOf("if (node.type === 'table' || node.type === 'view' || node.type === 'materialized-view')"),
|
||||
source.indexOf("if (node.type === 'db-trigger' || node.type === 'db-event' || node.type === 'routine')"),
|
||||
source.indexOf("if (node.type === 'db-trigger' || node.type === 'db-event' || node.type === 'routine' || node.type === 'sequence' || node.type === 'package')"),
|
||||
);
|
||||
|
||||
expect(commandSearchRunSource).toContain("tabId: String(node.key || '')");
|
||||
@@ -824,12 +824,16 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.all'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.tables'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.views'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.sequences'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.routines'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.packages'");
|
||||
expect(objectKindSource).toContain("labelKey: 'sidebar.command_search.object_kind.events'");
|
||||
expect(objectKindSource).not.toContain("label: '全部'");
|
||||
expect(objectKindSource).not.toContain("label: '表'");
|
||||
expect(objectKindSource).not.toContain("label: '视图'");
|
||||
expect(objectKindSource).not.toContain("label: '序列'");
|
||||
expect(objectKindSource).not.toContain("label: '函数'");
|
||||
expect(objectKindSource).not.toContain("label: '存储包'");
|
||||
expect(objectKindSource).not.toContain("label: '事件'");
|
||||
|
||||
expect(searchScopeSource).toContain("labelKey: 'sidebar.command_search.scope.smart'");
|
||||
@@ -875,7 +879,9 @@ describe('Sidebar locate toolbar', () => {
|
||||
'sidebar.command_search.object_kind.all',
|
||||
'sidebar.command_search.object_kind.tables',
|
||||
'sidebar.command_search.object_kind.views',
|
||||
'sidebar.command_search.object_kind.sequences',
|
||||
'sidebar.command_search.object_kind.routines',
|
||||
'sidebar.command_search.object_kind.packages',
|
||||
'sidebar.command_search.object_kind.events',
|
||||
'sidebar.command_search.scope.smart',
|
||||
'sidebar.command_search.scope.object',
|
||||
@@ -913,7 +919,9 @@ describe('Sidebar locate toolbar', () => {
|
||||
'sidebar.command_search.object_kind.all': 'All',
|
||||
'sidebar.command_search.object_kind.tables': 'Tables',
|
||||
'sidebar.command_search.object_kind.views': 'Views',
|
||||
'sidebar.command_search.object_kind.sequences': 'Sequences',
|
||||
'sidebar.command_search.object_kind.routines': 'Routines',
|
||||
'sidebar.command_search.object_kind.packages': 'Packages',
|
||||
'sidebar.command_search.object_kind.events': 'Events',
|
||||
'table_overview.section.pinned': 'Pinned',
|
||||
'table_overview.section.all': 'All',
|
||||
@@ -927,8 +935,8 @@ describe('Sidebar locate toolbar', () => {
|
||||
t('sidebar.search.scope.host', undefined, 'zh-CN'),
|
||||
t('sidebar.search.scope.tag', undefined, 'zh-CN'),
|
||||
]);
|
||||
expect(buildV2ExplorerFilterOptions(translate).map((option) => option.label)).toEqual(['All', 'Tables', 'Views', 'Routines', 'Events']);
|
||||
expect(V2_UTILS_EXPLORER_FILTER_OPTIONS.map((option) => option.label)).toEqual(['全部', '表', '视图', '函数', '事件']);
|
||||
expect(buildV2ExplorerFilterOptions(translate).map((option) => option.label)).toEqual(['All', 'Tables', 'Views', 'Sequences', 'Routines', 'Packages', 'Events']);
|
||||
expect(V2_UTILS_EXPLORER_FILTER_OPTIONS.map((option) => option.label)).toEqual(['全部', '表', '视图', '序列', '函数', '存储包', '事件']);
|
||||
|
||||
const tableNodes = [
|
||||
{ title: 'orders', key: 'orders', type: 'table' as const, dataRef: { pinnedSidebarTable: true } },
|
||||
@@ -994,6 +1002,13 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(css).not.toContain('.gn-v2-active-connection-trigger:hover');
|
||||
});
|
||||
|
||||
it('keeps v2 explorer filter tabs on a single line when Oracle object filters are present', () => {
|
||||
const css = readV2ThemeCss();
|
||||
|
||||
expect(css).toMatch(/\.gn-v2-explorer-filter-tabs \{[^}]*flex-wrap: nowrap;[^}]*overflow-x: auto;[^}]*overflow-y: hidden;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-filter-tabs button \{[^}]*flex: 0 0 auto;[^}]*white-space: nowrap;/s);
|
||||
});
|
||||
|
||||
it('keeps v2 tree status dots circular while using virtual horizontal scroll for long labels', () => {
|
||||
const css = readV2ThemeCss();
|
||||
const source = readSidebarSource();
|
||||
@@ -1370,6 +1385,13 @@ describe('Sidebar locate toolbar', () => {
|
||||
dataRef: { groupKey: 'views' },
|
||||
children: [{ title: 'v_users', key: 'v_users', type: 'view' as const }],
|
||||
},
|
||||
{
|
||||
title: '序列',
|
||||
key: 'conn-main-sequences',
|
||||
type: 'object-group' as const,
|
||||
dataRef: { groupKey: 'sequences' },
|
||||
children: [{ title: 'seq_person_id', key: 'seq_person_id', type: 'sequence' as const }],
|
||||
},
|
||||
{
|
||||
title: '函数',
|
||||
key: 'conn-main-routines',
|
||||
@@ -1377,6 +1399,13 @@ describe('Sidebar locate toolbar', () => {
|
||||
dataRef: { groupKey: 'routines' },
|
||||
children: [{ title: 'calc_total', key: 'calc_total', type: 'routine' as const }],
|
||||
},
|
||||
{
|
||||
title: '存储包',
|
||||
key: 'conn-main-packages',
|
||||
type: 'object-group' as const,
|
||||
dataRef: { groupKey: 'packages' },
|
||||
children: [{ title: 'pkg_person', key: 'pkg_person', type: 'package' as const }],
|
||||
},
|
||||
{
|
||||
title: '事件',
|
||||
key: 'conn-main-events',
|
||||
@@ -1391,12 +1420,16 @@ describe('Sidebar locate toolbar', () => {
|
||||
'conn-main-queries',
|
||||
'conn-main-tables',
|
||||
'conn-main-views',
|
||||
'conn-main-sequences',
|
||||
'conn-main-routines',
|
||||
'conn-main-packages',
|
||||
'conn-main-events',
|
||||
]);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'tables')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-tables']);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'views')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-views']);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'sequences')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-sequences']);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'routines')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-routines']);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'packages')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-packages']);
|
||||
expect(filterV2ExplorerTreeByKind(tree, 'events')[0].children?.map((node: { key: string }) => node.key)).toEqual(['conn-main-events']);
|
||||
});
|
||||
|
||||
@@ -2609,7 +2642,9 @@ describe('Sidebar locate toolbar', () => {
|
||||
const objectGroupTitleCases = [
|
||||
['tables', 'sidebar.v2_table_group_menu.title', '表'],
|
||||
['views', 'sidebar.object_group.views', '视图'],
|
||||
['sequences', 'sidebar.object_group.sequences', '序列'],
|
||||
['routines', 'sidebar.object_group.routines', '函数'],
|
||||
['packages', 'sidebar.object_group.packages', '存储包'],
|
||||
['triggers', 'sidebar.object_group.triggers', '触发器'],
|
||||
['events', 'sidebar.object_group.events', '事件'],
|
||||
['materializedViews', 'sidebar.object_group.materialized_views', '物化视图'],
|
||||
@@ -2684,7 +2719,9 @@ describe('Sidebar locate toolbar', () => {
|
||||
[
|
||||
"if (groupKey === 'tables') return t('sidebar.v2_table_group_menu.title');",
|
||||
"if (groupKey === 'views') return t('sidebar.object_group.views');",
|
||||
"if (groupKey === 'sequences') return t('sidebar.object_group.sequences');",
|
||||
"if (groupKey === 'routines') return t('sidebar.object_group.routines');",
|
||||
"if (groupKey === 'packages') return t('sidebar.object_group.packages');",
|
||||
"if (groupKey === 'triggers') return t('sidebar.object_group.triggers');",
|
||||
"if (groupKey === 'events') return t('sidebar.object_group.events');",
|
||||
"if (groupKey === 'materializedViews') return t('sidebar.object_group.materialized_views');",
|
||||
|
||||
@@ -228,7 +228,9 @@ const V2_EXPLORER_FILTER_OPTIONS: Array<{ key: V2ExplorerFilter; labelKey: strin
|
||||
{ key: 'all', labelKey: 'sidebar.command_search.object_kind.all' },
|
||||
{ key: 'tables', labelKey: 'sidebar.command_search.object_kind.tables' },
|
||||
{ key: 'views', labelKey: 'sidebar.command_search.object_kind.views' },
|
||||
{ key: 'sequences', labelKey: 'sidebar.command_search.object_kind.sequences' },
|
||||
{ key: 'routines', labelKey: 'sidebar.command_search.object_kind.routines' },
|
||||
{ key: 'packages', labelKey: 'sidebar.command_search.object_kind.packages' },
|
||||
{ key: 'events', labelKey: 'sidebar.command_search.object_kind.events' },
|
||||
];
|
||||
|
||||
@@ -1483,7 +1485,7 @@ const Sidebar: React.FC<{
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: dataRef.dbName });
|
||||
} else if (type === 'jvm-mode' || type === 'jvm-resource' || type === 'jvm-diagnostic' || type === 'jvm-monitoring') {
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: '' });
|
||||
} else if (type === 'view' || type === 'materialized-view' || type === 'db-trigger' || type === 'db-event' || type === 'routine') {
|
||||
} else if (type === 'view' || type === 'materialized-view' || type === 'sequence' || type === 'package' || type === 'db-trigger' || type === 'db-event' || type === 'routine') {
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: dataRef.dbName });
|
||||
} else if (type === 'saved-query') {
|
||||
setActiveContext({ connectionId: dataRef.connectionId, dbName: dataRef.dbName });
|
||||
@@ -1545,7 +1547,7 @@ const Sidebar: React.FC<{
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: dataRef.dbName });
|
||||
} else if (type === 'jvm-mode' || type === 'jvm-resource' || type === 'jvm-diagnostic' || type === 'jvm-monitoring') {
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: '' });
|
||||
} else if (type === 'table' || type === 'view' || type === 'materialized-view' || type === 'db-trigger' || type === 'db-event' || type === 'routine') {
|
||||
} else if (type === 'table' || type === 'view' || type === 'materialized-view' || type === 'sequence' || type === 'package' || type === 'db-trigger' || type === 'db-event' || type === 'routine') {
|
||||
setActiveContext({ connectionId: nodeConnectionId || dataRef.id, dbName: dataRef.dbName });
|
||||
} else if (type === 'saved-query') setActiveContext({ connectionId: dataRef.connectionId, dbName: dataRef.dbName });
|
||||
else if (type === 'redis-db') setActiveContext({ connectionId: dataRef.id, dbName: `db${dataRef.redisDB}` });
|
||||
@@ -1633,6 +1635,8 @@ const Sidebar: React.FC<{
|
||||
routineType
|
||||
});
|
||||
return;
|
||||
} else if (node.type === 'sequence' || node.type === 'package') {
|
||||
return;
|
||||
} else if (node.type === 'jvm-mode') {
|
||||
const { providerMode, id } = node.dataRef;
|
||||
const conn = (connections.find((item) => item.id === id) || node.dataRef) as SavedConnection;
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
const source = [
|
||||
readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8'),
|
||||
readFileSync(new URL('./sidebar/useSidebarObjectActions.tsx', import.meta.url), 'utf8'),
|
||||
].join('\n');
|
||||
const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'de-DE', 'ru-RU'] as const;
|
||||
|
||||
const requiredKeys = [
|
||||
'sidebar.copy_object_name.label.table',
|
||||
'sidebar.copy_object_name.label.view',
|
||||
'sidebar.copy_object_name.label.materialized_view',
|
||||
'sidebar.copy_object_name.label.sequence',
|
||||
'sidebar.copy_object_name.label.package',
|
||||
'sidebar.copy_object_name.label.event',
|
||||
'sidebar.copy_object_name.empty',
|
||||
'sidebar.copy_object_name.copied',
|
||||
@@ -27,6 +32,8 @@ describe('Sidebar copy object name i18n', () => {
|
||||
expect(source).not.toContain('`复制${label}失败: `');
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.view')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.materialized_view')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.sequence')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.package')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.event')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.label.table')");
|
||||
expect(source).toContain("t('sidebar.copy_object_name.empty'");
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
const source = [
|
||||
readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8'),
|
||||
readFileSync(new URL('./sidebar/useSidebarTreeLoaders.tsx', import.meta.url), 'utf8'),
|
||||
].join('\n');
|
||||
|
||||
const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'de-DE', 'ru-RU'] as const;
|
||||
const requiredKeys = [
|
||||
@@ -9,7 +12,9 @@ const requiredKeys = [
|
||||
'sidebar.object_group.tables',
|
||||
'sidebar.object_group.views',
|
||||
'sidebar.object_group.materialized_views',
|
||||
'sidebar.object_group.sequences',
|
||||
'sidebar.object_group.routines',
|
||||
'sidebar.object_group.packages',
|
||||
'sidebar.object_group.triggers',
|
||||
'sidebar.object_group.events',
|
||||
];
|
||||
@@ -21,13 +26,17 @@ describe('Sidebar object group i18n', () => {
|
||||
"buildObjectGroup(schemaNodeKey, 'tables', '表'",
|
||||
"buildObjectGroup(schemaNodeKey, 'views', '视图'",
|
||||
"buildObjectGroup(schemaNodeKey, 'materializedViews', '物化视图'",
|
||||
"buildObjectGroup(schemaNodeKey, 'sequences', '序列'",
|
||||
"buildObjectGroup(schemaNodeKey, 'routines', '函数'",
|
||||
"buildObjectGroup(schemaNodeKey, 'packages', '存储包'",
|
||||
"buildObjectGroup(schemaNodeKey, 'triggers', '触发器'",
|
||||
"buildObjectGroup(schemaNodeKey, 'events', '事件'",
|
||||
"buildObjectGroup(key as string, 'tables', '表'",
|
||||
"buildObjectGroup(key as string, 'views', '视图'",
|
||||
"buildObjectGroup(key as string, 'materializedViews', '物化视图'",
|
||||
"buildObjectGroup(key as string, 'sequences', '序列'",
|
||||
"buildObjectGroup(key as string, 'routines', '函数'",
|
||||
"buildObjectGroup(key as string, 'packages', '存储包'",
|
||||
"buildObjectGroup(key as string, 'triggers', '触发器'",
|
||||
"buildObjectGroup(key as string, 'events', '事件'",
|
||||
].forEach((snippet) => {
|
||||
|
||||
@@ -55,9 +55,11 @@ export const renderSidebarV2TreeTitle = ({
|
||||
const isMono = node.type === 'table'
|
||||
|| node.type === 'view'
|
||||
|| node.type === 'materialized-view'
|
||||
|| node.type === 'sequence'
|
||||
|| node.type === 'db-trigger'
|
||||
|| node.type === 'db-event'
|
||||
|| node.type === 'routine'
|
||||
|| node.type === 'package'
|
||||
|| node.type === 'saved-query'
|
||||
|| node.type === 'external-sql-file';
|
||||
const titleClassName = [
|
||||
|
||||
@@ -18,7 +18,7 @@ export const V2_RAIL_UNGROUPED_CONNECTION_GROUP_ID = '__gonavi-v2-ungrouped-conn
|
||||
// === 共享类型 ===
|
||||
|
||||
/** V2 资源管理器过滤维度 */
|
||||
export type V2ExplorerFilter = 'all' | 'tables' | 'views' | 'routines' | 'events';
|
||||
export type V2ExplorerFilter = 'all' | 'tables' | 'views' | 'sequences' | 'routines' | 'packages' | 'events';
|
||||
|
||||
// === 纯函数 ===
|
||||
|
||||
@@ -94,9 +94,11 @@ export const isV2SidebarObjectNode = (
|
||||
return node?.type === 'table'
|
||||
|| node?.type === 'view'
|
||||
|| node?.type === 'materialized-view'
|
||||
|| node?.type === 'sequence'
|
||||
|| node?.type === 'db-trigger'
|
||||
|| node?.type === 'db-event'
|
||||
|| node?.type === 'routine';
|
||||
|| node?.type === 'routine'
|
||||
|| node?.type === 'package';
|
||||
};
|
||||
|
||||
// === 第二期:依赖 i18n 但不依赖 TreeNode 内部类型的工具函数 ===
|
||||
@@ -125,7 +127,9 @@ export const resolveV2ObjectGroupTitle = (
|
||||
const groupKey = String(node?.dataRef?.groupKey || '');
|
||||
if (groupKey === 'tables') return t('sidebar.v2_table_group_menu.title');
|
||||
if (groupKey === 'views') return t('sidebar.object_group.views');
|
||||
if (groupKey === 'sequences') return t('sidebar.object_group.sequences');
|
||||
if (groupKey === 'routines') return t('sidebar.object_group.routines');
|
||||
if (groupKey === 'packages') return t('sidebar.object_group.packages');
|
||||
if (groupKey === 'triggers') return t('sidebar.object_group.triggers');
|
||||
if (groupKey === 'events') return t('sidebar.object_group.events');
|
||||
if (groupKey === 'materializedViews') return t('sidebar.object_group.materialized_views');
|
||||
@@ -139,7 +143,7 @@ export const resolveV2ObjectGroupTitle = (
|
||||
export const resolveSidebarTableNameForCopy = (
|
||||
node: Pick<SidebarNodeLike, 'title' | 'dataRef'> | null | undefined,
|
||||
): string => {
|
||||
return String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.eventName || node?.title || '').trim();
|
||||
return String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.sequenceName || node?.dataRef?.packageName || node?.dataRef?.eventName || node?.title || '').trim();
|
||||
};
|
||||
|
||||
// === 命令搜索相关类型与解析(V2 Command Search)===
|
||||
|
||||
@@ -5,7 +5,14 @@ vi.mock("../../../wailsjs/go/app/App", () => ({
|
||||
}));
|
||||
|
||||
import { DBQuery } from "../../../wailsjs/go/app/App";
|
||||
import { buildSchemasMetadataQuerySpecs, loadViews } from "./sidebarMetadataLoaders";
|
||||
import {
|
||||
buildPackagesMetadataQuerySpecs,
|
||||
buildSchemasMetadataQuerySpecs,
|
||||
buildSequencesMetadataQuerySpecs,
|
||||
loadPackages,
|
||||
loadSequences,
|
||||
loadViews,
|
||||
} from "./sidebarMetadataLoaders";
|
||||
|
||||
const mockedDBQuery = vi.mocked(DBQuery);
|
||||
|
||||
@@ -67,3 +74,61 @@ describe("buildSchemasMetadataQuerySpecs", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Oracle object metadata loaders", () => {
|
||||
it("builds owner-scoped sequence and package queries for Oracle", () => {
|
||||
expect(buildSequencesMetadataQuerySpecs("oracle", "MYCIMLED").map((spec) => spec.sql)).toEqual([
|
||||
"SELECT SEQUENCE_OWNER AS schema_name, SEQUENCE_NAME AS sequence_name FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = 'MYCIMLED' ORDER BY SEQUENCE_NAME",
|
||||
]);
|
||||
expect(buildPackagesMetadataQuerySpecs("oracle", "MYCIMLED").map((spec) => spec.sql)).toEqual([
|
||||
"SELECT OWNER AS schema_name, OBJECT_NAME AS package_name FROM ALL_OBJECTS WHERE OWNER = 'MYCIMLED' AND OBJECT_TYPE = 'PACKAGE' ORDER BY OBJECT_NAME",
|
||||
]);
|
||||
});
|
||||
|
||||
it("loads and deduplicates Oracle sequences and packages", async () => {
|
||||
mockedDBQuery.mockImplementation(async (_config: unknown, _dbName: string, sql: string) => {
|
||||
if (sql.includes("ALL_SEQUENCES")) {
|
||||
return {
|
||||
success: true,
|
||||
message: "",
|
||||
data: [
|
||||
{ schema_name: "MYCIMLED", sequence_name: "SEQ_PERSON_ID" },
|
||||
{ SEQUENCE_OWNER: "MYCIMLED", SEQUENCE_NAME: "SEQ_PERSON_ID" },
|
||||
],
|
||||
};
|
||||
}
|
||||
if (sql.includes("ALL_OBJECTS") && sql.includes("PACKAGE")) {
|
||||
return {
|
||||
success: true,
|
||||
message: "",
|
||||
data: [
|
||||
{ schema_name: "MYCIMLED", package_name: "PKG_PERSON" },
|
||||
{ OWNER: "MYCIMLED", OBJECT_NAME: "PKG_PERSON" },
|
||||
],
|
||||
};
|
||||
}
|
||||
return { success: false, message: "", data: [] };
|
||||
});
|
||||
|
||||
await expect(loadSequences({ config: { type: "oracle" } }, "MYCIMLED")).resolves.toEqual({
|
||||
supported: true,
|
||||
sequences: [
|
||||
{
|
||||
displayName: "MYCIMLED.SEQ_PERSON_ID",
|
||||
schemaName: "MYCIMLED",
|
||||
sequenceName: "MYCIMLED.SEQ_PERSON_ID",
|
||||
},
|
||||
],
|
||||
});
|
||||
await expect(loadPackages({ config: { type: "oracle" } }, "MYCIMLED")).resolves.toEqual({
|
||||
supported: true,
|
||||
packages: [
|
||||
{
|
||||
displayName: "MYCIMLED.PKG_PERSON",
|
||||
packageName: "MYCIMLED.PKG_PERSON",
|
||||
schemaName: "MYCIMLED",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -604,6 +604,46 @@ const buildFunctionsMetadataQuerySpecs = (
|
||||
}
|
||||
};
|
||||
|
||||
const buildSequencesMetadataQuerySpecs = (
|
||||
dialect: string,
|
||||
dbName: string,
|
||||
): MetadataQuerySpec[] => {
|
||||
const safeDbName = escapeSQLLiteral(dbName);
|
||||
switch (dialect) {
|
||||
case "oracle":
|
||||
case "dm":
|
||||
return normalizeMetadataQuerySpecs([
|
||||
{
|
||||
sql: safeDbName
|
||||
? `SELECT SEQUENCE_OWNER AS schema_name, SEQUENCE_NAME AS sequence_name FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = '${safeDbName.toUpperCase()}' ORDER BY SEQUENCE_NAME`
|
||||
: `SELECT SEQUENCE_NAME AS sequence_name FROM USER_SEQUENCES ORDER BY SEQUENCE_NAME`,
|
||||
},
|
||||
]);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const buildPackagesMetadataQuerySpecs = (
|
||||
dialect: string,
|
||||
dbName: string,
|
||||
): MetadataQuerySpec[] => {
|
||||
const safeDbName = escapeSQLLiteral(dbName);
|
||||
switch (dialect) {
|
||||
case "oracle":
|
||||
case "dm":
|
||||
return normalizeMetadataQuerySpecs([
|
||||
{
|
||||
sql: safeDbName
|
||||
? `SELECT OWNER AS schema_name, OBJECT_NAME AS package_name FROM ALL_OBJECTS WHERE OWNER = '${safeDbName.toUpperCase()}' AND OBJECT_TYPE = 'PACKAGE' ORDER BY OBJECT_NAME`
|
||||
: `SELECT OBJECT_NAME AS package_name FROM USER_OBJECTS WHERE OBJECT_TYPE = 'PACKAGE' ORDER BY OBJECT_NAME`,
|
||||
},
|
||||
]);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const buildEventsMetadataQuerySpecs = (
|
||||
dialect: string,
|
||||
dbName: string,
|
||||
@@ -977,6 +1017,127 @@ const loadFunctions = async (
|
||||
return { routines, supported: hasSuccessfulQuery };
|
||||
};
|
||||
|
||||
const loadSequences = async (
|
||||
conn: any,
|
||||
dbName: string,
|
||||
): Promise<{
|
||||
sequences: Array<{
|
||||
displayName: string;
|
||||
sequenceName: string;
|
||||
schemaName: string;
|
||||
}>;
|
||||
supported: boolean;
|
||||
}> => {
|
||||
const dialect = getMetadataDialect(conn as SavedConnection);
|
||||
const querySpecs = buildSequencesMetadataQuerySpecs(dialect, dbName);
|
||||
const { results, hasSuccessfulQuery } = await queryMetadataRowsBySpecs(
|
||||
conn,
|
||||
dbName,
|
||||
querySpecs,
|
||||
);
|
||||
const seen = new Set<string>();
|
||||
const sequences: Array<{
|
||||
displayName: string;
|
||||
sequenceName: string;
|
||||
schemaName: string;
|
||||
}> = [];
|
||||
|
||||
results.forEach((queryResult) => {
|
||||
queryResult.rows.forEach((row) => {
|
||||
const rawSequenceName =
|
||||
getCaseInsensitiveValue(row, [
|
||||
"sequence_name",
|
||||
"sequencename",
|
||||
"object_name",
|
||||
"name",
|
||||
]) || getFirstRowValue(row);
|
||||
if (!rawSequenceName) return;
|
||||
|
||||
const sequenceParts = splitQualifiedName(rawSequenceName);
|
||||
const schemaName = (
|
||||
getCaseInsensitiveValue(row, [
|
||||
"schema_name",
|
||||
"sequence_owner",
|
||||
"owner",
|
||||
]) ||
|
||||
sequenceParts.schemaName ||
|
||||
""
|
||||
).trim();
|
||||
const objectName = (sequenceParts.objectName || rawSequenceName).trim();
|
||||
const fullName = buildQualifiedName(schemaName, objectName);
|
||||
const uniqueKey = `${schemaName.toLowerCase()}@@${objectName.toLowerCase()}`;
|
||||
if (!fullName || seen.has(uniqueKey)) return;
|
||||
seen.add(uniqueKey);
|
||||
sequences.push({
|
||||
displayName: fullName,
|
||||
sequenceName: fullName,
|
||||
schemaName,
|
||||
});
|
||||
});
|
||||
});
|
||||
return { sequences, supported: hasSuccessfulQuery };
|
||||
};
|
||||
|
||||
const loadPackages = async (
|
||||
conn: any,
|
||||
dbName: string,
|
||||
): Promise<{
|
||||
packages: Array<{
|
||||
displayName: string;
|
||||
packageName: string;
|
||||
schemaName: string;
|
||||
}>;
|
||||
supported: boolean;
|
||||
}> => {
|
||||
const dialect = getMetadataDialect(conn as SavedConnection);
|
||||
const querySpecs = buildPackagesMetadataQuerySpecs(dialect, dbName);
|
||||
const { results, hasSuccessfulQuery } = await queryMetadataRowsBySpecs(
|
||||
conn,
|
||||
dbName,
|
||||
querySpecs,
|
||||
);
|
||||
const seen = new Set<string>();
|
||||
const packages: Array<{
|
||||
displayName: string;
|
||||
packageName: string;
|
||||
schemaName: string;
|
||||
}> = [];
|
||||
|
||||
results.forEach((queryResult) => {
|
||||
queryResult.rows.forEach((row) => {
|
||||
const rawPackageName =
|
||||
getCaseInsensitiveValue(row, [
|
||||
"package_name",
|
||||
"packagename",
|
||||
"object_name",
|
||||
"name",
|
||||
]) || getFirstRowValue(row);
|
||||
if (!rawPackageName) return;
|
||||
|
||||
const packageParts = splitQualifiedName(rawPackageName);
|
||||
const schemaName = (
|
||||
getCaseInsensitiveValue(row, [
|
||||
"schema_name",
|
||||
"owner",
|
||||
]) ||
|
||||
packageParts.schemaName ||
|
||||
""
|
||||
).trim();
|
||||
const objectName = (packageParts.objectName || rawPackageName).trim();
|
||||
const fullName = buildQualifiedName(schemaName, objectName);
|
||||
const uniqueKey = `${schemaName.toLowerCase()}@@${objectName.toLowerCase()}`;
|
||||
if (!fullName || seen.has(uniqueKey)) return;
|
||||
seen.add(uniqueKey);
|
||||
packages.push({
|
||||
displayName: fullName,
|
||||
packageName: fullName,
|
||||
schemaName,
|
||||
});
|
||||
});
|
||||
});
|
||||
return { packages, supported: hasSuccessfulQuery };
|
||||
};
|
||||
|
||||
const loadDatabaseEvents = async (
|
||||
conn: any,
|
||||
dbName: string,
|
||||
@@ -1084,8 +1245,10 @@ export {
|
||||
buildDuckDBMacroDDL,
|
||||
buildEventsMetadataQuerySpecs,
|
||||
buildFunctionsMetadataQuerySpecs,
|
||||
buildPackagesMetadataQuerySpecs,
|
||||
buildQualifiedName,
|
||||
buildSchemasMetadataQuerySpecs,
|
||||
buildSequencesMetadataQuerySpecs,
|
||||
buildSidebarObjectKeyName,
|
||||
buildSidebarTableStatusSQL,
|
||||
buildTriggersMetadataQuerySpecs,
|
||||
@@ -1102,7 +1265,9 @@ export {
|
||||
loadDatabaseEvents,
|
||||
loadDatabaseTriggers,
|
||||
loadFunctions,
|
||||
loadPackages,
|
||||
loadSchemas,
|
||||
loadSequences,
|
||||
loadStarRocksMaterializedViews,
|
||||
loadViews,
|
||||
normalizeMetadataQuerySpecs,
|
||||
|
||||
@@ -105,12 +105,14 @@ export const useSidebarCommandSearchRunner = ({
|
||||
onDoubleClick(null, node);
|
||||
return;
|
||||
}
|
||||
if (node.type === 'db-trigger' || node.type === 'db-event' || node.type === 'routine') {
|
||||
if (node.type === 'db-trigger' || node.type === 'db-event' || node.type === 'routine' || node.type === 'sequence' || node.type === 'package') {
|
||||
setActiveContext({ connectionId: dataRef.id, dbName: dataRef.dbName });
|
||||
setSelectedKeys([node.key]);
|
||||
selectedNodesRef.current = [node];
|
||||
scrollSidebarTreeToKey(node.key);
|
||||
onDoubleClick(null, node);
|
||||
if (node.type !== 'sequence' && node.type !== 'package') {
|
||||
onDoubleClick(null, node);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
activeContext,
|
||||
|
||||
@@ -108,6 +108,8 @@ type UseSidebarObjectActionsArgs = {
|
||||
const resolveCopyObjectNameLabel = (node: any): string => {
|
||||
if (node?.type === 'view') return t('sidebar.copy_object_name.label.view');
|
||||
if (node?.type === 'materialized-view') return t('sidebar.copy_object_name.label.materialized_view');
|
||||
if (node?.type === 'sequence') return t('sidebar.copy_object_name.label.sequence');
|
||||
if (node?.type === 'package') return t('sidebar.copy_object_name.label.package');
|
||||
if (node?.type === 'db-event') return t('sidebar.copy_object_name.label.event');
|
||||
return t('sidebar.copy_object_name.label.table');
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DatabaseOutlined,
|
||||
EyeOutlined,
|
||||
FilterOutlined,
|
||||
KeyOutlined,
|
||||
PlusOutlined,
|
||||
RobotOutlined,
|
||||
TableOutlined,
|
||||
@@ -398,12 +399,14 @@ export const useSidebarSearchModel = ({
|
||||
node.type === 'table'
|
||||
|| node.type === 'view'
|
||||
|| node.type === 'materialized-view'
|
||||
|| node.type === 'sequence'
|
||||
|| node.type === 'db-trigger'
|
||||
|| node.type === 'db-event'
|
||||
|| node.type === 'routine'
|
||||
|| node.type === 'package'
|
||||
) {
|
||||
const conn = connectionById.get(String(dataRef.id || ''));
|
||||
const objectName = String(dataRef.tableName || dataRef.viewName || dataRef.triggerName || dataRef.eventName || dataRef.routineName || node.title || '').trim();
|
||||
const objectName = String(dataRef.tableName || dataRef.viewName || dataRef.sequenceName || dataRef.triggerName || dataRef.eventName || dataRef.routineName || dataRef.packageName || node.title || '').trim();
|
||||
const displayName = String(node.title || extractObjectName(objectName) || objectName).trim();
|
||||
result.push({
|
||||
key: `node-${node.key}`,
|
||||
@@ -412,7 +415,9 @@ export const useSidebarSearchModel = ({
|
||||
meta: [conn?.name || dataRef.id, dataRef.dbName].filter(Boolean).join(' · '),
|
||||
icon: node.type === 'table'
|
||||
? <TableOutlined />
|
||||
: (node.type === 'db-event' ? <ClockCircleOutlined /> : (node.type === 'routine' ? <CodeOutlined /> : <EyeOutlined />)),
|
||||
: (node.type === 'sequence'
|
||||
? <KeyOutlined />
|
||||
: (node.type === 'db-event' ? <ClockCircleOutlined /> : ((node.type === 'routine' || node.type === 'package') ? <CodeOutlined /> : <EyeOutlined />))),
|
||||
node,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ export const useSidebarTitleRender = ({
|
||||
const displayTitle = String(node.title ?? '');
|
||||
const dragText = resolveSidebarObjectDragText(node);
|
||||
let hoverTitle = displayTitle;
|
||||
if (node.type === 'table' || node.type === 'view' || node.type === 'materialized-view' || node.type === 'db-event') {
|
||||
const rawTableName = String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.eventName || '').trim();
|
||||
if (node.type === 'table' || node.type === 'view' || node.type === 'materialized-view' || node.type === 'sequence' || node.type === 'package' || node.type === 'db-event') {
|
||||
const rawTableName = String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.sequenceName || node?.dataRef?.packageName || node?.dataRef?.eventName || '').trim();
|
||||
const conn = node?.dataRef as SavedConnection | undefined;
|
||||
if (rawTableName && shouldHideSchemaPrefix(conn)) {
|
||||
if (splitQualifiedName(rawTableName).schemaName) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
FolderOpenOutlined,
|
||||
FunctionOutlined,
|
||||
HddOutlined,
|
||||
KeyOutlined,
|
||||
TableOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
@@ -32,7 +33,9 @@ import {
|
||||
loadDatabaseEvents,
|
||||
loadDatabaseTriggers,
|
||||
loadFunctions,
|
||||
loadPackages,
|
||||
loadSchemas,
|
||||
loadSequences,
|
||||
loadStarRocksMaterializedViews,
|
||||
loadViews,
|
||||
parseMetadataRowCount,
|
||||
@@ -505,18 +508,22 @@ export const useSidebarTreeLoaders = ({
|
||||
};
|
||||
});
|
||||
|
||||
const [schemasResult, viewsResult, materializedViewsResult, triggersResult, routinesResult, eventsResult] = await Promise.all([
|
||||
const [schemasResult, viewsResult, materializedViewsResult, triggersResult, routinesResult, sequencesResult, packagesResult, eventsResult] = await Promise.all([
|
||||
loadSchemas(conn, conn.dbName),
|
||||
loadViews(conn, conn.dbName),
|
||||
loadStarRocksMaterializedViews(conn, conn.dbName),
|
||||
loadDatabaseTriggers(conn, conn.dbName),
|
||||
loadFunctions(conn, conn.dbName),
|
||||
loadSequences(conn, conn.dbName),
|
||||
loadPackages(conn, conn.dbName),
|
||||
loadDatabaseEvents(conn, conn.dbName),
|
||||
]);
|
||||
const viewRows: SidebarViewMetadataEntry[] = Array.isArray(viewsResult.views) ? viewsResult.views : [];
|
||||
const materializedViewRows: SidebarViewMetadataEntry[] = Array.isArray(materializedViewsResult.views) ? materializedViewsResult.views : [];
|
||||
const triggerRows: any[] = Array.isArray(triggersResult.triggers) ? triggersResult.triggers : [];
|
||||
const routineRows: any[] = Array.isArray(routinesResult.routines) ? routinesResult.routines : [];
|
||||
const sequenceRows: any[] = Array.isArray(sequencesResult.sequences) ? sequencesResult.sequences : [];
|
||||
const packageRows: any[] = Array.isArray(packagesResult.packages) ? packagesResult.packages : [];
|
||||
const eventRows: any[] = Array.isArray(eventsResult.events) ? eventsResult.events : [];
|
||||
const schemaRows: string[] = Array.isArray(schemasResult.schemas) ? schemasResult.schemas : [];
|
||||
|
||||
@@ -578,6 +585,24 @@ export const useSidebarTreeLoaders = ({
|
||||
};
|
||||
});
|
||||
|
||||
const sequenceEntries = sequenceRows.map((sequence: any) => {
|
||||
const parsed = splitQualifiedName(sequence.sequenceName);
|
||||
return {
|
||||
...sequence,
|
||||
schemaName: sequence.schemaName || parsed.schemaName,
|
||||
displayName: parsed.objectName || sequence.sequenceName,
|
||||
};
|
||||
});
|
||||
|
||||
const packageEntries = packageRows.map((packageEntry: any) => {
|
||||
const parsed = splitQualifiedName(packageEntry.packageName);
|
||||
return {
|
||||
...packageEntry,
|
||||
schemaName: packageEntry.schemaName || parsed.schemaName,
|
||||
displayName: parsed.objectName || packageEntry.packageName,
|
||||
};
|
||||
});
|
||||
|
||||
const eventEntries = eventRows.map((event: any) => ({
|
||||
...event,
|
||||
schemaName: String(event.schemaName || conn.dbName || '').trim(),
|
||||
@@ -627,6 +652,10 @@ export const useSidebarTreeLoaders = ({
|
||||
// Sort routines by display name (case-insensitive)
|
||||
routineEntries.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()));
|
||||
|
||||
sequenceEntries.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()));
|
||||
|
||||
packageEntries.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()));
|
||||
|
||||
eventEntries.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()));
|
||||
|
||||
const buildTableNode = (entry: { tableName: string; schemaName: string; displayName: string; rowCount?: number }): TreeNode => {
|
||||
@@ -695,6 +724,30 @@ export const useSidebarTreeLoaders = ({
|
||||
isLeaf: true,
|
||||
});
|
||||
|
||||
const buildSequenceNode = (entry: { sequenceName: string; schemaName: string; displayName: string }): TreeNode => {
|
||||
const keyName = buildSidebarObjectKeyName(conn.dbName, entry.schemaName, entry.sequenceName);
|
||||
return {
|
||||
title: entry.displayName,
|
||||
key: `${conn.id}-${conn.dbName}-sequence-${keyName}`,
|
||||
icon: <KeyOutlined />,
|
||||
type: 'sequence',
|
||||
dataRef: { ...conn, sequenceName: entry.sequenceName, schemaName: entry.schemaName },
|
||||
isLeaf: true,
|
||||
};
|
||||
};
|
||||
|
||||
const buildPackageNode = (entry: { packageName: string; schemaName: string; displayName: string }): TreeNode => {
|
||||
const keyName = buildSidebarObjectKeyName(conn.dbName, entry.schemaName, entry.packageName);
|
||||
return {
|
||||
title: entry.displayName,
|
||||
key: `${conn.id}-${conn.dbName}-package-${keyName}`,
|
||||
icon: <CodeOutlined />,
|
||||
type: 'package',
|
||||
dataRef: { ...conn, packageName: entry.packageName, schemaName: entry.schemaName },
|
||||
isLeaf: true,
|
||||
};
|
||||
};
|
||||
|
||||
const buildEventNode = (entry: { eventName: string; schemaName: string; displayName: string; eventType?: string; status?: string }): TreeNode => ({
|
||||
title: entry.displayName,
|
||||
key: `${conn.id}-${conn.dbName}-event-${entry.schemaName}-${entry.eventName}`,
|
||||
@@ -735,6 +788,8 @@ export const useSidebarTreeLoaders = ({
|
||||
views: TreeNode[];
|
||||
materializedViews: TreeNode[];
|
||||
routines: TreeNode[];
|
||||
sequences: TreeNode[];
|
||||
packages: TreeNode[];
|
||||
triggers: TreeNode[];
|
||||
events: TreeNode[];
|
||||
};
|
||||
@@ -751,6 +806,8 @@ export const useSidebarTreeLoaders = ({
|
||||
views: [],
|
||||
materializedViews: [],
|
||||
routines: [],
|
||||
sequences: [],
|
||||
packages: [],
|
||||
triggers: [],
|
||||
events: [],
|
||||
};
|
||||
@@ -764,12 +821,15 @@ export const useSidebarTreeLoaders = ({
|
||||
viewEntries.forEach((entry) => getSchemaBucket(entry.schemaName).views.push(buildViewNode(entry)));
|
||||
materializedViewEntries.forEach((entry) => getSchemaBucket(entry.schemaName).materializedViews.push(buildMaterializedViewNode(entry)));
|
||||
routineEntries.forEach((entry) => getSchemaBucket(entry.schemaName).routines.push(buildRoutineNode(entry)));
|
||||
sequenceEntries.forEach((entry) => getSchemaBucket(entry.schemaName).sequences.push(buildSequenceNode(entry)));
|
||||
packageEntries.forEach((entry) => getSchemaBucket(entry.schemaName).packages.push(buildPackageNode(entry)));
|
||||
triggerEntries.forEach((entry) => getSchemaBucket(entry.schemaName).triggers.push(buildTriggerNode(entry)));
|
||||
eventEntries.forEach((entry) => getSchemaBucket(entry.schemaName).events.push(buildEventNode(entry)));
|
||||
|
||||
const dialect = getMetadataDialect(conn as SavedConnection);
|
||||
const isOracleLike = (dialect === 'oracle' || dialect === 'dm');
|
||||
const includeMaterializedViews = dialect === 'starrocks';
|
||||
const includeOracleObjects = isOracleLike;
|
||||
const includeEvents = supportsDatabaseEvents(conn as SavedConnection);
|
||||
|
||||
const schemaNodes: TreeNode[] = Array.from(schemaMap.values())
|
||||
@@ -787,7 +847,9 @@ export const useSidebarTreeLoaders = ({
|
||||
buildObjectGroup(schemaNodeKey, 'tables', t('sidebar.object_group.tables'), <TableOutlined />, bucket.tables, { schemaName: bucket.schemaName }),
|
||||
buildObjectGroup(schemaNodeKey, 'views', t('sidebar.object_group.views'), <EyeOutlined />, bucket.views, { schemaName: bucket.schemaName }),
|
||||
...(includeMaterializedViews ? [buildObjectGroup(schemaNodeKey, 'materializedViews', t('sidebar.object_group.materialized_views'), <ThunderboltOutlined />, bucket.materializedViews, { schemaName: bucket.schemaName })] : []),
|
||||
...(includeOracleObjects ? [buildObjectGroup(schemaNodeKey, 'sequences', t('sidebar.object_group.sequences'), <KeyOutlined />, bucket.sequences, { schemaName: bucket.schemaName })] : []),
|
||||
buildObjectGroup(schemaNodeKey, 'routines', t('sidebar.object_group.routines'), <CodeOutlined />, bucket.routines, { schemaName: bucket.schemaName }),
|
||||
...(includeOracleObjects ? [buildObjectGroup(schemaNodeKey, 'packages', t('sidebar.object_group.packages'), <CodeOutlined />, bucket.packages, { schemaName: bucket.schemaName })] : []),
|
||||
buildObjectGroup(schemaNodeKey, 'triggers', t('sidebar.object_group.triggers'), <FunctionOutlined />, bucket.triggers, { schemaName: bucket.schemaName }),
|
||||
...(includeEvents ? [buildObjectGroup(schemaNodeKey, 'events', t('sidebar.object_group.events'), <ClockCircleOutlined />, bucket.events, { schemaName: bucket.schemaName })] : []),
|
||||
];
|
||||
@@ -805,13 +867,17 @@ export const useSidebarTreeLoaders = ({
|
||||
|
||||
replaceTreeNodeChildren(key, [queriesNode, ...schemaNodes]);
|
||||
} else {
|
||||
const includeMaterializedViews = getMetadataDialect(conn as SavedConnection) === 'starrocks';
|
||||
const dialect = getMetadataDialect(conn as SavedConnection);
|
||||
const includeMaterializedViews = dialect === 'starrocks';
|
||||
const includeOracleObjects = dialect === 'oracle' || dialect === 'dm';
|
||||
const includeEvents = supportsDatabaseEvents(conn as SavedConnection);
|
||||
const groupedNodes: TreeNode[] = [
|
||||
buildObjectGroup(key as string, 'tables', t('sidebar.object_group.tables'), <TableOutlined />, sortedTableEntries.map(buildTableNode)),
|
||||
buildObjectGroup(key as string, 'views', t('sidebar.object_group.views'), <EyeOutlined />, viewEntries.map(buildViewNode)),
|
||||
...(includeMaterializedViews ? [buildObjectGroup(key as string, 'materializedViews', t('sidebar.object_group.materialized_views'), <ThunderboltOutlined />, materializedViewEntries.map(buildMaterializedViewNode))] : []),
|
||||
...(includeOracleObjects ? [buildObjectGroup(key as string, 'sequences', t('sidebar.object_group.sequences'), <KeyOutlined />, sequenceEntries.map(buildSequenceNode))] : []),
|
||||
buildObjectGroup(key as string, 'routines', t('sidebar.object_group.routines'), <CodeOutlined />, routineEntries.map(buildRoutineNode)),
|
||||
...(includeOracleObjects ? [buildObjectGroup(key as string, 'packages', t('sidebar.object_group.packages'), <CodeOutlined />, packageEntries.map(buildPackageNode))] : []),
|
||||
buildObjectGroup(key as string, 'triggers', t('sidebar.object_group.triggers'), <FunctionOutlined />, triggerEntries.map(buildTriggerNode)),
|
||||
...(includeEvents ? [buildObjectGroup(key as string, 'events', t('sidebar.object_group.events'), <ClockCircleOutlined />, eventEntries.map(buildEventNode))] : []),
|
||||
];
|
||||
|
||||
@@ -59,5 +59,15 @@ describe('sidebarCoreUtils', () => {
|
||||
title: 'trg_users_audit',
|
||||
dataRef: {},
|
||||
})).toBe('trg_users_audit');
|
||||
expect(resolveSidebarObjectDragText({
|
||||
type: 'sequence',
|
||||
title: 'SEQ_PERSON_ID',
|
||||
dataRef: { sequenceName: 'MYCIMLED.SEQ_PERSON_ID' },
|
||||
})).toBe('MYCIMLED.SEQ_PERSON_ID');
|
||||
expect(resolveSidebarObjectDragText({
|
||||
type: 'package',
|
||||
title: 'PKG_PERSON',
|
||||
dataRef: { packageName: 'MYCIMLED.PKG_PERSON' },
|
||||
})).toBe('MYCIMLED.PKG_PERSON');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,9 +58,11 @@ export const isV2SidebarObjectNode = (node: Pick<SidebarObjectNodeLike, 'type'>
|
||||
return node?.type === 'table'
|
||||
|| node?.type === 'view'
|
||||
|| node?.type === 'materialized-view'
|
||||
|| node?.type === 'sequence'
|
||||
|| node?.type === 'db-trigger'
|
||||
|| node?.type === 'db-event'
|
||||
|| node?.type === 'routine';
|
||||
|| node?.type === 'routine'
|
||||
|| node?.type === 'package';
|
||||
};
|
||||
|
||||
export const resolveSidebarObjectDragText = (
|
||||
@@ -69,8 +71,10 @@ export const resolveSidebarObjectDragText = (
|
||||
const dataRef = node?.dataRef || {};
|
||||
if (node?.type === 'table') return String(dataRef.tableName || node?.title || '').trim();
|
||||
if (node?.type === 'view' || node?.type === 'materialized-view') return String(dataRef.viewName || dataRef.tableName || node?.title || '').trim();
|
||||
if (node?.type === 'sequence') return String(dataRef.sequenceName || node?.title || '').trim();
|
||||
if (node?.type === 'db-trigger') return String(dataRef.triggerName || node?.title || '').trim();
|
||||
if (node?.type === 'routine') return String(dataRef.routineName || node?.title || '').trim();
|
||||
if (node?.type === 'package') return String(dataRef.packageName || node?.title || '').trim();
|
||||
if (node?.type === 'db-event') return String(dataRef.eventName || node?.title || '').trim();
|
||||
return '';
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ export type SidebarTreeNodeType =
|
||||
| 'db-trigger'
|
||||
| 'db-event'
|
||||
| 'routine'
|
||||
| 'sequence'
|
||||
| 'package'
|
||||
| 'object-group'
|
||||
| 'v2-table-section'
|
||||
| 'queries-folder'
|
||||
@@ -76,7 +78,7 @@ export const shouldLoadSidebarNodeOnExpand = (
|
||||
export const resolveSidebarTableNameForCopy = (
|
||||
node: Pick<SidebarTreeNode, 'title' | 'dataRef'> | null | undefined,
|
||||
): string => {
|
||||
return String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.eventName || node?.title || '').trim();
|
||||
return String(node?.dataRef?.tableName || node?.dataRef?.viewName || node?.dataRef?.sequenceName || node?.dataRef?.packageName || node?.dataRef?.eventName || node?.title || '').trim();
|
||||
};
|
||||
|
||||
type SidebarTableSortPreference = 'name' | 'frequency';
|
||||
@@ -294,7 +296,7 @@ export const getV2RailConnectionGroupBadgeText = (name: unknown, fallback = t('c
|
||||
return trimmed.slice(0, 2);
|
||||
};
|
||||
|
||||
export type V2ExplorerFilter = 'all' | 'tables' | 'views' | 'routines' | 'events';
|
||||
export type V2ExplorerFilter = 'all' | 'tables' | 'views' | 'sequences' | 'routines' | 'packages' | 'events';
|
||||
|
||||
export const buildV2ExplorerFilterOptions = (
|
||||
translate: SidebarV2Translate = translateSidebarV2Current,
|
||||
@@ -302,7 +304,9 @@ export const buildV2ExplorerFilterOptions = (
|
||||
{ key: 'all', label: translate('sidebar.command_search.object_kind.all') },
|
||||
{ key: 'tables', label: translate('sidebar.command_search.object_kind.tables') },
|
||||
{ key: 'views', label: translate('sidebar.command_search.object_kind.views') },
|
||||
{ key: 'sequences', label: translate('sidebar.command_search.object_kind.sequences') },
|
||||
{ key: 'routines', label: translate('sidebar.command_search.object_kind.routines') },
|
||||
{ key: 'packages', label: translate('sidebar.command_search.object_kind.packages') },
|
||||
{ key: 'events', label: translate('sidebar.command_search.object_kind.events') },
|
||||
];
|
||||
|
||||
@@ -311,7 +315,9 @@ export const V2_EXPLORER_FILTER_OPTIONS: Array<{ key: V2ExplorerFilter; label: s
|
||||
const V2_EXPLORER_FILTER_GROUP_KEYS: Record<Exclude<V2ExplorerFilter, 'all'>, string[]> = {
|
||||
tables: ['tables'],
|
||||
views: ['views', 'materializedViews'],
|
||||
sequences: ['sequences'],
|
||||
routines: ['routines'],
|
||||
packages: ['packages'],
|
||||
events: ['events'],
|
||||
};
|
||||
|
||||
@@ -365,7 +371,9 @@ export const filterV2ExplorerTreeByKind = (
|
||||
const objectTypeMatches = (node: SidebarTreeNode): boolean => {
|
||||
if (filter === 'tables') return node.type === 'table';
|
||||
if (filter === 'views') return node.type === 'view' || node.type === 'materialized-view';
|
||||
if (filter === 'sequences') return node.type === 'sequence';
|
||||
if (filter === 'routines') return node.type === 'routine';
|
||||
if (filter === 'packages') return node.type === 'package';
|
||||
if (filter === 'events') return node.type === 'db-event';
|
||||
return false;
|
||||
};
|
||||
@@ -483,7 +491,9 @@ export const parseV2CommandSearchQuery = (value: unknown): V2CommandSearchQuery
|
||||
const isV2CommandSearchObjectNode = (node: SidebarTreeNode): boolean => {
|
||||
return node.type === 'table'
|
||||
|| node.type === 'view'
|
||||
|| node.type === 'materialized-view';
|
||||
|| node.type === 'materialized-view'
|
||||
|| node.type === 'sequence'
|
||||
|| node.type === 'package';
|
||||
};
|
||||
|
||||
export const V2_COMMAND_SEARCH_INITIAL_TREE_LIMIT = 24;
|
||||
@@ -499,7 +509,7 @@ export const buildV2CommandSearchTreeIndex = (
|
||||
const dataRef = item.node.dataRef || {};
|
||||
const normalizedTitle = String(item.title || '').toLowerCase();
|
||||
const normalizedPrimaryObjectText = String(
|
||||
dataRef.tableName || dataRef.viewName || item.title || '',
|
||||
dataRef.tableName || dataRef.viewName || dataRef.sequenceName || dataRef.packageName || item.title || '',
|
||||
).toLowerCase();
|
||||
|
||||
return [{
|
||||
@@ -509,6 +519,8 @@ export const buildV2CommandSearchTreeIndex = (
|
||||
item.meta,
|
||||
dataRef.tableName,
|
||||
dataRef.viewName,
|
||||
dataRef.sequenceName,
|
||||
dataRef.packageName,
|
||||
dataRef.dbName,
|
||||
dataRef.name,
|
||||
dataRef.config?.host,
|
||||
|
||||
Reference in New Issue
Block a user