feat(query-editor): 扩展 SQL 编辑器对象跳转到视图触发器和存储过程

- 为 QueryEditor 补充视图、物化视图、触发器和函数元数据解析
- 支持 Ctrl/Cmd 点击打开对应对象定义页并同步当前 host/db 上下文
- 扩展 sidebarLocate 对触发器和函数的定位能力
- 补充 QueryEditor 与 sidebarLocate 定向测试覆盖
This commit is contained in:
Syngnat
2026-05-30 21:44:42 +08:00
parent 6934285d83
commit ee96125385
4 changed files with 1438 additions and 8 deletions

View File

@@ -96,6 +96,32 @@ describe('sidebarLocate', () => {
})).toBeNull();
});
it('builds locate requests from trigger and routine tabs', () => {
expect(normalizeSidebarLocateObjectRequestFromTab({
id: 'trigger-conn-1-main-audit.users_bi',
type: 'trigger',
connectionId: 'conn-1',
dbName: 'main',
triggerName: 'audit.users_bi',
})).toMatchObject({
tableName: 'audit.users_bi',
schemaName: 'audit',
objectGroup: 'triggers',
});
expect(normalizeSidebarLocateObjectRequestFromTab({
id: 'routine-def-conn-1-main-reporting.refresh_stats',
type: 'routine-def',
connectionId: 'conn-1',
dbName: 'main',
routineName: 'reporting.refresh_stats',
})).toMatchObject({
tableName: 'reporting.refresh_stats',
schemaName: 'reporting',
objectGroup: 'routines',
});
});
it('keeps StarRocks materialized view tabs on the materialized views branch', () => {
const request = normalizeSidebarLocateObjectRequestFromTab({
id: 'view-def-conn-1-main-sales.mv_daily',
@@ -182,4 +208,81 @@ describe('sidebarLocate', () => {
'conn-1-main-public.users',
]);
});
it('finds trigger and routine paths from loaded tree data', () => {
const triggerTarget = resolveSidebarLocateTarget({
connectionId: 'conn-1',
dbName: 'main',
tableName: 'audit.users_bi',
schemaName: 'audit',
objectGroup: 'triggers',
}, { groupBySchema: true });
const routineTarget = resolveSidebarLocateTarget({
connectionId: 'conn-1',
dbName: 'main',
tableName: 'reporting.refresh_stats',
schemaName: 'reporting',
objectGroup: 'routines',
}, { groupBySchema: true });
const tree = [
{
key: 'conn-1',
children: [
{
key: 'conn-1-main',
dataRef: { id: 'conn-1', dbName: 'main' },
children: [
{
key: 'conn-1-main-schema-audit',
children: [
{
key: 'conn-1-main-schema-audit-triggers',
children: [
{
key: 'conn-1-main-trigger-audit.users_bi-audit.users',
type: 'db-trigger',
dataRef: { id: 'conn-1', dbName: 'main', triggerName: 'audit.users_bi', schemaName: 'audit' },
},
],
},
],
},
{
key: 'conn-1-main-schema-reporting',
children: [
{
key: 'conn-1-main-schema-reporting-routines',
children: [
{
key: 'conn-1-main-routine-reporting.refresh_stats',
type: 'routine',
dataRef: { id: 'conn-1', dbName: 'main', routineName: 'reporting.refresh_stats', schemaName: 'reporting' },
},
],
},
],
},
],
},
],
},
];
expect(findSidebarNodePathForLocate(tree, triggerTarget)).toEqual([
'conn-1',
'conn-1-main',
'conn-1-main-schema-audit',
'conn-1-main-schema-audit-triggers',
'conn-1-main-trigger-audit.users_bi-audit.users',
]);
expect(findSidebarNodePathForLocate(tree, routineTarget)).toEqual([
'conn-1',
'conn-1-main',
'conn-1-main-schema-reporting',
'conn-1-main-schema-reporting-routines',
'conn-1-main-routine-reporting.refresh_stats',
]);
});
});

View File

