mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
fix(updates): sync driver store state after update center runs
This commit is contained in:
@@ -35,7 +35,7 @@ import { canDownloadAndInstallUpdate, useAppUpdater } from "@/composables/useApp
|
||||
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 { COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentDriverUpdatesChanged, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { driverStoreUpdateBadgeCount, showMcpUpdateBadge, showToolbarUpdateAction } from "@/lib/updates/updateBadges";
|
||||
import {
|
||||
continuePreparedAppUpdate,
|
||||
@@ -1341,6 +1341,7 @@ function reportComponentUpdateResult(result: Awaited<ReturnType<typeof component
|
||||
// Only a clean refresh is authoritative; a failed registry check must not clear stale toolbar state.
|
||||
if (result.failed.length === 0) syncToolbarComponentUpdateState();
|
||||
if (result.plugins > 0) notifyComponentPluginsUpdated();
|
||||
if (result.drivers > 0 || result.jdbc) notifyComponentDriverUpdatesChanged();
|
||||
if (updatedComponents.length) toast(t("updates.componentsAutoUpdated", { components: updatedComponents.join(t("updates.componentListSeparator")) }));
|
||||
if (result.blockedDrivers.length) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: updateBlockerLabels(result.blockedDrivers).join(", ") }), 8000);
|
||||
|
||||
@@ -40,7 +40,7 @@ import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import { runAgentOfflineExportAction } from "@/lib/driverStore/agentOfflineExportFlow";
|
||||
import { DRIVER_CATEGORIES, getCategoryForAgentDriver, assertAgentDriverCategoriesComplete } from "@/lib/connection/driver-category-definitions";
|
||||
import { hasAnyUpdatableDriverMatching, countInstalledDrivers, countAvailableDrivers, partitionDriversByInstallStatus, upgradeAllDriverTypes, upgradeAllMatchesFullUpdateSet, type DriverInstallStatusFilter } from "@/lib/connection/driverListFilter";
|
||||
import { notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { updateBlockerLabels } from "@/lib/updates/componentUpdateOrchestration";
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -431,6 +431,10 @@ async function forceRefresh() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleComponentDriverUpdatesChanged() {
|
||||
void Promise.allSettled([forceRefresh(), loadJdbcDrivers(), loadJdbcPluginStatus()]);
|
||||
}
|
||||
|
||||
function setUpdateDownloadSource(value: unknown) {
|
||||
if (value !== "official" && value !== "cnb") return;
|
||||
if (value === settingsStore.editorSettings.updateDownloadSource) return;
|
||||
@@ -1460,6 +1464,7 @@ async function deleteJdbcLocalBundle(bundleId: string) {
|
||||
// ──────────── Lifecycle ────────────
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, handleComponentDriverUpdatesChanged);
|
||||
updateAgentDrivers(await api.listInstalledAgentsLocal());
|
||||
void loadJavaRuntimeConfig();
|
||||
void loadDriverStoreUsage();
|
||||
@@ -1507,6 +1512,7 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, handleComponentDriverUpdatesChanged);
|
||||
unlisten?.();
|
||||
stopDriverRuntimePolling();
|
||||
});
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { createApp, defineComponent, h, nextTick, type App } from "vue";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AgentDriverInfo } from "@/lib/backend/tauri";
|
||||
import type { JdbcPluginStatus } from "@/types/database";
|
||||
import { COMPONENT_DRIVER_UPDATES_CHANGED_EVENT } from "@/lib/updates/componentUpdateEvents";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
listInstalledAgentsLocal: vi.fn(),
|
||||
listInstalledAgents: vi.fn(),
|
||||
invalidateAgentRegistryCache: vi.fn(),
|
||||
getAgentJavaRuntimeConfig: vi.fn(),
|
||||
getDriverStoreUsage: vi.fn(),
|
||||
getDriverStorePath: vi.fn(),
|
||||
listenAgentInstallProgress: vi.fn(),
|
||||
listJdbcDrivers: vi.fn(),
|
||||
listJdbcMavenBundles: vi.fn(),
|
||||
listJdbcLocalBundles: vi.fn(),
|
||||
jdbcPluginStatus: vi.fn(),
|
||||
toast: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => mocks);
|
||||
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: mocks.toast }) }));
|
||||
vi.mock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.mock("@/stores/settingsStore", async () => {
|
||||
const { reactive } = await import("vue");
|
||||
return {
|
||||
useSettingsStore: () =>
|
||||
reactive({
|
||||
desktopSettings: { driver_store_dir: null, plugin_store_dir: null, agent_store_dir: null },
|
||||
editorSettings: { updateDownloadSource: "official" },
|
||||
updateEditorSettings: vi.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => key }) }));
|
||||
|
||||
function passthrough(name: string, tag = "div") {
|
||||
return defineComponent({
|
||||
name,
|
||||
inheritAttrs: false,
|
||||
setup(_props, { attrs, slots }) {
|
||||
return () => h(tag, attrs, [slots.default?.(), slots.content?.(), slots.header?.(), slots.footer?.()]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock("@/components/ui/button", () => ({ Button: passthrough("Button", "button") }));
|
||||
vi.mock("@/components/ui/input", () => ({ Input: passthrough("Input", "input") }));
|
||||
vi.mock("@/components/ui/label", () => ({ Label: passthrough("Label", "label") }));
|
||||
vi.mock("@/components/ui/badge", () => ({ Badge: passthrough("Badge", "span") }));
|
||||
vi.mock("@/components/ui/select", () => ({ Select: passthrough("Select"), SelectContent: passthrough("SelectContent"), SelectItem: passthrough("SelectItem"), SelectTrigger: passthrough("SelectTrigger"), SelectValue: passthrough("SelectValue") }));
|
||||
vi.mock("@/components/ui/tooltip", () => ({ Tooltip: passthrough("Tooltip"), TooltipContent: passthrough("TooltipContent"), TooltipTrigger: passthrough("TooltipTrigger") }));
|
||||
vi.mock("@/components/ui/tabs", () => ({ Tabs: passthrough("Tabs"), TabsContent: passthrough("TabsContent"), TabsList: passthrough("TabsList"), TabsTrigger: passthrough("TabsTrigger") }));
|
||||
vi.mock("@/components/config/DriverInstallProgressCircle.vue", () => ({ default: passthrough("DriverInstallProgressCircle") }));
|
||||
vi.mock("@/components/config/DriverStoreAgentRow.vue", () => ({ default: passthrough("DriverStoreAgentRow") }));
|
||||
vi.mock("@/components/config/AgentOfflineExportDialog.vue", () => ({ default: passthrough("AgentOfflineExportDialog") }));
|
||||
|
||||
import DriverStoreDialog from "@/components/config/DriverStoreDialog.vue";
|
||||
|
||||
function driver(updateAvailable: boolean): AgentDriverInfo {
|
||||
return {
|
||||
db_type: "oracle",
|
||||
label: "Oracle",
|
||||
version: "2.0.0",
|
||||
size: 1,
|
||||
installed: true,
|
||||
installed_version: updateAvailable ? "1.0.0" : "2.0.0",
|
||||
update_available: updateAvailable,
|
||||
jre: "21",
|
||||
jre_installed: true,
|
||||
};
|
||||
}
|
||||
|
||||
function jdbcStatus(updateAvailable: boolean): JdbcPluginStatus {
|
||||
return {
|
||||
installed: true,
|
||||
version: updateAvailable ? "1.0.0" : "2.0.0",
|
||||
compatible: true,
|
||||
latest_version: "2.0.0",
|
||||
update_available: updateAvailable,
|
||||
path: "/tmp/jdbc",
|
||||
};
|
||||
}
|
||||
|
||||
async function flushUi() {
|
||||
for (let index = 0; index < 10; index++) {
|
||||
await Promise.resolve();
|
||||
await nextTick();
|
||||
}
|
||||
}
|
||||
|
||||
let app: App;
|
||||
let host: HTMLElement;
|
||||
let updateCountChange: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetAllMocks();
|
||||
mocks.listInstalledAgentsLocal.mockResolvedValue([driver(true)]);
|
||||
mocks.listInstalledAgents.mockResolvedValue([driver(true)]);
|
||||
mocks.invalidateAgentRegistryCache.mockResolvedValue(undefined);
|
||||
mocks.getAgentJavaRuntimeConfig.mockResolvedValue({ mode: "managed", custom_java_path: null });
|
||||
mocks.getDriverStoreUsage.mockResolvedValue(null);
|
||||
mocks.getDriverStorePath.mockResolvedValue({ driver_store_dir: "", plugin_store_dir: "", agent_store_dir: "", plugins_dir: "", agents_dir: "" });
|
||||
mocks.listenAgentInstallProgress.mockResolvedValue(vi.fn());
|
||||
mocks.listJdbcDrivers.mockResolvedValue([]);
|
||||
mocks.listJdbcMavenBundles.mockResolvedValue([]);
|
||||
mocks.listJdbcLocalBundles.mockResolvedValue([]);
|
||||
mocks.jdbcPluginStatus.mockResolvedValue(jdbcStatus(true));
|
||||
|
||||
host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
updateCountChange = vi.fn();
|
||||
app = createApp({
|
||||
render: () =>
|
||||
h(DriverStoreDialog, {
|
||||
activeTab: "agent",
|
||||
onUpdateCountChange: updateCountChange,
|
||||
}),
|
||||
});
|
||||
app.mount(host);
|
||||
await flushUi();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
app.unmount();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
describe("DriverStoreDialog external component update sync", () => {
|
||||
it("refreshes local agent and JDBC state when the update center changes drivers", async () => {
|
||||
updateCountChange.mockClear();
|
||||
mocks.listInstalledAgents.mockResolvedValueOnce([driver(false)]);
|
||||
mocks.jdbcPluginStatus.mockResolvedValueOnce(jdbcStatus(false));
|
||||
|
||||
window.dispatchEvent(new Event(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT));
|
||||
await flushUi();
|
||||
|
||||
expect(mocks.invalidateAgentRegistryCache).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.listInstalledAgents).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.listJdbcDrivers).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.jdbcPluginStatus).toHaveBeenCalledTimes(2);
|
||||
expect(updateCountChange).toHaveBeenLastCalledWith(0);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { COMPONENT_PLUGINS_UPDATED_EVENT, COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, COMPONENT_PLUGINS_UPDATED_EVENT, COMPONENT_UPDATES_CHANGED_EVENT, notifyComponentDriverUpdatesChanged, notifyComponentPluginsUpdated, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
|
||||
describe("component update events", () => {
|
||||
it("notifies plugin surfaces after component-level plugin updates", () => {
|
||||
@@ -24,10 +24,21 @@ describe("component update events", () => {
|
||||
window.removeEventListener(COMPONENT_UPDATES_CHANGED_EVENT, listener);
|
||||
});
|
||||
|
||||
it("notifies the driver store when component updates change driver state", () => {
|
||||
const listener = vi.fn();
|
||||
window.addEventListener(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, listener);
|
||||
|
||||
notifyComponentDriverUpdatesChanged();
|
||||
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
window.removeEventListener(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT, listener);
|
||||
});
|
||||
|
||||
it("is inert when the store runs outside a browser environment", () => {
|
||||
vi.stubGlobal("window", undefined);
|
||||
try {
|
||||
expect(() => notifyComponentPluginsUpdated()).not.toThrow();
|
||||
expect(() => notifyComponentDriverUpdatesChanged()).not.toThrow();
|
||||
expect(() => notifyComponentUpdatesChanged()).not.toThrow();
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const COMPONENT_PLUGINS_UPDATED_EVENT = "dbx:component-plugins-updated";
|
||||
export const COMPONENT_DRIVER_UPDATES_CHANGED_EVENT = "dbx:component-driver-updates-changed";
|
||||
export const COMPONENT_UPDATES_CHANGED_EVENT = "dbx:component-updates-changed";
|
||||
|
||||
export function notifyComponentPluginsUpdated(): void {
|
||||
@@ -6,6 +7,11 @@ export function notifyComponentPluginsUpdated(): void {
|
||||
window.dispatchEvent(new Event(COMPONENT_PLUGINS_UPDATED_EVENT));
|
||||
}
|
||||
|
||||
export function notifyComponentDriverUpdatesChanged(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
window.dispatchEvent(new Event(COMPONENT_DRIVER_UPDATES_CHANGED_EVENT));
|
||||
}
|
||||
|
||||
export function notifyComponentUpdatesChanged(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
window.dispatchEvent(new Event(COMPONENT_UPDATES_CHANGED_EVENT));
|
||||
|
||||
Reference in New Issue
Block a user