diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index ddb200a6d4..99bc636248 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -28,7 +28,7 @@ import { canDownloadAndInstallUpdate, useAppUpdater } from "@/composables/useApp import { useMcpUpdateBadge } from "@/composables/useMcpUpdateBadge"; import { useComponentUpdates, type ComponentUpdateCategory } from "@/composables/useComponentUpdates"; import { COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents"; -import { driverStoreUpdateBadgeCount, showMcpUpdateBadge } from "@/lib/updates/updateBadges"; +import { driverStoreUpdateBadgeCount, showMcpUpdateBadge, showToolbarUpdateAction } from "@/lib/updates/updateBadges"; import { markPendingComponentUpdatesAfterAppUpdate, resolveUpdateAllAction, runPendingComponentUpdatePlan, shouldCloseUpdateCenterAfterComponentUpdate, takePendingComponentUpdatesAfterAppRestart, type PendingComponentUpdatePlan } from "@/lib/updates/componentUpdateOrchestration"; import { isUpdatePreviewMockEnabled } from "@/lib/updates/updatePreviewMock"; import { useExportTracker } from "@/composables/useExportTracker"; @@ -902,8 +902,16 @@ const toolbarAgentDriverUpdateCount = computed(() => Math.max(agentDriverUpdateC const toolbarDriverUpdateCount = computed(() => toolbarAgentDriverUpdateCount.value); const toolbarJdbcUpdateAvailable = computed(() => componentUpdates.jdbcUpdateAvailable.value); const toolbarMcpUpdateAvailable = computed(() => mcpUpdateAvailable.value || componentUpdates.mcpUpdateAvailable.value); -const toolbarPluginUpdateAvailable = computed(() => componentUpdates.pluginUpdateCount.value > 0); -const toolbarHasUpdateAvailable = computed(() => hasUpdateAvailable.value || toolbarDriverUpdateCount.value > 0 || toolbarJdbcUpdateAvailable.value || toolbarMcpUpdateAvailable.value || toolbarPluginUpdateAvailable.value); +const toolbarHasUpdateAvailable = computed(() => + showToolbarUpdateAction({ + appUpdateAvailable: hasUpdateAvailable.value, + driverUpdateCount: toolbarDriverUpdateCount.value, + jdbcUpdateAvailable: toolbarJdbcUpdateAvailable.value, + mcpUpdateAvailable: toolbarMcpUpdateAvailable.value, + pluginUpdateCount: componentUpdates.pluginUpdateCount.value, + componentUpdatesRunning: componentUpdates.updating.value, + }), +); const showDriverStoreUpdateBadge = computed(() => driverStoreUpdateBadgeCount(settingsStore.editorSettings.autoUpdateDrivers, settingsStore.editorSettings.autoUpdateJdbc, toolbarDriverUpdateCount.value, toolbarJdbcUpdateAvailable.value)); const showMcpSettingsUpdateBadge = computed(() => showMcpUpdateBadge(settingsStore.editorSettings.autoUpdateMcp, toolbarMcpUpdateAvailable.value)); const manualCheckingAllUpdates = ref(false); diff --git a/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts b/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts index f051ca5728..7a2ba25789 100644 --- a/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts +++ b/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { useComponentUpdates } from "@/composables/useComponentUpdates"; import { runPendingComponentUpdatePlan, type PendingComponentUpdates } from "@/lib/updates/componentUpdateOrchestration"; +import { showToolbarUpdateAction } from "@/lib/updates/updateBadges"; const mocks = vi.hoisted(() => ({ listInstalledAgents: vi.fn(), @@ -215,6 +216,36 @@ describe("useComponentUpdates", () => { expect(result).toEqual({ drivers: 1, jdbc: true, mcp: true, plugins: 1, skippedDrivers: 0, failed: [] }); }); + it("keeps the toolbar update action hidden while a persisted restart plan is still installing", async () => { + const jdbcInstall = deferred<{ installed: boolean; update_available: boolean }>(); + mocks.installJdbcPlugin.mockReturnValue(jdbcInstall.promise); + const updates = useComponentUpdates({ isDesktop: true }); + const pending: PendingComponentUpdates = { + fromVersion: "0.6.18", + targetVersion: "0.6.19", + plan: { kind: "manual", categories: ["jdbc"] }, + }; + + const updateOperation = runPendingComponentUpdatePlan(pending, updates); + await vi.waitFor(() => expect(mocks.installJdbcPlugin).toHaveBeenCalledOnce()); + + const toolbarOptions = { + appUpdateAvailable: false, + driverUpdateCount: updates.driverUpdateCount.value, + jdbcUpdateAvailable: updates.jdbcUpdateAvailable.value, + mcpUpdateAvailable: updates.mcpUpdateAvailable.value, + pluginUpdateCount: updates.pluginUpdateCount.value, + }; + expect(updates.updating.value).toBe(true); + expect(showToolbarUpdateAction({ ...toolbarOptions, componentUpdatesRunning: updates.updating.value })).toBe(false); + + jdbcInstall.resolve({ installed: true, update_available: false }); + await updateOperation; + + expect(updates.updating.value).toBe(false); + expect(showToolbarUpdateAction({ ...toolbarOptions, componentUpdatesRunning: updates.updating.value })).toBe(true); + }); + it("skips blocked agent updates while allowing other component updates", async () => { mocks.checkAgentUpdateBlockers.mockResolvedValue([{ db_type: "mysql", label: "MySQL" }]); const updates = useComponentUpdates({ isDesktop: true }); diff --git a/apps/desktop/src/lib/updates/__tests__/updateBadges.spec.ts b/apps/desktop/src/lib/updates/__tests__/updateBadges.spec.ts index d037b88cdf..2fd77cc0d0 100644 --- a/apps/desktop/src/lib/updates/__tests__/updateBadges.spec.ts +++ b/apps/desktop/src/lib/updates/__tests__/updateBadges.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { driverStoreUpdateBadgeCount, showMcpUpdateBadge } from "@/lib/updates/updateBadges"; +import { driverStoreUpdateBadgeCount, showMcpUpdateBadge, showToolbarUpdateAction } from "@/lib/updates/updateBadges"; describe("component update entry badges", () => { it("hides separate driver and JDBC badges while their automatic updates are enabled", () => { @@ -17,4 +17,35 @@ describe("component update entry badges", () => { expect(showMcpUpdateBadge(false, true)).toBe(true); expect(showMcpUpdateBadge(false, false)).toBe(false); }); + + it("keeps the toolbar update action hidden while component updates are still running", () => { + expect( + showToolbarUpdateAction({ + appUpdateAvailable: false, + driverUpdateCount: 1, + jdbcUpdateAvailable: true, + mcpUpdateAvailable: true, + pluginUpdateCount: 1, + componentUpdatesRunning: true, + }), + ).toBe(false); + }); + + it("shows the toolbar update action for remaining updates after component updating stops", () => { + const base = { + appUpdateAvailable: false, + driverUpdateCount: 0, + jdbcUpdateAvailable: false, + mcpUpdateAvailable: false, + pluginUpdateCount: 0, + componentUpdatesRunning: false, + }; + + expect(showToolbarUpdateAction(base)).toBe(false); + expect(showToolbarUpdateAction({ ...base, appUpdateAvailable: true })).toBe(true); + expect(showToolbarUpdateAction({ ...base, driverUpdateCount: 1 })).toBe(true); + expect(showToolbarUpdateAction({ ...base, jdbcUpdateAvailable: true })).toBe(true); + expect(showToolbarUpdateAction({ ...base, mcpUpdateAvailable: true })).toBe(true); + expect(showToolbarUpdateAction({ ...base, pluginUpdateCount: 1 })).toBe(true); + }); }); diff --git a/apps/desktop/src/lib/updates/updateBadges.ts b/apps/desktop/src/lib/updates/updateBadges.ts index deb51c0fa9..bf28a3a0bc 100644 --- a/apps/desktop/src/lib/updates/updateBadges.ts +++ b/apps/desktop/src/lib/updates/updateBadges.ts @@ -5,3 +5,9 @@ export function driverStoreUpdateBadgeCount(autoUpdateDrivers: boolean, autoUpda export function showMcpUpdateBadge(autoUpdateMcp: boolean, mcpUpdateAvailable: boolean): boolean { return !autoUpdateMcp && mcpUpdateAvailable; } + +export function showToolbarUpdateAction(options: { appUpdateAvailable: boolean; driverUpdateCount: number; jdbcUpdateAvailable: boolean; mcpUpdateAvailable: boolean; pluginUpdateCount: number; componentUpdatesRunning: boolean }): boolean { + // Installation refreshes before replacing components, so discovery can report pending entries mid-operation. + if (options.componentUpdatesRunning) return false; + return options.appUpdateAvailable || options.driverUpdateCount > 0 || options.jdbcUpdateAvailable || options.mcpUpdateAvailable || options.pluginUpdateCount > 0; +}