mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(app): show update progress in its notification
The welcome screen has no status bar, so clicking Install and Restart there showed nothing until the download ended. The notification now stays with the download progress and Cancel, and a cancelled update says so. Fixes #491
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
/** The running update's progress in its balloon, for windows without a status bar (the welcome screen). */
|
||||
import { ProgressBar } from "@/ui";
|
||||
import { useTasks } from "@/workbench/tasks";
|
||||
|
||||
export function UpdateBalloonBody({ task }: { task: string }) {
|
||||
const current = useTasks((s) => s.tasks.find((t) => t.id === task));
|
||||
if (!current || current.done) return null;
|
||||
return (
|
||||
<>
|
||||
{current.message && <div>{current.message}</div>}
|
||||
<ProgressBar value={current.fraction} aria-label={current.title} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -66,13 +66,15 @@ afterEach(() => vi.clearAllMocks());
|
||||
describe("Install and Restart", () => {
|
||||
it("runs as a background task with download progress, then restarts", async () => {
|
||||
const done = installUpdate("0.1.22");
|
||||
expect(expired).toEqual([UPDATE_KEY]);
|
||||
expect(notes.at(-1)).toMatchObject({ title: "Downloading OpenIDEA 0.1.22", key: UPDATE_KEY, sticky: true });
|
||||
expect(notes.at(-1)!.actions![0]!.label).toBe("Cancel");
|
||||
expect(task(INSTALL_TASK_ID)).toMatchObject({ title: "Downloading OpenIDEA 0.1.22", fraction: null, done: false });
|
||||
expect(task(INSTALL_TASK_ID)?.cancel).toBeTypeOf("function");
|
||||
|
||||
channel!.onmessage({ type: "downloading", downloaded: 21 * 1024 * 1024, total: 84 * 1024 * 1024 });
|
||||
expect(task(INSTALL_TASK_ID)).toMatchObject({ fraction: 0.25, message: "21.0 MB of 84.0 MB" });
|
||||
channel!.onmessage({ type: "installing" });
|
||||
expect(notes.at(-1)!.actions).toBeUndefined();
|
||||
expect(task(INSTALL_TASK_ID)).toMatchObject({ fraction: null, message: "Installing…", cancel: undefined });
|
||||
|
||||
install.resolve(ok(null));
|
||||
@@ -99,7 +101,7 @@ describe("Install and Restart", () => {
|
||||
ok({ enabled: true, update: { version: "0.1.22", currentVersion: "0.1.21", notes: null, date: null } }),
|
||||
);
|
||||
await checked;
|
||||
expect(notes).toHaveLength(1);
|
||||
expect(notes.map((n) => n.title)).toEqual(["OpenIDEA 0.1.22 is available", "Downloading OpenIDEA 0.1.22"]);
|
||||
|
||||
install.resolve(ok(null));
|
||||
await tick();
|
||||
@@ -107,7 +109,7 @@ describe("Install and Restart", () => {
|
||||
expect(appInstallUpdate).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("reports a failure with Retry, and stays quiet when cancelled", async () => {
|
||||
it("reports a failure with Retry, and a cancellation", async () => {
|
||||
const failed = installUpdate("0.1.22");
|
||||
install.resolve({ status: "error", error: { kind: "unavailable", message: "connection reset" } });
|
||||
await failed;
|
||||
@@ -127,7 +129,7 @@ describe("Install and Restart", () => {
|
||||
expect(cancelRequest).toHaveBeenCalledWith(appInstallUpdate.mock.calls.at(-1)![0]);
|
||||
install.resolve({ status: "error", error: { kind: "cancelled", message: "cancelled" } });
|
||||
await cancelled;
|
||||
expect(notes).toEqual([]);
|
||||
expect(notes.at(-1)).toMatchObject({ level: "info", title: "Update cancelled", key: UPDATE_KEY });
|
||||
expect(appRestart).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Update check and install. Both run as background tasks in the status bar: the
|
||||
* check while the request is out, the install with download progress and Cancel.
|
||||
* check while the request is out, the install with download progress and Cancel,
|
||||
* which its balloon shows too (the welcome screen has no status bar).
|
||||
* Every entry point (Help menu, toolbar gear, welcome screen, Search Everywhere,
|
||||
* the "is available" notification and its log entry) goes through these, so a
|
||||
* repeated click joins the running check or install instead of starting another.
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
} from "@/platform/ipc";
|
||||
import { notifications } from "@/workbench/contributions";
|
||||
import { upsertTask } from "@/workbench/tasks";
|
||||
import { UpdateBalloonBody } from "./UpdateBalloonBody";
|
||||
|
||||
export const UPDATE_KEY = "app.update";
|
||||
export const CHECK_TASK_ID = "app.update.check";
|
||||
@@ -117,8 +119,22 @@ async function runInstall(version: string) {
|
||||
const request = nextRequestId();
|
||||
const cancel = () => void commands.cancelRequest(request).catch(() => {});
|
||||
const channel = new Channel<UpdateProgress>();
|
||||
const balloon = (actions?: Array<{ label: string; run: () => void }>) =>
|
||||
notifications().notify({
|
||||
level: "info",
|
||||
title,
|
||||
body: <UpdateBalloonBody task={INSTALL_TASK_ID} />,
|
||||
sticky: true,
|
||||
key: UPDATE_KEY,
|
||||
actions,
|
||||
});
|
||||
let installingShown = false;
|
||||
channel.onmessage = (p) => {
|
||||
const { message, fraction } = progressText(p);
|
||||
if (p.type === "installing" && !installingShown) {
|
||||
installingShown = true;
|
||||
balloon();
|
||||
}
|
||||
upsertTask({
|
||||
id: INSTALL_TASK_ID,
|
||||
title,
|
||||
@@ -130,13 +146,15 @@ async function runInstall(version: string) {
|
||||
uncancellable: p.type === "installing" ? t("app.update.installUncancellable") : undefined,
|
||||
});
|
||||
};
|
||||
// The notification's action is spent: progress lives in the status bar from here.
|
||||
notifications().expire?.(UPDATE_KEY);
|
||||
upsertTask({ id: INSTALL_TASK_ID, title, message: null, fraction: null, done: false, cancel });
|
||||
// Replaces the notification whose action is now spent.
|
||||
balloon([{ label: t("app.update.cancel"), run: cancel }]);
|
||||
try {
|
||||
await call(commands.appInstallUpdate(request, channel));
|
||||
} catch (e) {
|
||||
if (!isCancelled(e)) {
|
||||
if (isCancelled(e)) {
|
||||
notifications().notify({ level: "info", title: t("app.update.cancelled"), key: UPDATE_KEY });
|
||||
} else {
|
||||
notifications().notify({
|
||||
level: "error",
|
||||
title: t("app.update.installFailed"),
|
||||
@@ -56,6 +56,8 @@
|
||||
"app.unsaved.saveFailed": "Could not save all files",
|
||||
"app.unsaved.title": "Unsaved Changes",
|
||||
"app.update.available": "OpenIDEA {version} is available",
|
||||
"app.update.cancel": "Cancel",
|
||||
"app.update.cancelled": "Update cancelled",
|
||||
"app.update.checking": "Checking for updates…",
|
||||
"app.update.disabled": "Automatic updates are not configured",
|
||||
"app.update.disabledBody": "Set an update endpoint (OPENIDEA_UPDATE_ENDPOINT or plugins.updater.endpoints) to enable update checks.",
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
"app.unsaved.saveFailed": "无法保存所有文件",
|
||||
"app.unsaved.title": "未保存的更改",
|
||||
"app.update.available": "OpenIDEA {version} 可用",
|
||||
"app.update.cancel": "取消",
|
||||
"app.update.cancelled": "已取消更新",
|
||||
"app.update.checking": "正在检查更新…",
|
||||
"app.update.disabled": "未配置自动更新",
|
||||
"app.update.disabledBody": "设置更新地址(OPENIDEA_UPDATE_ENDPOINT 或 plugins.updater.endpoints)以启用更新检查。",
|
||||
|
||||
Reference in New Issue
Block a user