fix(updates): sync update center state after manual component changes
This commit is contained in:
13 files changed
+143
-11
No files matched your search
@@ -40,6 +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";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
@@ -497,6 +498,7 @@ async function runDriverInstall(dbType: string) {
|
||||
if (managedResult.drivers) jdbcDrivers.value = managedResult.drivers;
|
||||
jdbcMavenBundles.value = managedResult.bundles;
|
||||
void loadDriverStoreUsage();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("driverStore.driverInstallSuccess", { label }));
|
||||
return;
|
||||
}
|
||||
@@ -509,6 +511,7 @@ async function runDriverInstall(dbType: string) {
|
||||
activeAgentOperationId.value = operationId;
|
||||
cancellableAgentInstallDbType.value = dbType;
|
||||
await api.installAgent(dbType, operationId);
|
||||
notifyComponentUpdatesChanged();
|
||||
await refreshAgents();
|
||||
toast(t("driverStore.driverInstallSuccess", { label }));
|
||||
} catch (e: any) {
|
||||
@@ -557,6 +560,7 @@ async function upgradeAll() {
|
||||
queuedDriverInstalls.value = [];
|
||||
resetAgentInstallProgress();
|
||||
const result = await api.upgradeAllAgents(activeAgentOperationId.value);
|
||||
notifyComponentUpdatesChanged();
|
||||
await refreshAgents();
|
||||
if (result.cancelled > 0 && result.failed.length === 0) {
|
||||
toast(t("driverStore.upgradeAllCancelled", { count: result.cancelled }));
|
||||
@@ -613,6 +617,7 @@ async function uninstallDriver(dbType: string) {
|
||||
if (managedResult.drivers) jdbcDrivers.value = managedResult.drivers;
|
||||
jdbcMavenBundles.value = managedResult.bundles;
|
||||
void loadDriverStoreUsage();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("driverStore.driverUninstallSuccess", { label }));
|
||||
return;
|
||||
}
|
||||
@@ -622,6 +627,7 @@ async function uninstallDriver(dbType: string) {
|
||||
return;
|
||||
}
|
||||
await api.uninstallAgent(dbType);
|
||||
notifyComponentUpdatesChanged();
|
||||
await refreshAgents();
|
||||
toast(t("driverStore.driverUninstallSuccess", { label }));
|
||||
} catch (e: any) {
|
||||
@@ -744,6 +750,7 @@ async function importOfflineZip() {
|
||||
resetAgentInstallProgress();
|
||||
try {
|
||||
const result = await api.importAgentsFromZip(selected, activeAgentOperationId.value);
|
||||
notifyComponentUpdatesChanged();
|
||||
await Promise.all([refreshAgents(), loadJdbcDrivers(), loadJdbcPluginStatus()]);
|
||||
toastOfflineImportResult(result);
|
||||
} catch (e: any) {
|
||||
@@ -796,6 +803,7 @@ async function importDriverFile(driver: AgentDriverInfo) {
|
||||
resetAgentInstallProgress();
|
||||
try {
|
||||
const result = await api.importAgentsFromZip(selected, activeAgentOperationId.value);
|
||||
notifyComponentUpdatesChanged();
|
||||
await Promise.all([refreshAgents(), loadJdbcDrivers(), loadJdbcPluginStatus()]);
|
||||
toastOfflineImportResult(result);
|
||||
} finally {
|
||||
@@ -805,6 +813,7 @@ async function importDriverFile(driver: AgentDriverInfo) {
|
||||
} else {
|
||||
await api.importAgentDriver(dbType, selected);
|
||||
await refreshAgents();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("driverStore.driverImportSuccess", { label }));
|
||||
}
|
||||
};
|
||||
@@ -1288,6 +1297,7 @@ async function installJdbcPlugin() {
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.installJdbcPlugin();
|
||||
emitDriverUpdateCount();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("settings.jdbcPluginInstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
@@ -1318,6 +1328,7 @@ async function installJdbcPluginLocal() {
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.installJdbcPluginLocal(selected);
|
||||
emitDriverUpdateCount();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("settings.jdbcPluginInstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
@@ -1334,6 +1345,7 @@ async function uninstallJdbcPlugin() {
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.uninstallJdbcPlugin();
|
||||
emitDriverUpdateCount();
|
||||
notifyComponentUpdatesChanged();
|
||||
toast(t("settings.jdbcPluginUninstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -92,6 +92,7 @@ import { configuredDatabaseProductName, connectionConfigFingerprint, databaseInf
|
||||
import { agentDriverInstallKey, appendAgentDriverUpdateHint, connectionUsesSsh, hasAgentDriverUpdate, showAgentDriverInstallHint, type AgentDriverInstallState, type DriverStoreFocus } from "@/lib/connection/agentDriverInstallHint";
|
||||
import { prestoSqlBuiltinDriverPaths } from "@/lib/database/prestoSqlBuiltinDriver";
|
||||
import { JDBCX_DEFAULT_URL, JDBCX_DRIVER_PROFILE, JDBCX_JDBC_DRIVER_CLASS, ensureJdbcxRuntimeDrivers, isJdbcxRuntimeBundle, isJdbcxRuntimePath, jdbcxHighPrivilegeExtensionsEnabled, setJdbcxHighPrivilegeExtensionsEnabled } from "@/lib/database/jdbcxBuiltinDriver";
|
||||
import { notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/database/databaseFileDetection";
|
||||
import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connection/connectionAttemptTimeout";
|
||||
import { consulAgentAddressesMatch } from "@/lib/consul/agentTarget";
|
||||
@@ -1964,6 +1965,7 @@ async function ensureRequiredAgentDriverInstalled(config: ConnectionConfig): Pro
|
||||
const operationId = beginAgentDriverInstall(driverKey, label);
|
||||
try {
|
||||
await api.installAgent(driverKey, operationId);
|
||||
notifyComponentUpdatesChanged();
|
||||
await refreshLocalAgentDrivers();
|
||||
// A stale promise (cancelled then retried) must not close the retry's dialog.
|
||||
if (agentInstallOperationId.value === operationId) finishAgentDriverInstall(operationId);
|
||||
@@ -1999,6 +2001,7 @@ async function ensureRequiredJdbcxDriverInstalled(config: ConnectionConfig): Pro
|
||||
testResult.value = { ok: true, message: "Installing JDBC plugin..." };
|
||||
});
|
||||
if (!result) return;
|
||||
notifyComponentUpdatesChanged();
|
||||
|
||||
jdbcMavenBundles.value = result.bundles;
|
||||
addJdbcDriverPaths(result.paths);
|
||||
@@ -2012,6 +2015,7 @@ async function ensureRequiredJdbcxDriverInstalled(config: ConnectionConfig): Pro
|
||||
async function ensureRequiredJdbcProductRuntimeInstalled(config: ConnectionConfig): Promise<void> {
|
||||
const result = await ensureRegisteredJdbcProductRuntimeDrivers(config, api);
|
||||
if (!result) return;
|
||||
notifyComponentUpdatesChanged();
|
||||
|
||||
jdbcMavenBundles.value = result.bundles;
|
||||
jdbcDriverPathsInput.value = result.paths.join("\n");
|
||||
@@ -2031,6 +2035,7 @@ async function ensureRequiredGaussdbMJdbcRuntime(config: ConnectionConfig): Prom
|
||||
if (status.installed && status.compatible) return;
|
||||
testResult.value = { ok: true, message: t("connection.gaussdbMJdbcPluginInstalling") };
|
||||
await api.installJdbcPlugin();
|
||||
notifyComponentUpdatesChanged();
|
||||
}
|
||||
|
||||
async function installSqlServerLegacyCompatibilityComponentIfNeeded(): Promise<boolean> {
|
||||
@@ -2040,6 +2045,7 @@ async function installSqlServerLegacyCompatibilityComponentIfNeeded(): Promise<b
|
||||
const operationId = beginAgentDriverInstall(SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY, label);
|
||||
try {
|
||||
await api.installAgent(SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY, operationId);
|
||||
notifyComponentUpdatesChanged();
|
||||
await refreshLocalAgentDrivers();
|
||||
// A stale promise (cancelled then retried) must not close the retry's dialog.
|
||||
if (agentInstallOperationId.value === operationId) finishAgentDriverInstall(operationId);
|
||||
|
||||
@@ -205,6 +205,7 @@ import {
|
||||
type McpLaunchConfig,
|
||||
} from "@/lib/mcp/mcpConfigTemplates";
|
||||
import { beginMcpStatusRequest, mcpUpdateAvailability } from "@/lib/mcp/mcpUpdateStatus";
|
||||
import { notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { isMcpPolicyMutationBlocked, MCP_CAPABILITY_ROWS, MCP_EXECUTION_MODE_COLUMNS, MCP_TOOL_OPTIONS, mcpExecutionModeFromPolicy, mcpPolicyFieldsForExecutionMode, toggleMcpAllowedToolName, type McpExecutionMode } from "@/lib/mcp/mcpPolicySelection";
|
||||
import { isMacOS, isWindows } from "@/lib/backend/platform";
|
||||
import { combineDataTypeForDatabase, dataTypeLengthInputValue, getDataTypeOptions, getDefaultLengthForType, isDataTypeLengthDisabled, splitDataType } from "@/lib/table/tableStructureEditorState";
|
||||
@@ -3793,6 +3794,7 @@ async function installMcp() {
|
||||
mcpInstallError.value = false;
|
||||
// 安装成功后刷新状态
|
||||
await refreshMcpStatus();
|
||||
notifyComponentUpdatesChanged();
|
||||
} catch (e: any) {
|
||||
mcpInstallMessage.value = e?.message || String(e);
|
||||
mcpInstallError.value = true;
|
||||
@@ -3815,6 +3817,7 @@ async function uninstallMcp() {
|
||||
try {
|
||||
mcpInstallMessage.value = await uninstallMcpServer();
|
||||
await refreshMcpStatus();
|
||||
notifyComponentUpdatesChanged();
|
||||
} catch (e: any) {
|
||||
mcpInstallMessage.value = t("settings.mcpUninstallFailed", { error: e?.message || String(e) });
|
||||
mcpInstallError.value = true;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { physicalDropPositionInsideRect } from "@/lib/ai/aiAttachments";
|
||||
import { createFrontendPluginRegistry, pluginConnectionProviderIcon } from "@/lib/plugins/frontendPlugin";
|
||||
import { beaconPluginInstall, buildMarketplacePluginListings, filterMarketplacePluginListings, listingRepositoryCanVerify, marketplaceHomepageUrl, type MarketplacePluginListing } from "@/lib/plugins/pluginMarketplace";
|
||||
import { isBatchSelectableListing, runBatch } from "@/lib/plugins/pluginBatch";
|
||||
import { COMPONENT_PLUGINS_UPDATED_EVENT } from "@/lib/updates/componentUpdateEvents";
|
||||
import { COMPONENT_PLUGINS_UPDATED_EVENT, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
|
||||
import { formatBytes } from "@/lib/database/serverMetrics";
|
||||
import type { PluginCenterFocus } from "@/lib/plugins/pluginCenterNavigation";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
@@ -216,6 +216,7 @@ async function installMarketplaceListing(listing: MarketplacePluginListing) {
|
||||
const result = await installListing(listing);
|
||||
toast(t(listing.status === "update" ? "pluginPlatform.updateSuccess" : "pluginPlatform.installSuccess", { name: result.plugin.manifest.name, version: result.plugin.manifest.version }));
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
notifyComponentUpdatesChanged();
|
||||
selectPlugin(result.plugin.manifest.id);
|
||||
} catch (cause) {
|
||||
toast(translateBackendError(t, cause), 8000);
|
||||
@@ -223,6 +224,7 @@ async function installMarketplaceListing(listing: MarketplacePluginListing) {
|
||||
// instance), so re-read the installed list instead of leaving the card on state it may no longer
|
||||
// describe.
|
||||
installedPlugins.value = await api.listPlugins().catch(() => installedPlugins.value);
|
||||
notifyComponentUpdatesChanged();
|
||||
} finally {
|
||||
marketplaceInstallingKey.value = "";
|
||||
}
|
||||
@@ -325,6 +327,7 @@ async function runBatchInstallUpdate() {
|
||||
clearBatchSelection();
|
||||
reportBatchSummary(outcome);
|
||||
await refreshAfterBatch();
|
||||
notifyComponentUpdatesChanged();
|
||||
} finally {
|
||||
batchRunning.value = false;
|
||||
}
|
||||
@@ -348,6 +351,7 @@ async function runBatchUninstall() {
|
||||
clearBatchSelection();
|
||||
reportBatchSummary(outcome);
|
||||
await refreshAfterBatch();
|
||||
notifyComponentUpdatesChanged();
|
||||
} finally {
|
||||
batchRunning.value = false;
|
||||
}
|
||||
@@ -543,6 +547,7 @@ async function finishInstall(result: PluginInstallResult) {
|
||||
toast(t("pluginPlatform.installSuccess", { name: result.plugin.manifest.name, version: result.plugin.manifest.version }));
|
||||
clearPluginIconCache();
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
notifyComponentUpdatesChanged();
|
||||
selectPlugin(result.plugin.manifest.id);
|
||||
activeSection.value = "installed";
|
||||
}
|
||||
@@ -689,6 +694,7 @@ async function rollbackSelectedPlugin() {
|
||||
toast(t("pluginPlatform.rollbackSuccess", { version: result.plugin.manifest.version }));
|
||||
clearPluginIconCache();
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
notifyComponentUpdatesChanged();
|
||||
selectPlugin(result.plugin.manifest.id);
|
||||
} catch (cause) {
|
||||
toast(translateBackendError(t, cause), 8000);
|
||||
@@ -703,6 +709,7 @@ async function uninstallSelectedPlugin() {
|
||||
operating.value = true;
|
||||
try {
|
||||
installedPlugins.value = await api.uninstallPlugin(definition.plugin.manifest.id);
|
||||
notifyComponentUpdatesChanged();
|
||||
clearPluginIconCache();
|
||||
toast(t("pluginPlatform.uninstallSuccess", { name: definition.plugin.manifest.name }));
|
||||
selectFirstProvider();
|
||||
|
||||
+19
-1
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { InstalledPlugin, PluginRepositoryCatalogResult } from "@/types/database";
|
||||
import type { MarketplacePluginListing } from "@/lib/plugins/pluginMarketplace";
|
||||
import { COMPONENT_PLUGINS_UPDATED_EVENT } from "@/lib/updates/componentUpdateEvents";
|
||||
import { COMPONENT_PLUGINS_UPDATED_EVENT, COMPONENT_UPDATES_CHANGED_EVENT } from "@/lib/updates/componentUpdateEvents";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
listPlugins: vi.fn(),
|
||||
@@ -246,6 +246,24 @@ describe("PluginContributionsPanel batch source validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("PluginContributionsPanel update center synchronization", () => {
|
||||
it("notifies after a single marketplace update without duplicating per batch item", async () => {
|
||||
const listener = vi.fn();
|
||||
window.addEventListener(COMPONENT_UPDATES_CHANGED_EVENT, listener);
|
||||
try {
|
||||
await state.installMarketplaceListing(state.marketplaceListings[0]);
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
|
||||
listener.mockClear();
|
||||
state.selectAllUpdatable();
|
||||
await state.runBatchInstallUpdate();
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
window.removeEventListener(COMPONENT_UPDATES_CHANGED_EVENT, listener);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("PluginContributionsPanel installed plugin pin controls", () => {
|
||||
it("renders selection and pin actions as sibling native buttons", async () => {
|
||||
state.batchMode = false;
|
||||
|
||||
Reference in new issue
Block a user