feat(xlsx): add optional auto filter to xlsx export
This commit is contained in:
29 files changed
+285
-119
No files matched your search
@@ -85,7 +85,7 @@ import { buildExecutableObjectSourceStatements, buildRoutineRenameObjectSourceSt
|
||||
import { buildRenameObjectSql, supportsObjectRename } from "@/lib/table/objectRenameSql";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { generateDatabaseExportId } from "@/lib/export/databaseExport";
|
||||
import { buildXlsxHeaderOverrides, hasXlsxHeaderComments, type XlsxHeaderMode } from "@/lib/export/xlsxHeader";
|
||||
import { buildXlsxHeaderOverrides, hasXlsxHeaderComments, type XlsxExportOptions, type XlsxHeaderMode } from "@/lib/export/xlsxHeader";
|
||||
import { copyToClipboard, eventTargetAllowsAppClipboardShortcut } from "@/lib/common/clipboard";
|
||||
import {
|
||||
defaultPasteTableMode,
|
||||
@@ -1999,16 +1999,17 @@ async function exportData(row: ObjectBrowserRow, format: "csv" | "json" | "sql")
|
||||
else await exportTableData(row, format);
|
||||
}
|
||||
|
||||
function showObjectBrowserXlsxHeaderDialog(hasComments: boolean): Promise<XlsxHeaderMode | null> {
|
||||
if (!hasComments) return Promise.resolve("name");
|
||||
function showObjectBrowserXlsxHeaderDialog(hasComments: boolean): Promise<XlsxExportOptions | null> {
|
||||
if (typeof document === "undefined") return Promise.resolve({ headerMode: "name", autoFilter: false });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const app = createApp(XlsxHeaderDialog, {
|
||||
open: true,
|
||||
onConfirm: (mode: XlsxHeaderMode) => {
|
||||
resolve(mode);
|
||||
showHeaderOptions: hasComments,
|
||||
onConfirm: (exportOptions: XlsxExportOptions) => {
|
||||
resolve(exportOptions);
|
||||
app.unmount();
|
||||
document.body.removeChild(container);
|
||||
},
|
||||
@@ -2025,22 +2026,20 @@ function showObjectBrowserXlsxHeaderDialog(hasComments: boolean): Promise<XlsxHe
|
||||
|
||||
async function exportDataXlsx(row: ObjectBrowserRow) {
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
let headerMode: XlsxHeaderMode = "name";
|
||||
let columnInfos: ColumnInfo[] | undefined;
|
||||
|
||||
try {
|
||||
columnInfos = await api.getColumns(props.connection.id, props.database, schema || props.database, row.name, props.catalog);
|
||||
const result = await showObjectBrowserXlsxHeaderDialog(hasXlsxHeaderComments(columnInfos.map((column) => column.comment)));
|
||||
if (result === null) return;
|
||||
headerMode = result;
|
||||
} catch {
|
||||
// Export still works with field-name headers when column metadata is unavailable.
|
||||
}
|
||||
|
||||
await exportTableData(row, "xlsx", columnInfos, headerMode);
|
||||
const exportOptions = await showObjectBrowserXlsxHeaderDialog(hasXlsxHeaderComments(columnInfos?.map((column) => column.comment)));
|
||||
if (exportOptions === null) return;
|
||||
await exportTableData(row, "xlsx", columnInfos, exportOptions.headerMode, exportOptions.autoFilter);
|
||||
}
|
||||
|
||||
async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "sql", columnInfos?: ColumnInfo[], headerMode: XlsxHeaderMode = "name") {
|
||||
async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "sql", columnInfos?: ColumnInfo[], headerMode: XlsxHeaderMode = "name", autoFilter = true) {
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
|
||||
// Save dialog first
|
||||
@@ -2080,7 +2079,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
|
||||
} else {
|
||||
const comments = result.columns.map((name) => columnInfos?.find((column) => column.name.toLocaleLowerCase() === name.toLocaleLowerCase())?.comment);
|
||||
const headerOverrides = buildXlsxHeaderOverrides(result.columns, comments, headerMode);
|
||||
await api.exportQueryResultXlsx(filePath, row.name, result.columns, result.column_types ?? result.columns.map(() => ""), headerOverrides, result.rows);
|
||||
await api.exportQueryResultXlsx(filePath, row.name, result.columns, result.column_types ?? result.columns.map(() => ""), headerOverrides, result.rows, undefined, autoFilter);
|
||||
}
|
||||
toast(t("grid.exported"));
|
||||
return;
|
||||
@@ -2116,6 +2115,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx" | "
|
||||
format,
|
||||
columns,
|
||||
columnComments: format === "xlsx" ? columnComments : undefined,
|
||||
autoFilter: format === "xlsx" ? autoFilter : undefined,
|
||||
batchSize: settingsStore.editorSettings.exportBatchSize,
|
||||
skipCount: format === "sql",
|
||||
rowLimit,
|
||||
|
||||
@@ -17,19 +17,19 @@ function functionBody(name: string): string {
|
||||
}
|
||||
|
||||
describe("ObjectBrowser XLSX export", () => {
|
||||
it("asks for a header mode before opening the save dialog when comments exist", () => {
|
||||
it("asks for export options before opening the save dialog", () => {
|
||||
const dialog = functionBody("showObjectBrowserXlsxHeaderDialog");
|
||||
const exportDataXlsx = functionBody("exportDataXlsx");
|
||||
|
||||
expect(dialog).toContain('if (!hasComments) return Promise.resolve("name")');
|
||||
expect(exportDataXlsx).toContain("hasXlsxHeaderComments(columnInfos.map((column) => column.comment))");
|
||||
expect(exportDataXlsx.indexOf("await showObjectBrowserXlsxHeaderDialog(")).toBeLessThan(exportDataXlsx.indexOf('await exportTableData(row, "xlsx", columnInfos, headerMode)'));
|
||||
expect(dialog).toContain("showHeaderOptions: hasComments");
|
||||
expect(exportDataXlsx).toContain("hasXlsxHeaderComments(columnInfos?.map((column) => column.comment))");
|
||||
expect(exportDataXlsx.indexOf("await showObjectBrowserXlsxHeaderDialog(")).toBeLessThan(exportDataXlsx.indexOf('await exportTableData(row, "xlsx", columnInfos, exportOptions.headerMode, exportOptions.autoFilter)'));
|
||||
});
|
||||
|
||||
it("falls back to field-name headers when column metadata is unavailable", () => {
|
||||
const exportDataXlsx = functionBody("exportDataXlsx");
|
||||
|
||||
expect(exportDataXlsx).toContain("catch {\n // Export still works with field-name headers when column metadata is unavailable.\n }");
|
||||
expect(exportDataXlsx).toContain('await exportTableData(row, "xlsx", columnInfos, headerMode)');
|
||||
expect(exportDataXlsx).toContain('await exportTableData(row, "xlsx", columnInfos, exportOptions.headerMode, exportOptions.autoFilter)');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user