From dab1d145bc3bfba3665b9dfb2dcd3094343400b2 Mon Sep 17 00:00:00 2001 From: zipg Date: Tue, 30 Jun 2026 01:52:37 +0800 Subject: [PATCH] fix(connection): show built-in driver update hint on connection failure (#2111) --- .../connection/ConnectionDialog.vue | 11 +++- apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + .../desktop/src/lib/agentDriverInstallHint.ts | 14 +++++ apps/desktop/src/stores/connectionStore.ts | 43 ++++++++++++++ .../app-tests/agentDriverInstallHint.test.ts | 57 +++++++++++-------- 6 files changed, 99 insertions(+), 28 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 5160941cd5..4ef64cd322 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -32,7 +32,7 @@ import { isLocalFileTypeDb } from "@/lib/connectionFile"; import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedVersion } from "@/lib/mqPinnedVersionOptions"; import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions"; import { copyToClipboard } from "@/lib/clipboard"; -import { showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint"; +import { appendAgentDriverUpdateHint, hasAgentDriverUpdate, showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint"; import { prestoSqlBuiltinDriverPaths } from "@/lib/prestoSqlBuiltinDriver"; import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/databaseFileDetection"; import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connectionAttemptTimeout"; @@ -757,6 +757,11 @@ function errorMessage(error: unknown): string { return String(error); } +function connectionErrorWithDriverUpdateHint(config: ConnectionConfig, message: string): string { + if (!hasAgentDriverUpdate(config.db_type, agentDrivers.value, config.driver_profile)) return message; + return appendAgentDriverUpdateHint(message, t("connection.agentDriverUpdateConnectionHint")); +} + async function testConnectionWithTimeout(config: ConnectionConfig, runId: number): Promise { const timeoutMs = connectionAttemptTimeoutMs(config); const timeoutMessage = connectionAttemptTimeoutMessage(timeoutMs); @@ -768,7 +773,7 @@ async function testConnectionWithTimeout(config: ConnectionConfig, runId: number if (runId !== testRunId) return; testResult.value = { ok: false, - message: connectionAttemptOriginalErrorMessage(timeoutMessage, errorMessage(error)), + message: connectionErrorWithDriverUpdateHint(config, connectionAttemptOriginalErrorMessage(timeoutMessage, errorMessage(error))), }; }); try { @@ -1554,7 +1559,7 @@ async function testConnection() { testResult.value = { ok: true, message: msg }; } catch (e: any) { if (runId !== testRunId) return; - const message = mongodbAuthFailureHint(String(e)); + const message = connectionErrorWithDriverUpdateHint(config, mongodbAuthFailureHint(String(e))); const fallbackMessage = await tryNacosDockerConsoleFallback(config, message, runId); if (runId !== testRunId) return; testResult.value = fallbackMessage ? { ok: true, message: fallbackMessage } : { ok: false, message }; diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index e12d5e855a..c67750ba67 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -295,6 +295,7 @@ export default { systemJavaNotFound: "System Java runtime was not found on PATH. Please install Java or choose a custom Java executable.", customJavaPathEmpty: "Custom Java runtime path is empty. Please choose a Java executable.", agentJavaTooOld: "This driver requires Java 21. Use DBX managed JRE 21 or select a Java 21 executable in Driver Manager.", + agentDriverUpdateConnectionHint: "A built-in driver update is available for this connection. The connection failure may be related to an outdated local driver. Update the corresponding driver in Driver Manager, then retry.", jdbcPluginNotInstalled: "JDBC plugin is not installed. Install the optional JDBC plugin to use this connection.", lastError: "Connection error", clearError: "Clear connection error", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 5dcec3b018..1d20669625 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -298,6 +298,7 @@ export default withEnglishFallback({ systemJavaNotFound: "未在 PATH 中找到系统 Java 运行环境,请安装 Java 或选择自定义 Java 可执行文件。", customJavaPathEmpty: "自定义 Java 运行环境路径为空,请选择 Java 可执行文件。", agentJavaTooOld: "该驱动需要 Java 21。请在驱动管理器中使用 DBX 托管 JRE 21,或选择 Java 21 可执行文件。", + agentDriverUpdateConnectionHint: "当前连接使用的内置驱动有可用更新,连接失败可能与本地驱动版本过旧有关。请在「驱动管理」中更新对应驱动后重试。", jdbcPluginNotInstalled: "JDBC 插件未安装,请先安装 JDBC 插件再使用此连接。", lastError: "连接错误", clearError: "清除连接错误", diff --git a/apps/desktop/src/lib/agentDriverInstallHint.ts b/apps/desktop/src/lib/agentDriverInstallHint.ts index dbf78f9fc2..ab94d81182 100644 --- a/apps/desktop/src/lib/agentDriverInstallHint.ts +++ b/apps/desktop/src/lib/agentDriverInstallHint.ts @@ -4,11 +4,13 @@ import { supportsDriverManagement } from "./databaseCapabilities"; export interface AgentDriverInstallState { db_type: string; installed: boolean; + update_available?: boolean; } function agentDriverInstallKey(dbType: DatabaseType | undefined, driverProfile?: string): string | undefined { if (dbType === "oracle") return "oracle"; if (dbType === "mongodb") return "mongodb"; + if (dbType === "dameng") return "dameng"; return driverProfile && driverProfile !== dbType ? driverProfile : dbType; } @@ -17,3 +19,15 @@ export function showAgentDriverInstallHint(dbType: DatabaseType | undefined, dri const driverKey = agentDriverInstallKey(dbType, driverProfile); return drivers.find((driver) => driver.db_type === driverKey)?.installed !== true; } + +export function hasAgentDriverUpdate(dbType: DatabaseType | undefined, drivers: readonly AgentDriverInstallState[], driverProfile?: string): boolean { + if (!supportsDriverManagement(dbType)) return false; + const driverKey = agentDriverInstallKey(dbType, driverProfile); + return drivers.find((driver) => driver.db_type === driverKey)?.update_available === true; +} + +export function appendAgentDriverUpdateHint(message: string, hint: string): string { + if (!message.trim()) return hint; + if (message.includes(hint)) return message; + return `${message}\n\n${hint}`; +} diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index c2db55a429..196d8dec6b 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -56,6 +56,8 @@ import { inferMongoCompletionFields, type MongoCompletionField } from "@/lib/mon import { completionSchemasFromTree, completionTablesFromTree } from "@/lib/completionTreeIndex"; import { kvRootNodeLabel } from "@/lib/kvRootPresentation"; import { REDIS_SCAN_PAGE_SIZE_DEFAULT } from "@/lib/redisKeyPattern"; +import { appendAgentDriverUpdateHint, hasAgentDriverUpdate, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint"; +import i18n from "@/i18n"; const PINNED_TREE_NODES_STORAGE_KEY = "dbx-pinned-tree-nodes"; const ACTIVE_CONNECTION_STORAGE_KEY = "dbx-active-connection"; @@ -168,6 +170,8 @@ export const useConnectionStore = defineStore("connection", () => { const pinnedTreeNodeIds = ref>(new Set()); const connectedIds = ref>(new Set()); const lastConnectionHealthCheckAt = ref>({}); + const agentDrivers = ref([]); + let agentDriversRefreshPromise: Promise | null = null; const loadedTreeNodeChildrenIds = ref>(new Set()); const connectionErrors = ref>({}); const editingConnectionId = ref(null); @@ -277,6 +281,44 @@ export const useConnectionStore = defineStore("connection", () => { connectionErrors.value[connectionId] = message; } + function agentDriverUpdateHint(): string { + return i18n.global.t("connection.agentDriverUpdateConnectionHint"); + } + + function connectionErrorWithDriverUpdateHint(config: ConnectionConfig | undefined, message: string): string { + if (!config) return message; + if (!hasAgentDriverUpdate(config.db_type, agentDrivers.value, config.driver_profile)) return message; + return appendAgentDriverUpdateHint(message, agentDriverUpdateHint()); + } + + function refreshAgentDriversForErrorHint(): Promise { + if (agentDriversRefreshPromise) return agentDriversRefreshPromise; + agentDriversRefreshPromise = api + .listInstalledAgents() + .then((drivers) => { + agentDrivers.value = drivers; + }) + .catch(() => undefined) + .finally(() => { + agentDriversRefreshPromise = null; + }); + return agentDriversRefreshPromise; + } + + function maybeAppendAgentDriverUpdateHint(connectionId: string, baseMessage: string) { + const config = getConfig(connectionId); + const message = connectionErrorWithDriverUpdateHint(config, baseMessage); + if (message !== baseMessage) { + setConnectionError(connectionId, message); + return; + } + void refreshAgentDriversForErrorHint().then(() => { + if (connectionErrors.value[connectionId] !== baseMessage) return; + const refreshedMessage = connectionErrorWithDriverUpdateHint(config, baseMessage); + if (refreshedMessage !== baseMessage) setConnectionError(connectionId, refreshedMessage); + }); + } + function clearConnectionError(connectionId: string) { if (!connectionErrors.value[connectionId]) return; delete connectionErrors.value[connectionId]; @@ -369,6 +411,7 @@ export const useConnectionStore = defineStore("connection", () => { function recordConnectionError(connectionId: string, error: unknown): string { const message = connectionErrorMessage(error); setConnectionError(connectionId, message); + maybeAppendAgentDriverUpdateHint(connectionId, message); return message; } diff --git a/packages/app-tests/agentDriverInstallHint.test.ts b/packages/app-tests/agentDriverInstallHint.test.ts index 681f5ac119..65ab176b98 100644 --- a/packages/app-tests/agentDriverInstallHint.test.ts +++ b/packages/app-tests/agentDriverInstallHint.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { test } from "vitest"; -import { showAgentDriverInstallHint } from "../../apps/desktop/src/lib/agentDriverInstallHint.ts"; +import { appendAgentDriverUpdateHint, hasAgentDriverUpdate, showAgentDriverInstallHint } from "../../apps/desktop/src/lib/agentDriverInstallHint.ts"; test("hides the agent driver install hint when the selected driver is installed", () => { assert.equal(showAgentDriverInstallHint("informix", [{ db_type: "informix", installed: true }]), false); @@ -23,30 +23,9 @@ test("does not show agent driver install hints for built-in database types", () }); test("uses the unified Oracle driver for legacy Oracle profiles", () => { - assert.equal( - showAgentDriverInstallHint( - "oracle", - [{ db_type: "oracle", installed: false }], - "oracle-10g", - ), - true, - ); - assert.equal( - showAgentDriverInstallHint( - "oracle", - [{ db_type: "oracle", installed: true }], - "oracle", - ), - false, - ); - assert.equal( - showAgentDriverInstallHint( - "oracle", - [{ db_type: "oracle", installed: true }], - "oracle-legacy", - ), - false, - ); + assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: false }], "oracle-10g"), true); + assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: true }], "oracle"), false); + assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: true }], "oracle-legacy"), false); assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: false }], "oracle"), true); }); @@ -74,3 +53,31 @@ test("uses selected non-Oracle agent driver profiles for install hints", () => { false, ); }); + +test("detects available updates for the selected agent driver", () => { + assert.equal(hasAgentDriverUpdate("dameng", [{ db_type: "dameng", installed: true, update_available: true }], "dm"), true); + assert.equal(hasAgentDriverUpdate("dameng", [{ db_type: "dameng", installed: true, update_available: false }], "dm"), false); + assert.equal(hasAgentDriverUpdate("mysql", [{ db_type: "mysql", installed: true, update_available: true }]), false); +}); + +test("uses unified Oracle and selected profile keys for update hints", () => { + assert.equal(hasAgentDriverUpdate("oracle", [{ db_type: "oracle", installed: true, update_available: true }], "oracle-10g"), true); + assert.equal( + hasAgentDriverUpdate( + "gbase", + [ + { db_type: "gbase", installed: true, update_available: false }, + { db_type: "gbase8s", installed: true, update_available: true }, + ], + "gbase8s", + ), + true, + ); +}); + +test("appends agent driver update hints once", () => { + const hint = "Driver update available."; + assert.equal(appendAgentDriverUpdateHint("Original error", hint), "Original error\n\nDriver update available."); + assert.equal(appendAgentDriverUpdateHint("Original error\n\nDriver update available.", hint), "Original error\n\nDriver update available."); + assert.equal(appendAgentDriverUpdateHint("", hint), hint); +});