mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-22 00:42:47 +08:00
✨ feat(data-transfer): 完善数据库 SQL 备份与恢复
- 数据导入工作台新增数据库模式,支持 SQL 文件选择、目标库、进度、取消与重试 - 新增受连接保护约束的 ImportDatabaseSQL 接口并复用流式执行链路 - SQL 导出支持切换数据库上下文,兼顾原库备份和跨库导入 - 调整侧栏与表概览入口,备份先进入工作台确认配置再执行 - 同步 Wails 绑定、Web 运行时限制、六语文案和回归测试
This commit is contained in:
@@ -12,6 +12,7 @@ describe('dataImportTab', () => {
|
||||
connectionId: ' conn-1 ',
|
||||
dbName: ' app ',
|
||||
tableName: ' public.users ',
|
||||
launchKey: ' launch-table-1 ',
|
||||
title: ' 数据导入 ',
|
||||
});
|
||||
|
||||
@@ -22,6 +23,8 @@ describe('dataImportTab', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'app',
|
||||
tableName: 'public.users',
|
||||
dataImportMode: 'table',
|
||||
dataImportLaunchKey: 'launch-table-1',
|
||||
initialTab: 'target',
|
||||
});
|
||||
});
|
||||
@@ -33,6 +36,26 @@ describe('dataImportTab', () => {
|
||||
expect(tab.connectionId).toBe('');
|
||||
expect(tab.dbName).toBeUndefined();
|
||||
expect(tab.tableName).toBeUndefined();
|
||||
expect(tab.dataImportMode).toBe('table');
|
||||
expect(tab.dataImportLaunchKey).toMatch(/^data-import-/);
|
||||
});
|
||||
|
||||
it('builds a database import launch that clears a stale table target', () => {
|
||||
const tab = buildDataImportWorkbenchTab({
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'app',
|
||||
tableName: 'users',
|
||||
mode: 'database',
|
||||
launchKey: 'database-launch-1',
|
||||
});
|
||||
|
||||
expect(tab).toMatchObject({
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'app',
|
||||
dataImportMode: 'database',
|
||||
dataImportLaunchKey: 'database-launch-1',
|
||||
});
|
||||
expect(tab).toHaveProperty('tableName', undefined);
|
||||
});
|
||||
|
||||
it('keeps the active target when the stable workbench is reopened during an import', () => {
|
||||
@@ -41,6 +64,7 @@ describe('dataImportTab', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'app',
|
||||
tableName: 'users',
|
||||
launchKey: 'running-launch',
|
||||
}),
|
||||
dataImportRunning: true,
|
||||
};
|
||||
@@ -49,6 +73,8 @@ describe('dataImportTab', () => {
|
||||
connectionId: 'conn-2',
|
||||
dbName: 'analytics',
|
||||
tableName: 'events',
|
||||
mode: 'database',
|
||||
launchKey: 'ignored-launch',
|
||||
})).toBe(existing);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,14 @@ import type { TabData } from '../types';
|
||||
|
||||
export const DATA_IMPORT_WORKBENCH_TAB_ID = 'data-import-workbench';
|
||||
|
||||
export type DataImportMode = 'table' | 'database';
|
||||
|
||||
export type BuildDataImportWorkbenchTabInput = {
|
||||
connectionId?: string;
|
||||
dbName?: string;
|
||||
tableName?: string;
|
||||
mode?: DataImportMode;
|
||||
launchKey?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
@@ -17,16 +21,23 @@ const normalizeOptionalText = (value: string | undefined): string | undefined =>
|
||||
|
||||
export const buildDataImportWorkbenchTab = (
|
||||
input: BuildDataImportWorkbenchTabInput = {},
|
||||
): TabData => ({
|
||||
id: DATA_IMPORT_WORKBENCH_TAB_ID,
|
||||
title: String(input.title || t('data_import.workbench.title')).trim()
|
||||
|| t('data_import.workbench.title'),
|
||||
type: 'data-import' as TabData['type'],
|
||||
connectionId: normalizeOptionalText(input.connectionId) || '',
|
||||
dbName: normalizeOptionalText(input.dbName),
|
||||
tableName: normalizeOptionalText(input.tableName),
|
||||
initialTab: 'target',
|
||||
});
|
||||
): TabData => {
|
||||
const mode: DataImportMode = input.mode === 'database' ? 'database' : 'table';
|
||||
const launchKey = normalizeOptionalText(input.launchKey)
|
||||
|| `data-import-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
return {
|
||||
id: DATA_IMPORT_WORKBENCH_TAB_ID,
|
||||
title: String(input.title || t('data_import.workbench.title')).trim()
|
||||
|| t('data_import.workbench.title'),
|
||||
type: 'data-import' as TabData['type'],
|
||||
connectionId: normalizeOptionalText(input.connectionId) || '',
|
||||
dbName: normalizeOptionalText(input.dbName),
|
||||
tableName: mode === 'table' ? normalizeOptionalText(input.tableName) : undefined,
|
||||
dataImportMode: mode,
|
||||
dataImportLaunchKey: launchKey,
|
||||
initialTab: 'target',
|
||||
};
|
||||
};
|
||||
|
||||
export const resolveDataImportWorkbenchLaunchTab = (
|
||||
existingTab: TabData | undefined,
|
||||
|
||||
@@ -126,10 +126,43 @@ describe('tableExportTab', () => {
|
||||
tableExportInitialObjectNames: ['users', 'orders'],
|
||||
tableExportContentMode: 'backup',
|
||||
tableExportIncludeDropIfExists: true,
|
||||
tableExportLaunchKey: 'batch-tables-1',
|
||||
tableExportRequestKey: 'batch-tables-1',
|
||||
}));
|
||||
});
|
||||
|
||||
it('carries launch-only refresh keys without creating auto-start requests', () => {
|
||||
const tableTab = buildBatchTableExportWorkbenchTab({
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'SYS',
|
||||
initialObjectNames: [' users '],
|
||||
contentMode: 'backup',
|
||||
includeDropIfExists: false,
|
||||
launchKey: ' table-launch-1 ',
|
||||
});
|
||||
const databaseTab = buildDatabaseExportWorkbenchTab({
|
||||
connectionId: 'conn-1',
|
||||
dbName: ' app ',
|
||||
contentMode: 'schema',
|
||||
launchKey: ' database-launch-1 ',
|
||||
});
|
||||
|
||||
expect(tableTab).toEqual(expect.objectContaining({
|
||||
tableExportInitialObjectNames: ['users'],
|
||||
tableExportContentMode: 'backup',
|
||||
tableExportIncludeDropIfExists: false,
|
||||
tableExportLaunchKey: 'table-launch-1',
|
||||
}));
|
||||
expect(databaseTab).toEqual(expect.objectContaining({
|
||||
exportWorkbenchMode: 'database',
|
||||
dbName: 'app',
|
||||
tableExportContentMode: 'schema',
|
||||
tableExportLaunchKey: 'database-launch-1',
|
||||
}));
|
||||
expect(tableTab).toHaveProperty('tableExportRequestKey', undefined);
|
||||
expect(databaseTab).toHaveProperty('tableExportRequestKey', undefined);
|
||||
});
|
||||
|
||||
it('builds batch database export workbench tabs with stable ids', () => {
|
||||
setCurrentLanguage('zh-CN');
|
||||
const tab = buildBatchDatabaseExportWorkbenchTab({
|
||||
@@ -155,6 +188,7 @@ describe('tableExportTab', () => {
|
||||
tableExportInitialDatabaseNames: ['app', 'audit'],
|
||||
tableExportContentMode: 'schema',
|
||||
tableExportIncludeDropIfExists: true,
|
||||
tableExportLaunchKey: 'batch-databases-1',
|
||||
tableExportRequestKey: 'batch-databases-1',
|
||||
}));
|
||||
});
|
||||
@@ -180,6 +214,7 @@ describe('tableExportTab', () => {
|
||||
exportWorkbenchMode: 'database',
|
||||
dbName: 'app',
|
||||
tableExportContentMode: 'backup',
|
||||
tableExportLaunchKey: 'database-1',
|
||||
tableExportRequestKey: 'database-1',
|
||||
}));
|
||||
expect(schemaTab).toEqual(expect.objectContaining({
|
||||
@@ -189,6 +224,7 @@ describe('tableExportTab', () => {
|
||||
dbName: 'app',
|
||||
schemaName: 'sales',
|
||||
tableExportContentMode: 'schema',
|
||||
tableExportLaunchKey: 'schema-1',
|
||||
tableExportRequestKey: 'schema-1',
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ export const buildExportWorkbenchHistoryKey = (
|
||||
type ExportWorkbenchLaunchOptions = {
|
||||
contentMode?: TableExportContentMode;
|
||||
includeDropIfExists?: boolean;
|
||||
launchKey?: string;
|
||||
requestKey?: string;
|
||||
};
|
||||
|
||||
@@ -103,11 +104,18 @@ const normalizeNameList = (values: string[] | undefined): string[] | undefined =
|
||||
return result.length > 0 ? result : undefined;
|
||||
};
|
||||
|
||||
const buildLaunchMetadata = (input: ExportWorkbenchLaunchOptions): Partial<TabData> => ({
|
||||
...(input.contentMode ? { tableExportContentMode: input.contentMode } : {}),
|
||||
...(input.includeDropIfExists === true ? { tableExportIncludeDropIfExists: true } : {}),
|
||||
...(String(input.requestKey || '').trim() ? { tableExportRequestKey: String(input.requestKey).trim() } : {}),
|
||||
});
|
||||
const buildLaunchMetadata = (input: ExportWorkbenchLaunchOptions): Partial<TabData> => {
|
||||
const requestKey = String(input.requestKey || '').trim();
|
||||
const launchKey = String(input.launchKey || '').trim() || requestKey;
|
||||
return {
|
||||
...(input.contentMode ? { tableExportContentMode: input.contentMode } : {}),
|
||||
...(input.includeDropIfExists !== undefined
|
||||
? { tableExportIncludeDropIfExists: input.includeDropIfExists === true }
|
||||
: {}),
|
||||
tableExportLaunchKey: launchKey || undefined,
|
||||
tableExportRequestKey: requestKey || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeScopeOptions = (
|
||||
scopeOptions: TableExportScopeOption[] | undefined,
|
||||
|
||||
Reference in New Issue
Block a user