fix(query): show timeout settings for structured errors
This commit is contained in:
5 files changed
+67
-4
No files matched your search
@@ -2,10 +2,12 @@
|
||||
import { Bot, Wrench } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { BackendError } from "@/lib/backend/errorUtils";
|
||||
import { isQueryTimeoutErrorMessage } from "@/lib/sql/queryError";
|
||||
|
||||
const props = defineProps<{
|
||||
errorMessage: string;
|
||||
backendError?: BackendError;
|
||||
connectionId?: string;
|
||||
}>();
|
||||
|
||||
@@ -18,7 +20,7 @@ const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button v-if="connectionId && isQueryTimeoutErrorMessage(errorMessage)" variant="outline" size="sm" class="h-7 gap-1.5 px-2.5 text-xs" @click="emit('changeQueryTimeout')">
|
||||
<Button v-if="connectionId && isQueryTimeoutErrorMessage(errorMessage, backendError)" variant="outline" size="sm" class="h-7 gap-1.5 px-2.5 text-xs" @click="emit('changeQueryTimeout')">
|
||||
<Wrench class="h-3.5 w-3.5" />
|
||||
{{ t("editor.changeQueryTimeout") }}
|
||||
</Button>
|
||||
|
||||
@@ -1634,7 +1634,13 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
||||
/>
|
||||
</template>
|
||||
<template v-if="activeTab.result && isQueryExecutionErrorResult(activeTab.result)" #error-actions="{ errorMessage }">
|
||||
<QueryErrorActions :error-message="String(errorMessage)" :connection-id="activeTab.connectionId" @change-query-timeout="activeTab.connectionId && emit('openConnectionSettings', activeTab.connectionId, 'advanced')" @fix-with-ai="(message) => emit('fixWithAi', message)" />
|
||||
<QueryErrorActions
|
||||
:error-message="String(errorMessage)"
|
||||
:backend-error="activeTab.result.error"
|
||||
:connection-id="activeResultConnectionId"
|
||||
@change-query-timeout="activeResultConnectionId && emit('openConnectionSettings', activeResultConnectionId, 'advanced')"
|
||||
@fix-with-ai="(message) => emit('fixWithAi', message)"
|
||||
/>
|
||||
</template>
|
||||
</DataGrid>
|
||||
<QueryLoadingState
|
||||
@@ -1962,7 +1968,13 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
||||
@sort="(column: string, columnIndex: number, direction: 'asc' | 'desc' | null, whereInput?: string, mode?: DataGridSortMode) => emit('sort', column, columnIndex, direction, whereInput, mode)"
|
||||
>
|
||||
<template v-if="activeTab.result && isQueryExecutionErrorResult(activeTab.result)" #error-actions="{ errorMessage }">
|
||||
<QueryErrorActions :error-message="String(errorMessage)" :connection-id="activeTab.connectionId" @change-query-timeout="activeTab.connectionId && emit('openConnectionSettings', activeTab.connectionId, 'advanced')" @fix-with-ai="(message) => emit('fixWithAi', message)" />
|
||||
<QueryErrorActions
|
||||
:error-message="String(errorMessage)"
|
||||
:backend-error="activeTab.result.error"
|
||||
:connection-id="activeResultConnectionId"
|
||||
@change-query-timeout="activeResultConnectionId && emit('openConnectionSettings', activeResultConnectionId, 'advanced')"
|
||||
@fix-with-ai="(message) => emit('fixWithAi', message)"
|
||||
/>
|
||||
</template>
|
||||
</DataGrid>
|
||||
<QueryLoadingState v-else-if="activeTab.isExecuting" class="h-full" :label-key="queryExecutionLabelKey(activeTab)" :elapsed-seconds="queryRunningElapsedSeconds" show-cancel :cancel-disabled="!canCancelQueryExecution(activeTab)" :cancelling="activeTab.isCancelling" @cancel="emit('cancel')" />
|
||||
|
||||
@@ -26,6 +26,33 @@ describe("isQueryTimeoutErrorMessage", () => {
|
||||
expect(isQueryTimeoutErrorMessage("Agent RPC error (-1): Agent RPC call timed out (120s)")).toBe(true);
|
||||
});
|
||||
|
||||
it("detects structured query operation timeouts before localized text matching", () => {
|
||||
const timeoutError = {
|
||||
version: 1,
|
||||
code: "DBX-JDBC-2002",
|
||||
messageKey: "backendErrors.jdbc.operationTimedOut",
|
||||
messageParams: { stage: "execute" },
|
||||
source: "jdbcAgent",
|
||||
operationOutcome: "unknown" as const,
|
||||
} as const;
|
||||
|
||||
expect(isQueryTimeoutErrorMessage("数据库操作超时(阶段:execute)。", timeoutError)).toBe(true);
|
||||
expect(isQueryTimeoutErrorMessage("Database operation timed out (stage: fetch).", { ...timeoutError, messageParams: { stage: "fetch" } })).toBe(true);
|
||||
});
|
||||
|
||||
it("does not offer query timeout settings for structured infrastructure timeouts", () => {
|
||||
const timeoutError = {
|
||||
version: 1,
|
||||
code: "DBX-JDBC-2001",
|
||||
messageKey: "backendErrors.jdbc.operationTimedOut",
|
||||
messageParams: { stage: "connect" },
|
||||
source: "jdbcAgent",
|
||||
operationOutcome: "not_started" as const,
|
||||
} as const;
|
||||
|
||||
expect(isQueryTimeoutErrorMessage("Database operation timed out (stage: connect).", timeoutError)).toBe(false);
|
||||
});
|
||||
|
||||
it("does not classify unrelated errors as query timeouts", () => {
|
||||
expect(isQueryTimeoutErrorMessage('syntax error at or near "select"')).toBe(false);
|
||||
expect(isQueryTimeoutErrorMessage("Connection timed out while loading databases")).toBe(false);
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export function isQueryTimeoutErrorMessage(message: string): boolean {
|
||||
import type { BackendError } from "@/lib/backend/errorUtils";
|
||||
|
||||
const QUERY_TIMEOUT_STAGES = new Set(["execute", "fetch"]);
|
||||
|
||||
export function isQueryTimeoutErrorMessage(message: string, backendError?: BackendError): boolean {
|
||||
if (backendError?.messageKey === "backendErrors.jdbc.operationTimedOut") {
|
||||
const stage = String(backendError.messageParams.stage ?? "").toLowerCase();
|
||||
return QUERY_TIMEOUT_STAGES.has(stage);
|
||||
}
|
||||
const lower = message.toLowerCase();
|
||||
if (lower.includes("query timed out") || lower.includes("查询超时") || lower.includes("查詢逾時")) return true;
|
||||
// Agent RPC client-side timeout (tokio::time::timeout in agent_driver.rs). This is the
|
||||
|
||||
Reference in new issue
Block a user