@@ -1,4 +1,4 @@
export type SidebarLocateObjectGroup = 'tables' | 'views' | 'materializedViews';
export type SidebarLocateObjectGroup = 'tables' | 'views' | 'materializedViews' | 'triggers' | 'routines';
export interface SidebarLocateObjectRequest {
tabId?: string;
@@ -38,6 +38,8 @@ export interface SidebarLocateTabLike {
tableName?: string;
viewName?: string;
viewKind?: string;
triggerName?: string;
routineName?: string;
}
const toTrimmedString = (value: unknown): string => String(value ?? '').trim();
@@ -57,15 +59,21 @@ const inferObjectGroup = (detail: Record<string, unknown>, connectionId: string,
const explicitGroup = toTrimmedString(detail.objectGroup);
if (explicitGroup === 'views' || explicitGroup === 'view') return 'views';
if (explicitGroup === 'materializedViews' || explicitGroup === 'materialized-view') return 'materializedViews';
if (explicitGroup === 'triggers' || explicitGroup === 'trigger') return 'triggers';
if (explicitGroup === 'routines' || explicitGroup === 'routine') return 'routines';
const explicitType = toTrimmedString(detail.objectType);
if (explicitType === 'view' || explicitType === 'views') return 'views';
if (explicitType === 'materialized' || explicitType === 'materialized-view') return 'materializedViews';
if (explicitType === 'trigger' || explicitType === 'triggers') return 'triggers';
if (explicitType === 'routine' || explicitType === 'routines') return 'routines';
const tabId = toTrimmedString(detail.tabId);
const dbNodeKey = `${connectionId}-${dbName}`;
if (tabId.startsWith(`${dbNodeKey}-materialized-view-`)) return 'materializedViews';
if (tabId.startsWith(`${dbNodeKey}-view-`)) return 'views';
if (tabId.startsWith(`${dbNodeKey}-trigger-`)) return 'triggers';
if (tabId.startsWith(`${dbNodeKey}-routine-`) || tabId.startsWith(`routine-def-${connectionId}-${dbName}-`)) return 'routines';
return 'tables';
};
@@ -74,7 +82,7 @@ export const normalizeSidebarLocateObjectRequest = (detail: unknown): SidebarLoc
const raw = (detail || {}) as Record<string, unknown>;
const connectionId = toTrimmedString(raw.connectionId);
const dbName = toTrimmedString(raw.dbName);
const tableName = toTrimmedString(raw.tableName || raw.objectName || raw.viewName);
const tableName = toTrimmedString(raw.tableName || raw.objectName || raw.viewName || raw.triggerName || raw.routineName);
if (!connectionId || !dbName || !tableName) {
return null;
@@ -97,8 +105,12 @@ export const normalizeSidebarLocateObjectRequestFromTab = (tab: SidebarLocateTab
if (!tab) return null;
const objectName = tab.type === 'view-def'
? toTrimmedString(tab.viewName || tab.tableName)
: toTrimmedString(tab.tableName || tab.viewName);
if (tab.type !== 'table' && tab.type !== 'view-def') {
: tab.type === 'trigger'
? toTrimmedString(tab.triggerName || tab.tableName)
: tab.type === 'routine-def'
? toTrimmedString(tab.routineName || tab.tableName)
: toTrimmedString(tab.tableName || tab.viewName);
if (tab.type !== 'table' && tab.type !== 'view-def' && tab.type !== 'trigger' && tab.type !== 'routine-def') {
return null;
}
@@ -109,7 +121,7 @@ export const normalizeSidebarLocateObjectRequestFromTab = (tab: SidebarLocateTab
tableName: objectName,
objectGroup: tab.type === 'view-def'
? (tab.viewKind === 'materialized' ? 'materializedViews' : 'views')
: undefined,
: (tab.type === 'trigger' ? 'triggers' : (tab.type === 'routine-def' ? 'routines' : undefined)),
});
};
@@ -121,7 +133,13 @@ export const resolveSidebarLocateTarget = (
const databaseKey = `${request.connectionId}-${request.dbName}`;
const fallbackTargetKey = request.objectGroup === 'materializedViews'
? `${databaseKey}-materialized-view-${request.tableName}`
: (request.objectGroup === 'views' ? `${databaseKey}-view-${request.tableName}` : `${databaseKey}-${request.tableName}`);
: request.objectGroup === 'views'
? `${databaseKey}-view-${request.tableName}`
: request.objectGroup === 'triggers'
? `${databaseKey}-trigger-${request.tableName}`
: request.objectGroup === 'routines'
? `${databaseKey}-routine-${request.tableName}`
: `${databaseKey}-${request.tableName}`;
const targetKey = request.tabId || fallbackTargetKey;
const schemaSegment = request.schemaName || 'default';
const schemaKey = options.groupBySchema ? `${databaseKey}-schema-${schemaSegment}` : undefined;
@@ -204,6 +222,16 @@ const matchesLocateObjectNode = (node: SidebarLocateTreeNodeLike, target: Sideba
return matchesLocateObjectName(target, toTrimmedString(dataRef.viewName || dataRef.tableName), toTrimmedString(dataRef.schemaName));
}
if (target.objectGroup === 'triggers') {
if (node.type !== 'db-trigger') return false;
return matchesLocateObjectName(target, toTrimmedString(dataRef.triggerName || dataRef.tableName), toTrimmedString(dataRef.schemaName));
}
if (target.objectGroup === 'routines') {
if (node.type !== 'routine') return false;
return matchesLocateObjectName(target, toTrimmedString(dataRef.routineName || dataRef.tableName), toTrimmedString(dataRef.schemaName));
}
if (node.type !== 'table') return false;
return matchesLocateObjectName(target, toTrimmedString(dataRef.tableName), toTrimmedString(dataRef.schemaName));
};