feat(export): support safe background query exports
This commit is contained in:
1 parent
2f44d36018
commit
a07def6042
17 files changed
+577
-195
No files matched your search
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated, watch, shallowRef, computed, nextTick } from "vue";
|
||||
import { CaseLower, CaseUpper, Code2, FileCode, Pencil, PencilRuler, Play, Copy, List, Search, Sparkles, Table2, TextSelect, Trash2 } from "@lucide/vue";
|
||||
import { CaseLower, CaseUpper, Code2, Download, FileCode, Pencil, PencilRuler, Play, Copy, List, Search, Sparkles, Table2, TextSelect, Trash2 } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { CompletionContext } from "@codemirror/autocomplete";
|
||||
import { Transaction, StateEffect } from "@codemirror/state";
|
||||
@@ -138,6 +138,7 @@ const emit = defineEmits<{
|
||||
formatError: [message: string];
|
||||
execute: [source: SqlExecutionOverride];
|
||||
executeInNewResultTab: [source: SqlExecutionOverride];
|
||||
exportQuery: [payload: { sql: string; format: "csv" | "xlsx" | "txt" }];
|
||||
save: [];
|
||||
clickTable: [target: SqlObjectNavigationTarget];
|
||||
viewTableData: [target: SqlObjectNavigationTarget];
|
||||
@@ -1000,6 +1001,12 @@ function executeInNewResultTabFromContextMenu() {
|
||||
focusEditor();
|
||||
}
|
||||
|
||||
function exportQueryFromContextMenu(format: "csv" | "xlsx" | "txt") {
|
||||
const sql = executableSql.value;
|
||||
if (!sql.trim()) return;
|
||||
emit("exportQuery", { sql, format });
|
||||
}
|
||||
|
||||
async function copySelectedSqlFromContextMenu() {
|
||||
if (!canCopySelectedSql.value) return;
|
||||
try {
|
||||
@@ -1274,6 +1281,16 @@ const contextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
icon: Play,
|
||||
shortcut: shortcuts.executeSqlInNewResultTab,
|
||||
},
|
||||
{
|
||||
label: t("editor.contextMenu.export"),
|
||||
icon: Download,
|
||||
disabled: !canExecuteContextSql.value,
|
||||
children: [
|
||||
{ label: t("editor.contextMenu.exportQueryResultTo", { format: "CSV" }), action: () => exportQueryFromContextMenu("csv") },
|
||||
{ label: t("editor.contextMenu.exportQueryResultTo", { format: "XLSX" }), action: () => exportQueryFromContextMenu("xlsx") },
|
||||
{ label: t("editor.contextMenu.exportQueryResultTo", { format: "TXT" }), action: () => exportQueryFromContextMenu("txt") },
|
||||
],
|
||||
},
|
||||
]),
|
||||
...queryContextObjectActions(contextObjectTarget.value?.type).map(contextObjectMenuItem),
|
||||
{ label: "", separator: true },
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Loader2, CheckCircle2, XCircle, AlertCircle, FolderOpen, X } from "@lucide/vue";
|
||||
import { Loader2, CheckCircle2, XCircle, AlertCircle, FolderOpen, Minimize2, X } from "@lucide/vue";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import * as api from "@/lib/backend/api";
|
||||
@@ -24,10 +24,12 @@ const props = defineProps<{
|
||||
errorMessage: string | null;
|
||||
filePath?: string | null;
|
||||
disableCancel?: boolean;
|
||||
canMinimize?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: [];
|
||||
minimize: [];
|
||||
"update:open": [value: boolean];
|
||||
}>();
|
||||
|
||||
@@ -123,6 +125,10 @@ async function revealExportFile() {
|
||||
|
||||
<DialogFooter>
|
||||
<template v-if="isActive">
|
||||
<Button v-if="canMinimize" variant="ghost" size="sm" @click="emit('minimize')">
|
||||
<Minimize2 class="h-3.5 w-3.5 mr-1" />
|
||||
{{ t("exportProgress.minimize") }}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" :disabled="disableCancel" @click="emit('cancel')">
|
||||
<X class="h-3.5 w-3.5 mr-1" />
|
||||
{{ t("exportProgress.cancel") }}
|
||||
|
||||
@@ -5121,6 +5121,7 @@ const exportProgressState = ref({
|
||||
filePath: null as string | null,
|
||||
});
|
||||
const exportCancelHandler = ref<(() => Promise<void>) | null>(null);
|
||||
const exportCanMinimize = ref(false);
|
||||
|
||||
async function cancelActiveExport() {
|
||||
await exportCancelHandler.value?.();
|
||||
@@ -5196,6 +5197,7 @@ const {
|
||||
exportProgressDialog,
|
||||
exportProgressState,
|
||||
exportCancelHandler,
|
||||
exportCanMinimize,
|
||||
});
|
||||
|
||||
function copyExtractorLabel(extractor: DataGridCopyExtractorId): string {
|
||||
@@ -9302,7 +9304,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
/>
|
||||
<ImagePreviewDialog v-if="imagePreviewMounted" v-model:open="imagePreviewOpen" :src="imagePreviewSrc" :title="imagePreviewTitle" />
|
||||
<component v-if="previewDialogOpen && previewDialogConfig" :is="previewDialogConfig.component" v-model:open="previewDialogOpen" v-bind="previewDialogConfig.props" />
|
||||
<ExportProgressDialog v-if="exportProgressDialogMounted" v-model:open="exportProgressDialog" v-bind="exportProgressState" :disable-cancel="!exportCancelHandler" @cancel="cancelActiveExport" />
|
||||
<ExportProgressDialog v-if="exportProgressDialogMounted" v-model:open="exportProgressDialog" v-bind="exportProgressState" :disable-cancel="!exportCancelHandler" :can-minimize="exportCanMinimize" @cancel="cancelActiveExport" @minimize="exportProgressDialog = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore, type DataGridSearchMode, type ResultRunDisplayMode } from "@/stores/settingsStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/sql/queryExecutionState";
|
||||
import { databaseDisplayNameForTab, executionSummaryItems, queryResultExecutionSql, resultGridCacheKey, resultRunItems, resultSourceRange, resultSqlForGrid, statementExecutionMarkers, tabularResultItems } from "@/lib/tabs/tabPresentation";
|
||||
import { defaultQueryResultArchiveFileName } from "@/lib/query/queryResultArchive";
|
||||
@@ -865,6 +866,20 @@ function requestQueryEditorExecuteInNewResultTab() {
|
||||
return queryEditorRef.value?.requestExecuteInNewResultTab();
|
||||
}
|
||||
|
||||
async function handleExportQuery(payload: { sql: string; format: "csv" | "xlsx" | "txt" }) {
|
||||
const tab = props.activeTab;
|
||||
if (!tab || tab.mode !== "query") return;
|
||||
let filePath = `query-result.${payload.format}`;
|
||||
if (isTauriRuntime()) {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const filterName = payload.format === "csv" ? "CSV" : payload.format === "xlsx" ? "Excel" : "Text";
|
||||
const picked = await save({ defaultPath: filePath, filters: [{ name: filterName, extensions: [payload.format] }] });
|
||||
if (!picked) return;
|
||||
filePath = picked as string;
|
||||
}
|
||||
await queryStore.exportQuerySqlDirect(tab.id, payload.sql, payload.format, filePath);
|
||||
}
|
||||
|
||||
function pasteClipboardAsSqlInCondition() {
|
||||
return queryEditorRef.value?.pasteClipboardAsSqlInCondition();
|
||||
}
|
||||
@@ -932,6 +947,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
||||
@format-error="emit('formatError')"
|
||||
@execute="emit('execute', $event)"
|
||||
@execute-in-new-result-tab="emit('executeInNewResultTab', $event)"
|
||||
@export-query="handleExportQuery"
|
||||
@save="emit('saveSql')"
|
||||
@click-table="onHandleClickTable"
|
||||
@view-table-data="onHandleViewTableData"
|
||||
|
||||
@@ -128,6 +128,7 @@ export interface UseDataGridExportOptions {
|
||||
filePath: string | null;
|
||||
}>;
|
||||
exportCancelHandler?: Ref<(() => Promise<void>) | null>;
|
||||
exportCanMinimize?: Ref<boolean>;
|
||||
}
|
||||
|
||||
interface CopyInsertData {
|
||||
@@ -140,6 +141,7 @@ interface CopyInsertData {
|
||||
export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const tracker = useExportTracker();
|
||||
const exportGuard: ActionActivationGuard = {};
|
||||
const { addTask, updateTableExportTask, registerTaskCancelHandler, unregisterTaskCancelHandler, removeTask } = useExportTracker();
|
||||
|
||||
@@ -185,6 +187,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
exportProgressDialog,
|
||||
exportProgressState,
|
||||
exportCancelHandler,
|
||||
exportCanMinimize,
|
||||
} = options;
|
||||
const selectedCellMatrix = selectedCellMatrixOption;
|
||||
const allColumns = allColumnsOption ?? columns;
|
||||
@@ -906,11 +909,14 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
};
|
||||
}
|
||||
if (exportProgressDialog) exportProgressDialog.value = true;
|
||||
if (exportCanMinimize) exportCanMinimize.value = true;
|
||||
|
||||
const exportId = uuid();
|
||||
const task = tracker.addTask(meta.tableName, format, outputPath);
|
||||
const exportId = task.exportId;
|
||||
if (exportCancelHandler) {
|
||||
exportCancelHandler.value = () => api.cancelTableExport(exportId);
|
||||
}
|
||||
tracker.registerTaskCancelHandler(exportId, () => api.cancelTableExport(exportId));
|
||||
const editorSettings = useSettingsStore().editorSettings;
|
||||
const rowLimit = editorSettings.exportRowLimitEnabled ? editorSettings.exportRowLimit : null;
|
||||
|
||||
@@ -946,6 +952,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
errorMessage: progress.errorMessage || null,
|
||||
};
|
||||
}
|
||||
tracker.updateTableExportTask(exportId, progress);
|
||||
},
|
||||
);
|
||||
if (progress.status === "Done") {
|
||||
@@ -953,6 +960,8 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
}
|
||||
} finally {
|
||||
if (exportCancelHandler) exportCancelHandler.value = null;
|
||||
tracker.unregisterTaskCancelHandler(exportId);
|
||||
if (exportCanMinimize) exportCanMinimize.value = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -997,9 +1006,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
};
|
||||
}
|
||||
if (exportProgressDialog) exportProgressDialog.value = true;
|
||||
if (exportCanMinimize) exportCanMinimize.value = true;
|
||||
tracker.addTask("Query Result", format, outputPath, exportId);
|
||||
if (exportCancelHandler) {
|
||||
exportCancelHandler.value = () => api.cancelQueryResultExport(exportId, request.executionId);
|
||||
}
|
||||
tracker.registerTaskCancelHandler(exportId, () => api.cancelQueryResultExport(exportId, request.executionId));
|
||||
|
||||
try {
|
||||
const terminalProgress = await api.startQueryResultExport(request, (progress) => {
|
||||
@@ -1014,12 +1026,15 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
errorMessage: progress.errorMessage || null,
|
||||
};
|
||||
}
|
||||
tracker.updateTableExportTask(exportId, progress);
|
||||
});
|
||||
if (terminalProgress.status === "Done") {
|
||||
toast(t("grid.exported"));
|
||||
}
|
||||
} finally {
|
||||
if (exportCancelHandler) exportCancelHandler.value = null;
|
||||
tracker.unregisterTaskCancelHandler(exportId);
|
||||
if (exportCanMinimize) exportCanMinimize.value = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -229,10 +229,10 @@ export function useExportTracker() {
|
||||
.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
||||
}
|
||||
|
||||
function addTask(tableName: string, format: string, filePath: string): ExportTask {
|
||||
const exportId = generateUUID();
|
||||
function addTask(tableName: string, format: string, filePath: string, exportId?: string): ExportTask {
|
||||
const id = exportId ?? generateUUID();
|
||||
const task = reactive<ExportTask>({
|
||||
exportId,
|
||||
exportId: id,
|
||||
kind: "table-export",
|
||||
tableName,
|
||||
format,
|
||||
@@ -242,7 +242,7 @@ export function useExportTracker() {
|
||||
status: "Running",
|
||||
errorMessage: null,
|
||||
});
|
||||
taskMap.set(exportId, task);
|
||||
taskMap.set(id, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
@@ -819,6 +819,8 @@ export default {
|
||||
contextMenu: {
|
||||
executeSelection: "Execute selection",
|
||||
executeCurrent: "Execute SQL",
|
||||
export: "Export",
|
||||
exportQueryResultTo: "Export current query result to {format}",
|
||||
copySelection: "Copy selection",
|
||||
sendToAi: "Send to AI",
|
||||
uppercaseSelection: "Convert to uppercase",
|
||||
@@ -1399,6 +1401,7 @@ export default {
|
||||
rowsExported: "{count} rows exported",
|
||||
rowsShort: "rows",
|
||||
cancel: "Cancel",
|
||||
minimize: "Minimize to background",
|
||||
openFolder: "Open containing folder",
|
||||
openFolderFailed: "Failed to open folder: {message}",
|
||||
close: "Close",
|
||||
|
||||
@@ -799,6 +799,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "Ejecutar seleccion",
|
||||
executeCurrent: "Ejecutar SQL",
|
||||
export: "Exportar",
|
||||
exportQueryResultTo: "Exportar el conjunto de resultados de la consulta actual a {format}",
|
||||
copySelection: "Copiar seleccion",
|
||||
sendToAi: "Enviar a IA",
|
||||
uppercaseSelection: "Convertir a mayusculas",
|
||||
@@ -1342,6 +1344,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "{count} filas exportadas",
|
||||
rowsShort: "filas",
|
||||
cancel: "Cancelar",
|
||||
minimize: "Minimizar a segundo plano",
|
||||
openFolder: "Abrir carpeta contenedora",
|
||||
openFolderFailed: "No se pudo abrir la carpeta: {message}",
|
||||
close: "Cerrar",
|
||||
|
||||
@@ -797,6 +797,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "Esegui selezione",
|
||||
executeCurrent: "Esegui SQL",
|
||||
export: "Esporta",
|
||||
exportQueryResultTo: "Esporta il set di risultati della query corrente in {format}",
|
||||
copySelection: "Copia selezione",
|
||||
sendToAi: "Invia ad AI",
|
||||
uppercaseSelection: "Converti in maiuscolo",
|
||||
@@ -1340,6 +1342,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "{count} righe esportate",
|
||||
rowsShort: "righe",
|
||||
cancel: "Annulla",
|
||||
minimize: "Minimizza in background",
|
||||
openFolder: "Apri cartella contenente",
|
||||
openFolderFailed: "Impossibile aprire la cartella: {message}",
|
||||
close: "Chiudi",
|
||||
|
||||
@@ -796,6 +796,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "選択範囲を実行",
|
||||
executeCurrent: "SQLを実行",
|
||||
export: "エクスポート",
|
||||
exportQueryResultTo: "現在のクエリ結果セットを {format} にエクスポート",
|
||||
copySelection: "選択範囲をコピー",
|
||||
sendToAi: "AIに送信",
|
||||
uppercaseSelection: "大文字に変換",
|
||||
@@ -1341,6 +1343,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "{count}行をエクスポートしました",
|
||||
rowsShort: "行",
|
||||
cancel: "キャンセル",
|
||||
minimize: "バックグラウンドに最小化",
|
||||
openFolder: "保存先フォルダーを開く",
|
||||
openFolderFailed: "フォルダーを開けませんでした:{message}",
|
||||
close: "閉じる",
|
||||
|
||||
@@ -798,6 +798,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "Executar seleção",
|
||||
executeCurrent: "Executar SQL",
|
||||
export: "Exportar",
|
||||
exportQueryResultTo: "Exportar conjunto de resultados da consulta atual para {format}",
|
||||
copySelection: "Copiar seleção",
|
||||
sendToAi: "Enviar para IA",
|
||||
uppercaseSelection: "Converter para maiúsculas",
|
||||
@@ -1342,6 +1344,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "{count} linhas exportadas",
|
||||
rowsShort: "linhas",
|
||||
cancel: "Cancelar",
|
||||
minimize: "Minimizar para segundo plano",
|
||||
openFolder: "Abrir pasta do arquivo",
|
||||
openFolderFailed: "Falha ao abrir a pasta: {message}",
|
||||
close: "Fechar",
|
||||
|
||||
@@ -820,6 +820,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "执行选中 SQL",
|
||||
executeCurrent: "执行 SQL",
|
||||
export: "导出",
|
||||
exportQueryResultTo: "导出当前查询结果集到 {format}",
|
||||
copySelection: "复制选中内容",
|
||||
sendToAi: "发送到 AI",
|
||||
uppercaseSelection: "转为大写",
|
||||
@@ -1400,6 +1402,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "已导出 {count} 行",
|
||||
rowsShort: "行",
|
||||
cancel: "取消",
|
||||
minimize: "最小化到后台",
|
||||
openFolder: "打开所在文件夹",
|
||||
openFolderFailed: "打开文件夹失败:{message}",
|
||||
close: "关闭",
|
||||
|
||||
@@ -797,6 +797,8 @@ export default withEnglishFallback({
|
||||
contextMenu: {
|
||||
executeSelection: "執行選取 SQL",
|
||||
executeCurrent: "執行 SQL",
|
||||
export: "匯出",
|
||||
exportQueryResultTo: "匯出目前查詢結果集到 {format}",
|
||||
copySelection: "複製選取內容",
|
||||
sendToAi: "傳送至 AI",
|
||||
uppercaseSelection: "轉為大寫",
|
||||
@@ -1341,6 +1343,7 @@ export default withEnglishFallback({
|
||||
rowsExported: "已匯出 {count} 列",
|
||||
rowsShort: "列",
|
||||
cancel: "取消",
|
||||
minimize: "最小化到背景",
|
||||
openFolder: "開啟所在資料夾",
|
||||
openFolderFailed: "開啟資料夾失敗:{message}",
|
||||
close: "關閉",
|
||||
|
||||
@@ -58,6 +58,7 @@ import * as api from "@/lib/backend/api";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
import { useExportTracker } from "@/composables/useExportTracker";
|
||||
import { recordQueryCancellationLatency, resourceLifecycleDiagnostics } from "@/lib/diagnostics/resourceLifecycleDiagnostics";
|
||||
import { appendDebugLog } from "@/lib/backend/debugLog";
|
||||
import { formatError } from "@/lib/backend/errorUtils";
|
||||
@@ -4770,7 +4771,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const setupSql = batchStatements[resultStatementIndex!]?.sql === tab.result.sourceStatement ? batchStatements.slice(0, resultStatementIndex).map((statement) => statement.sql) : undefined;
|
||||
const rowLimit = settings.exportRowLimitEnabled ? settings.exportRowLimit : null;
|
||||
const totalRows = typeof tab.resultTotalRowCount === "number" ? (rowLimit === null ? tab.resultTotalRowCount : Math.min(tab.resultTotalRowCount, rowLimit)) : null;
|
||||
const clientSessionId = tabClientSessionId(tab, "export");
|
||||
const clientSessionId = `${tabClientSessionId(tab, "export")}:${options.exportId}`;
|
||||
|
||||
return {
|
||||
exportId: options.exportId,
|
||||
@@ -4798,6 +4799,58 @@ export const useQueryStore = defineStore("query", () => {
|
||||
};
|
||||
}
|
||||
|
||||
async function exportQuerySqlDirect(id: string, sql: string, format: "csv" | "xlsx" | "txt", filePath: string) {
|
||||
const tab = tabs.value.find((item) => item.id === id);
|
||||
if (!tab || tab.mode !== "query" || !sql.trim()) return;
|
||||
|
||||
const connStore = useConnectionStore();
|
||||
await connStore.ensureConnected(tab.connectionId);
|
||||
const conn = connStore.getConfig(tab.connectionId);
|
||||
const settings = useSettingsStore().editorSettings;
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(conn);
|
||||
if (!effectiveDbType) return;
|
||||
|
||||
const exportId = uuid();
|
||||
const request: api.QueryResultExportRequest = {
|
||||
exportId,
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
schema: tab.schema,
|
||||
sql,
|
||||
queryBaseSql: sql,
|
||||
databaseType: effectiveDbType,
|
||||
useAgentCursor: usesAgentCursorForQuery(conn?.db_type),
|
||||
filePath,
|
||||
format,
|
||||
pageSize: settings.exportBatchSize,
|
||||
rowLimit: settings.exportRowLimitEnabled ? settings.exportRowLimit : null,
|
||||
totalRows: null,
|
||||
timeoutSecs: queryTimeoutSecsForConnection(conn),
|
||||
keysetOptimizationEnabled: settings.queryExportKeysetOptimizationEnabled,
|
||||
clientSessionId: `${tabClientSessionId(tab, "export")}:${exportId}`,
|
||||
executionId: uuid(),
|
||||
numericColumnRightAlign: settings.numericColumnRightAlign,
|
||||
};
|
||||
|
||||
const tracker = useExportTracker();
|
||||
tracker.addTask("Query Result", format, filePath, request.exportId);
|
||||
tracker.registerTaskCancelHandler(request.exportId, () => api.cancelQueryResultExport(request.exportId, request.executionId));
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
await api.startQueryResultExport(request, (progress) => tracker.updateTableExportTask(request.exportId, progress));
|
||||
} catch (error: any) {
|
||||
const task = tracker.tasks.value.find((item) => item.exportId === request.exportId);
|
||||
if (task) {
|
||||
task.status = "Error";
|
||||
task.errorMessage = error?.message || String(error);
|
||||
}
|
||||
} finally {
|
||||
tracker.unregisterTaskCancelHandler(request.exportId);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTabId,
|
||||
@@ -4900,6 +4953,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
importResultArchive,
|
||||
fetchTabResultForExport,
|
||||
buildQueryResultExportRequest,
|
||||
exportQuerySqlDirect,
|
||||
getResourceLifecycleDiagnostics: () => resourceLifecycleDiagnostics(tabs.value),
|
||||
notifyConnectionMayBeLost,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user