fix(connection): show built-in driver update hint on connection failure (#2111)

This commit is contained in:
zipg 2026-06-30 01:52:37 +08:00 committed by GitHub
parent 96f265507d
commit dab1d145bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 99 additions and 28 deletions

View File

@ -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<string> {
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 };

View File

@ -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",

View File

@ -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: "清除连接错误",

View File

@ -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}`;
}

View File

@ -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<Set<string>>(new Set());
const connectedIds = ref<Set<string>>(new Set());
const lastConnectionHealthCheckAt = ref<Record<string, number>>({});
const agentDrivers = ref<AgentDriverInstallState[]>([]);
let agentDriversRefreshPromise: Promise<void> | null = null;
const loadedTreeNodeChildrenIds = ref<Set<string>>(new Set());
const connectionErrors = ref<Record<string, string>>({});
const editingConnectionId = ref<string | null>(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<void> {
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;
}

View File

@ -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);
});