mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-19 05:14:09 +08:00
🐛 fix(sidebar): 修复事件定义编辑入口 #540
- 事件右键编辑定义改为打开 object-edit 查询 - 拉取 CREATE EVENT 作为可编辑 SQL,避免只显示 SHOW CREATE EVENT - 补充事件定义编辑与菜单入口回归测试 Fixes #540
This commit is contained in:
@@ -194,6 +194,47 @@ describe('DefinitionViewer object edit entry', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('opens an editable query tab for event definitions', async () => {
|
||||
storeState.connections[0].config.type = 'mysql';
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
success: true,
|
||||
data: [{
|
||||
Event: 'daily_cleanup',
|
||||
'Create Event': 'CREATE EVENT `daily_cleanup`\nON SCHEDULE EVERY 1 DAY\nDO DELETE FROM logs',
|
||||
}],
|
||||
});
|
||||
|
||||
let renderer: any;
|
||||
await act(async () => {
|
||||
renderer = create(renderWithI18n(createTab({
|
||||
id: 'event-def-conn-1-main-daily_cleanup',
|
||||
title: '事件: daily_cleanup',
|
||||
type: 'event-def',
|
||||
eventName: 'daily_cleanup',
|
||||
viewName: undefined,
|
||||
viewKind: undefined,
|
||||
})));
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const button = renderer.root.findAll((node: any) => node.type === 'button' && findButtonText(node).includes('Edit object'))[0];
|
||||
|
||||
await act(async () => {
|
||||
button.props.onClick();
|
||||
});
|
||||
|
||||
const query = storeState.addTab.mock.calls[0][0].query;
|
||||
expect(storeState.addTab).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: 'Edit Event: daily_cleanup',
|
||||
type: 'query',
|
||||
queryMode: 'object-edit',
|
||||
query: expect.stringContaining('CREATE EVENT `daily_cleanup`'),
|
||||
}));
|
||||
expect(query).toContain('-- Edit Event: daily_cleanup');
|
||||
expect(query).toContain('ON SCHEDULE EVERY 1 DAY');
|
||||
expect(query).not.toContain('SHOW CREATE EVENT');
|
||||
});
|
||||
|
||||
it('uses SQL Server catalog metadata when loading routine definitions', async () => {
|
||||
storeState.connections[0].config.type = 'sqlserver';
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
|
||||
@@ -692,6 +692,23 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(onSelectSource).toContain('openSidebarObjectNode(info.node)');
|
||||
});
|
||||
|
||||
it('opens event edit menu with editable object SQL instead of a SHOW query', () => {
|
||||
const sidebarSource = readSidebarSource();
|
||||
const menuSource = readSourceFile('./sidebar/sidebarLegacyNodeMenu.tsx');
|
||||
const actionsSource = readSourceFile('./sidebar/useSidebarObjectActions.tsx');
|
||||
const eventMenuSource = menuSource.slice(
|
||||
menuSource.indexOf("} else if (node.type === 'db-event') {"),
|
||||
menuSource.indexOf("} else if (node.type === 'table') {"),
|
||||
);
|
||||
|
||||
expect(sidebarSource).toContain('openEditEvent,');
|
||||
expect(eventMenuSource).toContain('onClick: () => void openEditEvent(node)');
|
||||
expect(eventMenuSource).not.toContain('SHOW CREATE EVENT');
|
||||
expect(actionsSource).toContain('const openEditEvent = async (node: any) =>');
|
||||
expect(actionsSource).toContain("queryMode: 'object-edit'");
|
||||
expect(actionsSource).toContain('SHOW CREATE EVENT ${eventRef}');
|
||||
});
|
||||
|
||||
it('wires external SQL directory file actions to dedicated Wails APIs', () => {
|
||||
const source = readSidebarSource();
|
||||
const loadTablesSource = source.slice(
|
||||
|
||||
@@ -1929,6 +1929,7 @@ const Sidebar: React.FC<{
|
||||
handleRebindSavedQuery,
|
||||
openRoutineDefinition,
|
||||
openEventDefinition,
|
||||
openEditEvent,
|
||||
openSequenceDefinition,
|
||||
openPackageDefinition,
|
||||
openEditRoutine,
|
||||
@@ -2279,6 +2280,7 @@ const Sidebar: React.FC<{
|
||||
openEditRoutine,
|
||||
handleDropRoutine,
|
||||
openEventDefinition,
|
||||
openEditEvent,
|
||||
openSequenceDefinition,
|
||||
openPackageDefinition,
|
||||
resolveMessagePublishTarget,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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/useSidebarObjectActions.tsx', import.meta.url), 'utf8');
|
||||
|
||||
const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'de-DE', 'ru-RU'] as const;
|
||||
const key = 'sidebar.tab.edit_event';
|
||||
|
||||
@@ -171,6 +171,7 @@ export const buildSidebarLegacyNodeMenuItems = (
|
||||
openEditRoutine,
|
||||
handleDropRoutine,
|
||||
openEventDefinition,
|
||||
openEditEvent,
|
||||
openSequenceDefinition,
|
||||
openPackageDefinition,
|
||||
resolveMessagePublishTarget,
|
||||
@@ -884,17 +885,7 @@ export const buildSidebarLegacyNodeMenuItems = (
|
||||
key: 'edit-event-query',
|
||||
label: t('sidebar.menu.edit_definition'),
|
||||
icon: <EditOutlined />,
|
||||
onClick: () => {
|
||||
const { eventName, dbName, id } = node.dataRef;
|
||||
addTab({
|
||||
id: `query-edit-event-${Date.now()}`,
|
||||
title: t('sidebar.tab.edit_event', { name: eventName }),
|
||||
type: 'query',
|
||||
connectionId: id,
|
||||
dbName,
|
||||
query: `SHOW CREATE EVENT \`${String(eventName || '').replace(/`/g, '``')}\`;`
|
||||
});
|
||||
}
|
||||
onClick: () => void openEditEvent(node)
|
||||
},
|
||||
];
|
||||
} else if (node.type === 'table') {
|
||||
|
||||
@@ -114,6 +114,38 @@ const resolveCopyObjectNameLabel = (node: any): string => {
|
||||
return t('sidebar.copy_object_name.label.table');
|
||||
};
|
||||
|
||||
const quoteMySqlIdentifier = (raw: string): string => `\`${String(raw || '').replace(/`/g, '``')}\``;
|
||||
|
||||
const buildMySqlEventReference = (eventName: string, schemaName?: string): string => {
|
||||
const parsed = splitQualifiedName(eventName);
|
||||
const name = parsed.objectName || eventName;
|
||||
const schema = parsed.schemaName || String(schemaName || '').trim();
|
||||
return [schema, name]
|
||||
.filter(Boolean)
|
||||
.map(quoteMySqlIdentifier)
|
||||
.join('.');
|
||||
};
|
||||
|
||||
const ensureSidebarObjectEditSqlTerminator = (sql: string): string => {
|
||||
const normalized = String(sql || '').trim();
|
||||
if (!normalized) return '';
|
||||
return /;\s*$/.test(normalized) ? normalized : `${normalized};`;
|
||||
};
|
||||
|
||||
const extractMySqlEventCreateSql = (rows: any[]): string => {
|
||||
if (!Array.isArray(rows) || rows.length === 0) return '';
|
||||
for (const row of rows) {
|
||||
const keys = Object.keys(row || {});
|
||||
const sqlKey = keys.find(key => key.toLowerCase().includes('create event'));
|
||||
if (!sqlKey) continue;
|
||||
const definition = row[sqlKey];
|
||||
if (definition !== undefined && definition !== null && String(definition).trim()) {
|
||||
return String(definition);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const useSidebarObjectActions = ({
|
||||
connections,
|
||||
connectionIds,
|
||||
@@ -986,6 +1018,52 @@ export const useSidebarObjectActions = ({
|
||||
});
|
||||
};
|
||||
|
||||
const openEditEvent = async (node: any) => {
|
||||
const conn = node.dataRef;
|
||||
const eventName = String(conn?.eventName || '').trim();
|
||||
const dbName = String(conn?.dbName || '').trim();
|
||||
const id = String(conn?.id || '').trim();
|
||||
if (!eventName) return;
|
||||
|
||||
const objectLabel = t('definition_viewer.object.event');
|
||||
const header = [
|
||||
`-- ${t('definition_viewer.edit.comment_title', { object: objectLabel, name: eventName })}`,
|
||||
`-- ${t('definition_viewer.edit.comment_compatibility')}`,
|
||||
].join('\n') + '\n';
|
||||
let template = `${header}-- ${t('definition_viewer.edit.comment_empty_definition', { name: eventName })}\n`;
|
||||
|
||||
try {
|
||||
const dialect = getMetadataDialect(conn as SavedConnection);
|
||||
if (dialect === 'mysql') {
|
||||
const config = buildRuntimeConfig(conn, dbName);
|
||||
const eventRef = buildMySqlEventReference(eventName, conn?.schemaName || dbName);
|
||||
const result = await DBQuery(
|
||||
buildRpcConnectionConfig(config) as any,
|
||||
dbName,
|
||||
`SHOW CREATE EVENT ${eventRef}`,
|
||||
);
|
||||
if (result.success) {
|
||||
const createSql = extractMySqlEventCreateSql(result.data as any[]);
|
||||
if (createSql) {
|
||||
template = `${header}${ensureSidebarObjectEditSqlTerminator(createSql)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 降级使用空编辑模板,避免把 SHOW 语句当成可编辑定义。
|
||||
}
|
||||
|
||||
addTab({
|
||||
id: `query-edit-event-${Date.now()}`,
|
||||
title: t('sidebar.tab.edit_event', { name: eventName }),
|
||||
type: 'query',
|
||||
connectionId: id,
|
||||
dbName,
|
||||
query: template,
|
||||
queryMode: 'object-edit',
|
||||
});
|
||||
};
|
||||
|
||||
const openSequenceDefinition = (node: any) => {
|
||||
const { sequenceName, dbName, id } = node.dataRef;
|
||||
addTab({
|
||||
@@ -1246,6 +1324,7 @@ export const useSidebarObjectActions = ({
|
||||
handleRebindSavedQuery,
|
||||
openRoutineDefinition,
|
||||
openEventDefinition,
|
||||
openEditEvent,
|
||||
openSequenceDefinition,
|
||||
openPackageDefinition,
|
||||
openEditRoutine,
|
||||
|
||||
Reference in New Issue
Block a user