fix(plugins): block updates while connections are active
This commit is contained in:
15 files changed
+571
-40
No files matched your search
@@ -204,7 +204,7 @@ async function installMarketplaceListing(listing: MarketplacePluginListing) {
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
selectPlugin(result.plugin.manifest.id);
|
||||
} catch (cause) {
|
||||
toast(cause instanceof Error ? cause.message : String(cause), 8000);
|
||||
toast(translateBackendError(t, cause), 8000);
|
||||
// A failed install can still have mutated the store (a partially replaced version directory, for
|
||||
// instance), so re-read the installed list instead of leaving the card on state it may no longer
|
||||
// describe.
|
||||
@@ -218,8 +218,9 @@ function batchSummaryKey(outcome: { succeeded: unknown[]; failed: { name: string
|
||||
return outcome.failed.length ? "pluginPlatform.batchSummaryWithFailures" : "pluginPlatform.batchSummary";
|
||||
}
|
||||
|
||||
function reportBatchSummary(outcome: { succeeded: unknown[]; failed: { name: string }[] }) {
|
||||
function reportBatchSummary(outcome: { succeeded: unknown[]; failed: { name: string; error: string }[] }) {
|
||||
const failedNames = outcome.failed.map((failure) => failure.name).join("、");
|
||||
error.value = outcome.failed.map((failure) => `${failure.name}: ${translateBackendError(t, failure.error)}`).join("\n");
|
||||
toast(t(batchSummaryKey(outcome), { success: outcome.succeeded.length, failed: outcome.failed.length, names: failedNames }), outcome.failed.length ? 8000 : 4000);
|
||||
}
|
||||
|
||||
@@ -227,7 +228,7 @@ async function refreshAfterBatch() {
|
||||
try {
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
} catch (cause) {
|
||||
error.value = t("pluginPlatform.batchRefreshFailed", { error: cause instanceof Error ? cause.message : String(cause) });
|
||||
error.value = [error.value, t("pluginPlatform.batchRefreshFailed", { error: cause instanceof Error ? cause.message : String(cause) })].filter(Boolean).join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +662,7 @@ async function rollbackSelectedPlugin() {
|
||||
installedPlugins.value = await api.listPlugins();
|
||||
selectPlugin(result.plugin.manifest.id);
|
||||
} catch (cause) {
|
||||
toast(cause instanceof Error ? cause.message : String(cause), 5000);
|
||||
toast(translateBackendError(t, cause), 8000);
|
||||
} finally {
|
||||
operating.value = false;
|
||||
}
|
||||
@@ -732,7 +733,7 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<div ref="panelRootRef" class="plugin-center-view relative mx-auto flex h-full w-full max-w-6xl flex-col gap-4 overflow-hidden px-6 py-6" @dragenter="onWebDragEnter" @dragover="onWebDragOver" @dragleave="onWebDragLeave" @drop="onWebDrop">
|
||||
<input ref="webFileInput" type="file" accept=".dbxp" class="hidden" @change="handleWebPackage" />
|
||||
<div v-if="error" class="shrink-0 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive">{{ error }}</div>
|
||||
<div v-if="error" class="shrink-0 whitespace-pre-wrap rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive">{{ error }}</div>
|
||||
|
||||
<Tabs v-model="activeSection" class="min-h-0 flex-1 gap-3">
|
||||
<TabsList class="grid h-9 w-full grid-cols-3">
|
||||
|
||||
+29
-2
@@ -366,6 +366,33 @@ describe("PluginContributionsPanel mutation exclusion", () => {
|
||||
});
|
||||
|
||||
describe("PluginContributionsPanel completed batch outcomes", () => {
|
||||
it.each(["marketplace", "package", "url", "rollback"] as const)("shows related connection names when %s is blocked", async (entry) => {
|
||||
const blocked = new Error("Plugin update blocked by active connections: Production S3");
|
||||
const mutations = {
|
||||
marketplace: mocks.installMarketplacePlugin,
|
||||
package: mocks.installPluginPackage,
|
||||
url: mocks.installPluginPackageFromUrl,
|
||||
rollback: mocks.rollbackPlugin,
|
||||
};
|
||||
mutations[entry].mockRejectedValueOnce(blocked);
|
||||
if (entry === "marketplace") await state.installMarketplaceListing(state.marketplaceListings[0]);
|
||||
else if (entry === "package") await state.installPlugin("plugin.dbxp");
|
||||
else if (entry === "url") await state.installPluginFromUrl();
|
||||
else await state.rollbackSelectedPlugin();
|
||||
expect(mocks.toast).toHaveBeenLastCalledWith('pluginPlatform.updateBlockedByConnections:{"labels":"Production S3"}', 8000);
|
||||
expect(mocks.listPlugins).toHaveBeenCalledTimes(entry === "marketplace" ? 1 : 0);
|
||||
});
|
||||
|
||||
it("shows per-plugin blockers while continuing other batch updates", async () => {
|
||||
state.selectAllUpdatable();
|
||||
mocks.installMarketplacePlugin.mockRejectedValueOnce(new Error("Plugin update blocked by active connections: Production S3"));
|
||||
await state.runBatchInstallUpdate();
|
||||
expect(mocks.installMarketplacePlugin).toHaveBeenCalledTimes(3);
|
||||
expect(state.error).toBe('a: pluginPlatform.updateBlockedByConnections:{"labels":"Production S3"}');
|
||||
await nextTick();
|
||||
expect(host.textContent).toContain("Production S3");
|
||||
});
|
||||
|
||||
it.each(batches.flatMap((batch) => [0, 1, 3].map((failures) => ({ batch, failures }))))("preserves batch $batch outcomes with $failures failures when refresh rejects", async ({ batch, failures }) => {
|
||||
state.selectAllUpdatable();
|
||||
state.selectedInstalledIds = new Set(["a", "b", "c"]);
|
||||
@@ -390,7 +417,7 @@ describe("PluginContributionsPanel completed batch outcomes", () => {
|
||||
expect(mutation).toHaveBeenCalledTimes(3);
|
||||
expect(mocks.toast).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.toast).toHaveBeenLastCalledWith(summary, failures ? 8000 : 4000);
|
||||
expect(state.error).toBe('pluginPlatform.batchRefreshFailed:{"error":"refresh offline"}');
|
||||
expect(state.error).toBe([...["a", "b", "c"].slice(0, failures).map((name) => `${name}: denied`), 'pluginPlatform.batchRefreshFailed:{"error":"refresh offline"}'].join("\n"));
|
||||
await nextTick();
|
||||
expect(host.textContent).toContain(state.error);
|
||||
expect(state.batchRunning).toBe(false);
|
||||
@@ -414,7 +441,7 @@ describe("PluginContributionsPanel completed batch outcomes", () => {
|
||||
expect(mutation.mock.calls.map(([request]) => (batch === "install" ? request.pluginId : request))).toEqual(["a", "b", "c"]);
|
||||
expect(mocks.listPlugins).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.toast).toHaveBeenLastCalledWith('pluginPlatform.batchSummaryWithFailures:{"success":2,"failed":1,"names":"b"}', 8000);
|
||||
expect(state.error).toBe("");
|
||||
expect(state.error).toBe("b: denied");
|
||||
expect(state.batchRunning).toBe(false);
|
||||
expect(state.selectedListingKeys.size).toBe(0);
|
||||
expect(state.selectedInstalledIds.size).toBe(0);
|
||||
|
||||
Reference in new issue
Block a user