🐛 fix(sidebar): 修复事件定义编辑入口 #540

- 事件右键编辑定义改为打开 object-edit 查询

- 拉取 CREATE EVENT 作为可编辑 SQL,避免只显示 SHOW CREATE EVENT

- 补充事件定义编辑与菜单入口回归测试

Fixes #540
This commit is contained in:
Syngnat
2026-06-27 17:58:54 +08:00
parent e456925c23
commit 60eb696859
6 changed files with 142 additions and 12 deletions

View File

@@ -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({

View File

@@ -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(

View File

@@ -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,

View File

@@ -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';

View File

@@ -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') {

View File

@@ -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,