fix(import): preserve selected Excel sheet during execution

This commit is contained in:
t8y2 2026-07-21 13:59:30 +08:00
parent cda8c4c8e2
commit 177545dca8
4 changed files with 69 additions and 20 deletions

View File

@ -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<api.TableImportParseOptions>(() => ({
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<api.TableImportParseOptions>(() => 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>): 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",

View File

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

View File

@ -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, " ");
}

View File

@ -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);
}