fix(updates): show why a plugin update was blocked
This commit is contained in:
3 files changed
+63
-8
No files matched your search
@@ -27,6 +27,7 @@ import { useTheme } from "@/composables/useTheme";
|
||||
import { canDownloadAndInstallUpdate, useAppUpdater } from "@/composables/useAppUpdater";
|
||||
import { useMcpUpdateBadge } from "@/composables/useMcpUpdateBadge";
|
||||
import { useComponentUpdates, type ComponentUpdateCategory } from "@/composables/useComponentUpdates";
|
||||
import type { PluginUpdateBlock } from "@/composables/useComponentUpdates";
|
||||
import { COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { driverStoreUpdateBadgeCount, showMcpUpdateBadge, showToolbarUpdateAction } from "@/lib/updates/updateBadges";
|
||||
import {
|
||||
@@ -1271,6 +1272,12 @@ function handleComponentUpdatesChanged() {
|
||||
});
|
||||
}
|
||||
|
||||
function pluginUpdateBlockMessage(block: PluginUpdateBlock): string {
|
||||
if (block.reason === "connections") return `${block.pluginName}: ${t("pluginPlatform.updateBlockedByConnections", { labels: block.connections })}`;
|
||||
const key = block.reason === "operations" ? "pluginPlatform.updateBlockedByOperations" : "pluginPlatform.updateInProgress";
|
||||
return `${block.pluginName}: ${t(key)}`;
|
||||
}
|
||||
|
||||
function reportComponentUpdateResult(result: Awaited<ReturnType<typeof componentUpdates.installCategory>>) {
|
||||
const updatedComponents = [result.drivers > 0 ? t("settings.updateDrivers") : "", result.jdbc ? t("settings.updateJdbc") : "", result.mcp ? t("settings.updateMcp") : "", result.plugins > 0 ? t("settings.updatePlugins") : ""].filter(Boolean);
|
||||
// Only a clean refresh is authoritative; a failed registry check must not clear stale toolbar state.
|
||||
@@ -1278,7 +1285,9 @@ function reportComponentUpdateResult(result: Awaited<ReturnType<typeof component
|
||||
if (result.plugins > 0) notifyComponentPluginsUpdated();
|
||||
if (updatedComponents.length) toast(t("updates.componentsAutoUpdated", { components: updatedComponents.join(t("updates.componentListSeparator")) }));
|
||||
if (result.skippedDrivers > 0) toast(t("updates.componentsAutoUpdateSkipped"), 6000);
|
||||
if (result.failed.length) toast(t("updates.componentsAutoUpdateFailed", { count: result.failed.length }), 6000);
|
||||
const otherFailureCount = result.failed.length - result.blockedPlugins.length;
|
||||
const failureMessages = [result.blockedPlugins.map(pluginUpdateBlockMessage).join("\n"), otherFailureCount > 0 ? t("updates.componentsAutoUpdateFailed", { count: otherFailureCount }) : ""].filter(Boolean);
|
||||
if (failureMessages.length) toast(failureMessages.join("\n"), 8000);
|
||||
|
||||
if (
|
||||
shouldCloseUpdateCenterAfterComponentUpdate({
|
||||
|
||||
@@ -131,7 +131,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledWith({ repositoryId: "official", pluginId: "example", version: "1.1.0" });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("shares one update operation between automatic and manual callers", async () => {
|
||||
@@ -172,7 +172,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).not.toHaveBeenCalled();
|
||||
expect(mocks.installMcpServer).not.toHaveBeenCalled();
|
||||
expect(mocks.installMarketplacePlugin).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [] });
|
||||
expect(result).toEqual({ drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("installs every manually selected category when no DBX update exists and automatic updates are disabled", async () => {
|
||||
@@ -190,7 +190,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("resumes a persisted manual update-all plan after restart while automatic updates are disabled", async () => {
|
||||
@@ -213,7 +213,7 @@ describe("useComponentUpdates", () => {
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMcpServer).toHaveBeenCalledOnce();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [] });
|
||||
expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("keeps the toolbar update action hidden while a persisted restart plan is still installing", async () => {
|
||||
@@ -302,6 +302,32 @@ describe("useComponentUpdates", () => {
|
||||
const result = await updates.installCategory("jdbc");
|
||||
|
||||
expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual({ drivers: 0, jdbc: true, mcp: false, plugins: 0, skippedDrivers: 0, failed: [] });
|
||||
expect(result).toEqual({ drivers: 0, jdbc: true, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] });
|
||||
});
|
||||
|
||||
it("reports the blocking connections when a K8S plugin update cannot start", async () => {
|
||||
mocks.buildMarketplacePluginListings.mockReturnValue([
|
||||
{
|
||||
...pluginUpdate,
|
||||
plugin: { id: "io.dbx.k8s", latestVersion: "0.1.3" },
|
||||
name: "Kubernetes 管理",
|
||||
},
|
||||
]);
|
||||
mocks.installMarketplacePlugin.mockRejectedValue(new Error("Plugin update blocked by active connections: market测试环境"));
|
||||
const updates = useComponentUpdates({ isDesktop: true });
|
||||
|
||||
const result = await updates.installCategory("plugins");
|
||||
|
||||
expect(result.blockedPlugins).toEqual([{ pluginName: "Kubernetes 管理", reason: "connections", connections: "market测试环境" }]);
|
||||
expect(result.failed).toEqual(["Kubernetes 管理: Plugin update blocked by active connections: market测试环境"]);
|
||||
});
|
||||
|
||||
it("reports active operations separately from connections", async () => {
|
||||
mocks.installMarketplacePlugin.mockRejectedValue(new Error("Plugin update blocked by active operations. Please wait for them to finish."));
|
||||
const updates = useComponentUpdates({ isDesktop: true });
|
||||
|
||||
const result = await updates.installCategory("plugins");
|
||||
|
||||
expect(result.blockedPlugins).toEqual([{ pluginName: "Example plugin", reason: "operations" }]);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,8 @@ import type { JdbcPluginStatus } from "@/types/database";
|
||||
|
||||
export type { ComponentUpdateCategory } from "@/lib/updates/componentUpdateOrchestration";
|
||||
|
||||
export type PluginUpdateBlock = { pluginName: string; reason: "connections"; connections: string } | { pluginName: string; reason: "operations" | "inProgress" };
|
||||
|
||||
export interface ComponentUpdateResult {
|
||||
drivers: number;
|
||||
jdbc: boolean;
|
||||
@@ -18,6 +20,7 @@ export interface ComponentUpdateResult {
|
||||
plugins: number;
|
||||
skippedDrivers: number;
|
||||
failed: string[];
|
||||
blockedPlugins: PluginUpdateBlock[];
|
||||
}
|
||||
|
||||
interface ComponentUpdateRefreshOptions {
|
||||
@@ -25,7 +28,21 @@ interface ComponentUpdateRefreshOptions {
|
||||
}
|
||||
|
||||
function emptyResult(): ComponentUpdateResult {
|
||||
return { drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [] };
|
||||
return { drivers: 0, jdbc: false, mcp: false, plugins: 0, skippedDrivers: 0, failed: [], blockedPlugins: [] };
|
||||
}
|
||||
|
||||
function pluginUpdateBlock(pluginName: string, message: string): PluginUpdateBlock | null {
|
||||
const connectionsPrefix = "Plugin update blocked by active connections: ";
|
||||
if (message.startsWith(connectionsPrefix)) {
|
||||
return { pluginName, reason: "connections", connections: message.slice(connectionsPrefix.length) };
|
||||
}
|
||||
if (message === "Plugin update blocked by active operations. Please wait for them to finish.") {
|
||||
return { pluginName, reason: "operations" };
|
||||
}
|
||||
if (message === "Plugin update is in progress. Please try again after it finishes.") {
|
||||
return { pluginName, reason: "inProgress" };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useComponentUpdates(options: { isDesktop: boolean }) {
|
||||
@@ -167,7 +184,10 @@ export function useComponentUpdates(options: { isDesktop: boolean }) {
|
||||
});
|
||||
result.plugins += 1;
|
||||
} catch (error) {
|
||||
result.failed.push(`${listing.name}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const block = pluginUpdateBlock(listing.name, message);
|
||||
if (block) result.blockedPlugins.push(block);
|
||||
result.failed.push(`${listing.name}: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user