From 177545dca8f26c36e24f7bce49b0d0dc0aaf8d95 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 21 Jul 2026 13:59:30 +0800 Subject: [PATCH] fix(import): preserve selected Excel sheet during execution --- .../components/import/TableImportDialog.vue | 30 +++++++------------ .../lib/__tests__/table/tableImport.spec.ts | 18 ++++++++++- apps/desktop/src/lib/table/tableImport.ts | 29 ++++++++++++++++++ crates/dbx-core/src/table_import.rs | 12 ++++++++ 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/components/import/TableImportDialog.vue b/apps/desktop/src/components/import/TableImportDialog.vue index 95bf8f433..46b666c1e 100644 --- a/apps/desktop/src/components/import/TableImportDialog.vue +++ b/apps/desktop/src/components/import/TableImportDialog.vue @@ -13,7 +13,7 @@ import { AlertTriangle, ArrowLeft, ArrowRight, Check, CheckCircle2, FileJson, Fi import { useConnectionStore } from "@/stores/connectionStore"; import { useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; -import { autoMapImportColumns, nextTableImportWizardStep, previousTableImportWizardStep, requiredImportTargetColumns, suggestImportTargetDataTypes, validateImportMappings, type TableImportWizardStep } from "@/lib/table/tableImport"; +import { autoMapImportColumns, buildTableImportParseOptions, nextTableImportWizardStep, previousTableImportWizardStep, requiredImportTargetColumns, suggestImportTargetDataTypes, validateImportMappings, type TableImportWizardStep } from "@/lib/table/tableImport"; import { getDataTypeOptions } from "@/lib/table/tableStructureEditorState"; import { tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; import type { ColumnInfo } from "@/types/database"; @@ -180,17 +180,7 @@ const createColumnSummaries = computed(() => targetDataType: mapping.targetDataType || "", })), ); -const parseOptions = computed(() => ({ - delimiter: sourceFormat.value === "tsv" ? "\\t" : sourceFormat.value === "csv" ? "," : delimiter.value, - encoding: isDelimitedFormat(sourceFormat.value) ? textEncoding.value : null, - titleRow: titleRow.value, - dataStartRow: dataStartRow.value, - lastDataRow: lastDataRow.value, - trimValues: trimValues.value, - emptyStringAsNull: emptyStringAsNull.value, - sheetName: sourceFormat.value === "excel" ? selectedSheet.value || null : null, - jsonShape: sourceFormat.value === "json" ? jsonShape.value : null, -})); +const parseOptions = computed(() => taskParseOptions(sourceFormat.value, selectedSheet.value)); const terminalStatus = computed(() => progress.value?.status && ["done", "error", "cancelled"].includes(progress.value.status)); function resetState() { @@ -268,17 +258,18 @@ function uniqueTableName(baseName: string, usedNames: Set): string { } function taskParseOptions(format: api.TableImportSourceFormat, sheetName = ""): api.TableImportParseOptions { - return { - delimiter: format === "tsv" ? "\\t" : format === "csv" ? "," : delimiter.value, - encoding: isDelimitedFormat(format) ? textEncoding.value : null, + return buildTableImportParseOptions({ + format, + delimiter: delimiter.value, + textEncoding: textEncoding.value, titleRow: titleRow.value, dataStartRow: dataStartRow.value, lastDataRow: lastDataRow.value, trimValues: trimValues.value, emptyStringAsNull: emptyStringAsNull.value, - sheetName: format === "excel" ? sheetName || null : null, - jsonShape: format === "json" ? jsonShape.value : null, - }; + sheetName, + jsonShape: jsonShape.value, + }); } function importParseOptions(format: api.TableImportSourceFormat, currentPreview: api.TableImportPreview, sheetName = ""): api.TableImportParseOptions { @@ -622,7 +613,8 @@ async function startImport() { filePath: currentPreview.filePath, sourceRef: currentPreview.sourceRef || null, sourceFormat: sourceFormat.value, - parseOptions: importParseOptions(sourceFormat.value, currentPreview), + // Execution must parse the same worksheet that produced the preview and mappings. + parseOptions: importParseOptions(sourceFormat.value, currentPreview, selectedSheet.value), mappings: mappedColumns.value, mode: targetMode.value === "create" ? "append" : importMode.value, createTable: targetMode.value === "create", diff --git a/apps/desktop/src/lib/__tests__/table/tableImport.spec.ts b/apps/desktop/src/lib/__tests__/table/tableImport.spec.ts index 6887130b9..48544b05b 100644 --- a/apps/desktop/src/lib/__tests__/table/tableImport.spec.ts +++ b/apps/desktop/src/lib/__tests__/table/tableImport.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { autoMapImportColumns, nextTableImportWizardStep, previousTableImportWizardStep, requiredImportTargetColumns, suggestImportTargetDataTypes, validateImportMappings } from "@/lib/table/tableImport"; +import { autoMapImportColumns, buildTableImportParseOptions, nextTableImportWizardStep, previousTableImportWizardStep, requiredImportTargetColumns, suggestImportTargetDataTypes, validateImportMappings } from "@/lib/table/tableImport"; describe("tableImport", () => { it("auto maps exact and normalized column names", () => { @@ -54,6 +54,22 @@ describe("tableImport", () => { expect(previousTableImportWizardStep("source")).toBe("source"); }); + it("keeps the selected Excel worksheet in execution parse options", () => { + const baseSettings = { + delimiter: ",", + textEncoding: "auto" as const, + titleRow: 1, + dataStartRow: 2, + lastDataRow: 0, + trimValues: false, + emptyStringAsNull: true, + jsonShape: "auto" as const, + }; + + expect(buildTableImportParseOptions({ ...baseSettings, format: "excel", sheetName: "Second" }).sheetName).toBe("Second"); + expect(buildTableImportParseOptions({ ...baseSettings, format: "csv", sheetName: "Second" }).sheetName).toBeNull(); + }); + it("suggests create-table data types from preview rows", () => { expect( suggestImportTargetDataTypes( diff --git a/apps/desktop/src/lib/table/tableImport.ts b/apps/desktop/src/lib/table/tableImport.ts index 96586bda7..f9aebba35 100644 --- a/apps/desktop/src/lib/table/tableImport.ts +++ b/apps/desktop/src/lib/table/tableImport.ts @@ -1,4 +1,5 @@ import type { DatabaseType } from "@/types/database"; +import type { TableImportJsonShape, TableImportParseOptions, TableImportSourceFormat, TableImportTextEncoding } from "@/lib/backend/api"; export const IMPORT_SKIP_TARGET = ""; @@ -26,6 +27,34 @@ export type TableImportWizardStep = "source" | "options" | "mapping" | "review" export const TABLE_IMPORT_WIZARD_STEPS: TableImportWizardStep[] = ["source", "options", "mapping", "review", "execution"]; +export interface TableImportParseSettings { + format: TableImportSourceFormat; + delimiter: string; + textEncoding: TableImportTextEncoding; + titleRow: number; + dataStartRow: number; + lastDataRow: number; + trimValues: boolean; + emptyStringAsNull: boolean; + sheetName?: string; + jsonShape: TableImportJsonShape; +} + +export function buildTableImportParseOptions(settings: TableImportParseSettings): TableImportParseOptions { + const isDelimited = settings.format === "csv" || settings.format === "tsv" || settings.format === "delimited"; + return { + delimiter: settings.format === "tsv" ? "\\t" : settings.format === "csv" ? "," : settings.delimiter, + encoding: isDelimited ? settings.textEncoding : null, + titleRow: settings.titleRow, + dataStartRow: settings.dataStartRow, + lastDataRow: settings.lastDataRow, + trimValues: settings.trimValues, + emptyStringAsNull: settings.emptyStringAsNull, + sheetName: settings.format === "excel" ? settings.sheetName || null : null, + jsonShape: settings.format === "json" ? settings.jsonShape : null, + }; +} + export function normalizeImportColumnName(name: string): string { return name.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " "); } diff --git a/crates/dbx-core/src/table_import.rs b/crates/dbx-core/src/table_import.rs index 921076c98..a8c893f02 100644 --- a/crates/dbx-core/src/table_import.rs +++ b/crates/dbx-core/src/table_import.rs @@ -3060,6 +3060,18 @@ mod tests { assert_eq!(xlsx_sheet_names(&path.to_string_lossy()).unwrap(), vec!["First", "Second"]); assert_eq!(parsed.columns, vec!["name"]); assert_eq!(parsed.rows, vec![vec![serde_json::json!("Ada")]]); + assert_eq!( + mapping_indexes( + &parsed, + &[TableImportColumnMapping { + source_column: "name".to_string(), + target_column: "display_name".to_string(), + target_data_type: None, + }], + ) + .unwrap(), + vec![(0, "display_name".to_string())] + ); let _ = std::fs::remove_file(path); }