From ac19202b5a41a76cf10dfb2630118c4e34dd5e89 Mon Sep 17 00:00:00 2001 From: Dhruv Shah Date: Wed, 12 Aug 2026 17:05:51 -0400 Subject: [PATCH] Fix multi-statement result export --- src/pages/Editor.tsx | 69 ++++++++++++++++++++++++++++++-- src/utils/resultExport.ts | 52 ++++++++++++++++++++++++ tests/utils/resultExport.test.ts | 57 ++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 src/utils/resultExport.ts create mode 100644 tests/utils/resultExport.test.ts diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index e448f019..b3d077f6 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -3,6 +3,7 @@ import { useLocation, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { reconstructTableQuery } from "../utils/editor"; import { formatRowsForCopy, copyTextToClipboard } from "../utils/clipboard"; +import { formatResultForExport } from "../utils/resultExport"; import { serializePkKey, buildPkMap } from "../utils/dataGrid"; import { buildKeylessUpdatePlan, @@ -69,6 +70,7 @@ import { } from "lucide-react"; import { invoke } from "@tauri-apps/api/core"; import { listen, emit } from "@tauri-apps/api/event"; +import { writeTextFile } from "@tauri-apps/plugin-fs"; import { TableToolbar } from "../components/ui/TableToolbar"; import { DataGrid } from "../components/ui/DataGrid"; import { MultiResultPanel } from "../components/ui/MultiResultPanel"; @@ -97,6 +99,7 @@ import { removeOtherEntries, removeEntriesToRight, removeEntriesToLeft, + findActiveEntry, } from "../utils/multiResult"; import { extractQueryParams, @@ -467,6 +470,16 @@ export const Editor = ({ commandScopeId }: EditorProps) => { const isMultiDb = usesMultiDatabaseLayout(activeCapabilities, selectedDatabases); const isEditorOpen = !isTableTab && (activeTab?.isEditorOpen ?? activeTab?.type !== "table"); + const activeResultEntry = useMemo( + () => + activeTab?.results + ? findActiveEntry(activeTab.results, activeTab.activeResultId) + : undefined, + [activeTab?.activeResultId, activeTab?.results], + ); + const activeExportResult = activeResultEntry?.result ?? activeTab?.result; + const canExportActiveResult = + !!activeExportResult && activeExportResult.rows.length > 0; const handleCloseTab = useCallback( (tabId: string) => { @@ -1345,7 +1358,15 @@ export const Editor = ({ commandScopeId }: EditorProps) => { applied.add(idx); applyStatement(idx, item); }); - updateTab(targetTabId, { isLoading: false }); + const firstResultEntry = batchResults.findIndex( + (item) => (item.result?.rows.length ?? 0) > 0, + ); + updateTab(targetTabId, { + isLoading: false, + ...(firstResultEntry >= 0 + ? { activeResultId: entries[firstResultEntry].id } + : {}), + }); }, [ activeConnectionId, @@ -3272,6 +3293,49 @@ export const Editor = ({ commandScopeId }: EditorProps) => { const handleExportCommon = async (format: "csv" | "json" | "markdown") => { if (!activeTab || !activeConnectionId) return; + const extension = format === "markdown" ? "md" : format; + const multiResult = activeResultEntry?.result; + if (multiResult?.rows.length) { + try { + const filePath = await save({ + filters: [ + { + name: format === "markdown" ? "Markdown" : format.toUpperCase(), + extensions: [extension], + }, + ], + defaultPath: `result_${Date.now()}.${extension}`, + }); + + if (!filePath) return; + + setExportState({ + isOpen: true, + status: "exporting", + rowsProcessed: multiResult.rows.length, + fileName: filePath.split(/[/\\]/).pop() || filePath, + }); + setExportMenuOpen(false); + + await writeTextFile( + filePath, + formatResultForExport(multiResult, format, csvDelimiter), + ); + + setExportState((prev) => ({ + ...prev, + status: "completed", + })); + } catch (e) { + setExportState((prev) => ({ + ...prev, + status: "error", + errorMessage: String(e), + })); + } + return; + } + const effectiveSchema = activeCapabilities?.schemas === true ? activeTab.schema : undefined; const tabForQuery = { ...activeTab, schema: effectiveSchema }; @@ -3283,7 +3347,6 @@ export const Editor = ({ commandScopeId }: EditorProps) => { if (!query || !query.trim()) return; try { - const extension = format === "markdown" ? "md" : format; const filePath = await save({ filters: [ { @@ -3812,7 +3875,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => {