From 02a56e4f641dd35d2d4d7e338551f8cbe428c528 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 12 Aug 2026 01:01:47 +0000 Subject: [PATCH] fix(grid): show structured copy errors Closes #5401 --- .../__tests__/useDataGridExport.spec.ts | 16 ++++++++++++++++ .../src/composables/useDataGridExtractor.ts | 7 ++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts index ec6e5ccfce..e73c78b5de 100644 --- a/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts +++ b/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts @@ -644,6 +644,22 @@ describe("useDataGridExport prepared row statements", () => { expect(toast).not.toHaveBeenCalledWith("grid.copyExtractorWarningOmittedColumns", 5000); }); + it.each([ + ["output limit", { code: "output-too-large", message: "Extracted clipboard output exceeds 32 MiB; export the data to a file instead." }, "Extracted clipboard output exceeds 32 MiB; export the data to a file instead."], + ["input limit", { code: "input-too-large", message: "Selected data is too large for clipboard extraction; export the data to a file instead." }, "Selected data is too large for clipboard extraction; export the data to a file instead."], + ["Error", new Error("extractor failed"), "extractor failed"], + ["string", "extractor failed", "extractor failed"], + ["null", null, "Unknown error occurred"], + ])("formats %s extractor failures", async (_label, rejection, expectedMessage) => { + const matrix: CellSelectionMatrix = { rowIndexes: [0], columnIndexes: [0], columns: ["id"], rows: [[1]] }; + vi.mocked(extractDataGridSelection).mockRejectedValueOnce(rejection); + const state = createExportState(editableTable, ["id"], matrix, [1]); + + await expect(state.copyWithExtractor("csv")).resolves.toBe(false); + + expect(toast).toHaveBeenCalledWith(`grid.copyFailed: ${expectedMessage}`, 5000); + }); + it("rejects irregular discrete cell selections before building an extractor request", async () => { const state = createExportState(editableTable, ["id", "name"], undefined, undefined, { columns: ["id", "name"], diff --git a/apps/desktop/src/composables/useDataGridExtractor.ts b/apps/desktop/src/composables/useDataGridExtractor.ts index 889df046c2..abd4d89460 100644 --- a/apps/desktop/src/composables/useDataGridExtractor.ts +++ b/apps/desktop/src/composables/useDataGridExtractor.ts @@ -19,6 +19,7 @@ import { type DataGridExtractWarningCode, } from "@/lib/dataGrid/dataGridCopyExtractor"; import type { DataGridTableMeta } from "@/lib/dataGrid/dataGridSql"; +import { formatError } from "@/lib/backend/errorUtils"; import type { DatabaseType } from "@/types/database"; interface ExtractorRowItem { @@ -245,7 +246,7 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) { showWarnings(result.warnings, result.omittedColumns); return true; } catch (error: unknown) { - toast(t("grid.copyFailed", { message: errorMessage(error) }), 5000); + toast(t("grid.copyFailed", { message: formatError(error) }), 5000); return false; } } @@ -295,10 +296,6 @@ function compactTableMeta(tableMeta: DataGridTableMeta | undefined, requiredColu return { ...tableMeta, columns: tableMeta.columns.filter((column) => requiredNames.has(normalizeName(column.name))) }; } -function errorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - function normalizeName(name: string): string { const trimmed = name.trim(); const first = trimmed[0];