mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-01 19:41:26 +08:00
🐛 fix(sql-editor): 修复对象超链接侧栏定位与样式
- 侧栏定位 fallback 统一按限定名解析,兼容 schema.object 与对象名、大小写差异 - 补充视图、函数、触发器超链接定位事件与树节点匹配回归测试 - 将 SQL 编辑器超链接改为蓝色实线下划线,并补充暗色主题样式断言
This commit is contained in:
@@ -649,9 +649,11 @@ body[data-theme='light'] .redis-viewer-workbench .ant-radio-button-wrapper-check
|
||||
}
|
||||
}
|
||||
.gonavi-query-editor-link-hint {
|
||||
color: #1677ff !important;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-color: currentColor;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
@@ -672,6 +674,10 @@ body[data-theme='dark'] .gonavi-query-editor-object-token {
|
||||
color: #7dd3fc;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] .gonavi-query-editor-link-hint {
|
||||
color: #69b1ff !important;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] .gonavi-query-editor-column-token {
|
||||
color: #5eead4;
|
||||
}
|
||||
|
||||
@@ -1079,6 +1079,13 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(lastDecorationCall?.[1]?.[0]?.options?.inlineClassName).toBe('gonavi-query-editor-link-hint');
|
||||
});
|
||||
|
||||
it('keeps query editor hyperlink decorations blue with a solid underline', () => {
|
||||
const css = readFileSync(new URL('../App.css', import.meta.url), 'utf8');
|
||||
|
||||
expect(css).toMatch(/\.gonavi-query-editor-link-hint\s*\{[^}]*color:\s*#1677ff\s*!important;[^}]*text-decoration:\s*underline;[^}]*text-decoration-style:\s*solid;[^}]*text-decoration-color:\s*currentColor;/s);
|
||||
expect(css).toMatch(/body\[data-theme='dark'\]\s+\.gonavi-query-editor-link-hint\s*\{[^}]*color:\s*#69b1ff\s*!important;/s);
|
||||
});
|
||||
|
||||
it('opens a view tab on ctrl left click inside the editor', async () => {
|
||||
editorState.value = 'select * from reporting.active_users';
|
||||
autoFetchState.visible = true;
|
||||
@@ -1209,6 +1216,24 @@ describe('QueryEditor external SQL save', () => {
|
||||
schemaName: 'reporting',
|
||||
sidebarLocateKey: 'conn-1-main-routine-reporting.refresh_stats',
|
||||
});
|
||||
expect((window as any).dispatchEvent).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'gonavi:locate-sidebar-object',
|
||||
detail: expect.objectContaining({
|
||||
tabId: 'conn-1-main-trigger-audit.users_bi-audit.users',
|
||||
triggerName: 'audit.users_bi',
|
||||
schemaName: 'audit',
|
||||
objectGroup: 'triggers',
|
||||
}),
|
||||
}));
|
||||
expect((window as any).dispatchEvent).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'gonavi:locate-sidebar-object',
|
||||
detail: expect.objectContaining({
|
||||
tabId: 'conn-1-main-routine-reporting.refresh_stats',
|
||||
routineName: 'reporting.refresh_stats',
|
||||
schemaName: 'reporting',
|
||||
objectGroup: 'routines',
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('switches current database on cmd left click for database identifiers', async () => {
|
||||
|
||||
@@ -309,6 +309,111 @@ describe('sidebarLocate', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('finds schema objects when tree nodes use unqualified names or different case', () => {
|
||||
const viewTarget = resolveSidebarLocateTarget({
|
||||
tabId: 'conn-1-main-view-reporting.active_users',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
tableName: 'reporting.active_users',
|
||||
schemaName: 'reporting',
|
||||
objectGroup: 'views',
|
||||
}, { groupBySchema: true });
|
||||
|
||||
const routineTarget = resolveSidebarLocateTarget({
|
||||
tabId: 'conn-1-main-routine-reporting.refresh_stats',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
tableName: 'reporting.refresh_stats',
|
||||
schemaName: 'reporting',
|
||||
objectGroup: 'routines',
|
||||
}, { groupBySchema: true });
|
||||
|
||||
const triggerTarget = resolveSidebarLocateTarget({
|
||||
tabId: 'conn-1-main-trigger-audit.users_bi-audit.users',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
tableName: 'audit.users_bi',
|
||||
schemaName: 'audit',
|
||||
objectGroup: 'triggers',
|
||||
}, { groupBySchema: true });
|
||||
|
||||
const tree = [
|
||||
{
|
||||
key: 'conn-1',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main',
|
||||
dataRef: { id: 'conn-1', dbName: 'main' },
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-schema-REPORTING',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-schema-REPORTING-views',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-view-ACTIVE_USERS',
|
||||
type: 'view',
|
||||
dataRef: { id: 'conn-1', dbName: 'main', viewName: 'ACTIVE_USERS', schemaName: 'REPORTING' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'conn-1-main-schema-REPORTING-routines',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-routine-REFRESH_STATS',
|
||||
type: 'routine',
|
||||
dataRef: { id: 'conn-1', dbName: 'main', routineName: 'REFRESH_STATS', schemaName: 'REPORTING' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'conn-1-main-schema-AUDIT',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-schema-AUDIT-triggers',
|
||||
children: [
|
||||
{
|
||||
key: 'conn-1-main-trigger-USERS_BI-AUDIT.USERS',
|
||||
type: 'db-trigger',
|
||||
dataRef: { id: 'conn-1', dbName: 'main', triggerName: 'USERS_BI', tableName: 'AUDIT.USERS', schemaName: 'AUDIT' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
expect(findSidebarNodePathForLocate(tree, viewTarget)).toEqual([
|
||||
'conn-1',
|
||||
'conn-1-main',
|
||||
'conn-1-main-schema-REPORTING',
|
||||
'conn-1-main-schema-REPORTING-views',
|
||||
'conn-1-main-view-ACTIVE_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-REFRESH_STATS',
|
||||
]);
|
||||
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-USERS_BI-AUDIT.USERS',
|
||||
]);
|
||||
});
|
||||
|
||||
it('finds external SQL file paths from loaded tree data', () => {
|
||||
const target = resolveSidebarLocateTarget({
|
||||
filePath: 'C:\\Users\\me\\sql\\report.sql',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { splitQualifiedNameLast } from './qualifiedName';
|
||||
|
||||
export type SidebarLocateObjectGroup = 'tables' | 'views' | 'materializedViews' | 'triggers' | 'routines' | 'externalSqlFiles';
|
||||
export type SidebarLocateDatabaseObjectGroup = Exclude<SidebarLocateObjectGroup, 'externalSqlFiles'>;
|
||||
|
||||
@@ -66,11 +68,10 @@ const normalizeExternalSQLLocatePath = (value: unknown): string => toTrimmedStri
|
||||
export const splitSidebarQualifiedName = (qualifiedName: string): { schemaName: string; objectName: string } => {
|
||||
const raw = toTrimmedString(qualifiedName);
|
||||
if (!raw) return { schemaName: '', objectName: '' };
|
||||
const idx = raw.lastIndexOf('.');
|
||||
if (idx <= 0 || idx >= raw.length - 1) return { schemaName: '', objectName: raw };
|
||||
const parsed = splitQualifiedNameLast(raw);
|
||||
return {
|
||||
schemaName: raw.substring(0, idx).trim(),
|
||||
objectName: raw.substring(idx + 1).trim(),
|
||||
schemaName: parsed.parentPath,
|
||||
objectName: parsed.objectName,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -251,16 +252,23 @@ export const findSidebarNodePathByKey = (
|
||||
const matchesLocateObjectName = (target: SidebarLocateTarget, nodeObjectName: string, nodeSchemaName: string): boolean => {
|
||||
const normalizedNodeName = toTrimmedString(nodeObjectName);
|
||||
if (!normalizedNodeName) return false;
|
||||
if (normalizedNodeName === target.tableName) return true;
|
||||
|
||||
if (!target.schemaName) return false;
|
||||
|
||||
const nodeParsed = splitSidebarQualifiedName(normalizedNodeName);
|
||||
const targetParsed = splitSidebarQualifiedName(target.tableName);
|
||||
const nodeObject = nodeParsed.objectName || normalizedNodeName;
|
||||
const targetObject = targetParsed.objectName || target.tableName;
|
||||
const resolvedNodeSchema = toTrimmedString(nodeSchemaName) || nodeParsed.schemaName;
|
||||
return resolvedNodeSchema === target.schemaName && nodeObject === targetObject;
|
||||
const resolvedTargetSchema = toTrimmedString(target.schemaName) || targetParsed.schemaName;
|
||||
const normalize = (value: string): string => toTrimmedString(value).toLowerCase();
|
||||
|
||||
if (normalize(normalizedNodeName) === normalize(target.tableName)) return true;
|
||||
|
||||
if (!resolvedTargetSchema) {
|
||||
return !resolvedNodeSchema && normalize(nodeObject) === normalize(targetObject);
|
||||
}
|
||||
|
||||
return normalize(resolvedNodeSchema) === normalize(resolvedTargetSchema)
|
||||
&& normalize(nodeObject) === normalize(targetObject);
|
||||
};
|
||||
|
||||
const matchesLocateObjectNode = (node: SidebarLocateTreeNodeLike, target: SidebarLocateTarget): boolean => {
|
||||
|
||||
Reference in New Issue
Block a user