1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add notifications about change in update status

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-05-19 08:02:06 +03:00
parent 975109b75c
commit a54d366a24
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
11 changed files with 147 additions and 80 deletions

View File

@ -23,6 +23,7 @@ import showApplicationWindowInjectable from "../../main/start-main-application/l
import type { AskBoolean } from "../../main/ask-boolean/ask-boolean.injectable";
import askBooleanInjectable from "../../main/ask-boolean/ask-boolean.injectable";
import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable";
describe("installing update using tray", () => {
let applicationBuilder: ApplicationBuilder;
@ -31,21 +32,23 @@ describe("installing update using tray", () => {
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
let setUpdateOnQuitMock: jest.Mock;
let showApplicationWindowMock: jest.Mock;
let showNotificationMock: jest.Mock;
let showInfoNotificationMock: jest.Mock;
let askBooleanMock: AsyncFnMock<AskBoolean>;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
quitAndInstallUpdateMock = jest.fn();
checkForPlatformUpdatesMock = asyncFn();
downloadPlatformUpdateMock = asyncFn();
setUpdateOnQuitMock = jest.fn();
showApplicationWindowMock = jest.fn();
showNotificationMock = jest.fn(() => () => {});
showInfoNotificationMock = jest.fn(() => () => {});
askBooleanMock = asyncFn();
rendererDi.override(showInfoNotificationInjectable, () => showInfoNotificationMock);
mainDi.override(askBooleanInjectable, () => askBooleanMock);
mainDi.override(showApplicationWindowInjectable, () => showApplicationWindowMock);
mainDi.override(setUpdateOnQuitInjectable, () => setUpdateOnQuitMock);
@ -103,8 +106,8 @@ describe("installing update using tray", () => {
expect(showApplicationWindowMock).not.toHaveBeenCalled();
});
xit("notifies the user that checking for updates is happening", () => {
expect(showNotificationMock).toHaveBeenCalledWith("Checking for updates...");
it("notifies the user that checking for updates is happening", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Checking for updates...");
});
it("user cannot check for updates again", () => {
@ -129,7 +132,7 @@ describe("installing update using tray", () => {
describe("when no new update is discovered", () => {
beforeEach(async () => {
showNotificationMock.mockClear();
showInfoNotificationMock.mockClear();
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: false,
@ -142,8 +145,8 @@ describe("installing update using tray", () => {
expect(showApplicationWindowMock).toHaveBeenCalled();
});
xit("notifies the user", () => {
expect(showNotificationMock).toHaveBeenCalledWith("No new updates available");
it("notifies the user", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("No new updates available");
});
it("does not start downloading update", () => {
@ -189,8 +192,8 @@ describe("installing update using tray", () => {
expect(downloadPlatformUpdateMock).toHaveBeenCalled();
});
xit("notifies the user that download is happening", () => {
expect(showNotificationMock).toHaveBeenCalledWith("Download for version some-version started...");
it("notifies the user that download is happening", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Download for version some-version started...");
});
it("user cannot check for updates again yet", () => {
@ -246,8 +249,8 @@ describe("installing update using tray", () => {
).toBe(true);
});
xit("notifies the user about failed download", () => {
expect(showNotificationMock).toHaveBeenCalledWith("Failed to download update");
it("notifies the user about failed download", () => {
expect(showInfoNotificationMock).toHaveBeenCalledWith("Download of update failed");
});
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
@ -400,6 +403,7 @@ describe("installing update using tray", () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();
@ -425,6 +429,7 @@ describe("installing update using tray", () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();
@ -450,6 +455,7 @@ describe("installing update using tray", () => {
await checkForPlatformUpdatesMock.resolve({
updateWasDiscovered: true,
version: "some-version",
});
expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled();

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Channel } from "../channel/channel-injection-token";
import { channelInjectionToken } from "../channel/channel-injection-token";
export type ApplicationUpdateStatusEventId =
| "checking-for-updates"
| "no-updates-available"
| "download-for-update-started"
| "download-for-update-failed";
export type ApplicationUpdateStatusChannel = Channel<{ eventId: ApplicationUpdateStatusEventId; version?: string }>;
const applicationUpdateStatusChannelInjectable = getInjectable({
id: "application-update-status-channel",
instantiate: (): ApplicationUpdateStatusChannel => ({
id: "application-update-status-channel",
}),
injectionToken: channelInjectionToken,
});
export default applicationUpdateStatusChannelInjectable;

View File

@ -12,7 +12,7 @@ const discoveredUpdateVersionInjectable = getInjectable({
instantiate: (di) => {
const createSyncBox = di.inject(createSyncBoxInjectable);
return createSyncBox<{ version: string; updateChannel: UpdateChannel }>(
return createSyncBox<{ version: string; updateChannel: UpdateChannel } | null>(
"discovered-update-version",
);
},

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createChannel } from "../ipc-channel/create-channel/create-channel";
export const notificationChannel = createChannel<string>("notification:message");

View File

@ -1,21 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import applicationWindowInjectable from "../start-main-application/lens-window/application-window/application-window.injectable";
import { notificationChannel } from "../../common/notification/notification-channel";
const showNotificationInjectable = getInjectable({
id: "show-notification",
instantiate: (di) => {
const applicationWindow = di.inject(applicationWindowInjectable);
return (message: string) => {
applicationWindow.send({ channel: notificationChannel.name, data: [message] });
};
},
});
export default showNotificationInjectable;

View File

@ -7,7 +7,6 @@ import { computed } from "mobx";
import updatingIsEnabledInjectable from "./updating-is-enabled.injectable";
import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token";
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
import showNotificationInjectable from "../show-notification/show-notification.injectable";
import askBooleanInjectable from "../ask-boolean/ask-boolean.injectable";
import quitAndInstallUpdateInjectable from "../electron-app/features/quit-and-install-update.injectable";
import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable";
@ -16,6 +15,9 @@ import updatesAreBeingDiscoveredInjectable from "../../common/application-update
import checkForUpdatesInjectable from "./check-for-updates/check-for-updates.injectable";
import downloadUpdateInjectable from "./download-update/download-update.injectable";
import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
import assert from "assert";
import { sendToAgnosticChannelInjectionToken } from "../../common/channel/send-to-agnostic-channel-injection-token";
import applicationUpdateStatusChannelInjectable from "../../common/application-update/application-update-status-channel.injectable";
const checkForUpdatesTrayItemInjectable = getInjectable({
id: "check-for-updates-tray-item",
@ -24,7 +26,6 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable);
const showNotification = di.inject(showNotificationInjectable);
const askBoolean = di.inject(askBooleanInjectable);
const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
@ -32,6 +33,8 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
const checkForUpdates = di.inject(checkForUpdatesInjectable);
const downloadUpdate = di.inject(downloadUpdateInjectable);
const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken);
const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable);
return {
id: "check-for-updates",
@ -40,7 +43,11 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
label: computed(() => {
if (downloadingUpdateState.value.get()) {
return `Downloading update ${discoveredVersionState.value.get().version} (${progressOfUpdateDownload.value.get()}%)...`;
const discoveredVersion = discoveredVersionState.value.get();
assert(discoveredVersion);
return `Downloading update ${discoveredVersion.version} (${progressOfUpdateDownload.value.get()}%)...`;
}
if (checkingForUpdatesState.value.get()) {
@ -58,13 +65,13 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
const { updateWasDiscovered, version } = await checkForUpdates();
if (updateWasDiscovered) {
showNotification(`Download for version ${version} started...`);
sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "download-for-update-started", version });
// Note: intentional orphan promise to make download happen in the background
downloadUpdate().then(async ({ downloadWasSuccessful }) => {
if (!downloadWasSuccessful) {
showNotification(`Download for update failed`);
sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "download-for-update-failed" });
return;
}

View File

@ -7,17 +7,20 @@ import type { CheckForPlatformUpdates } from "../check-for-platform-updates/chec
import checkForPlatformUpdatesInjectable from "../check-for-platform-updates/check-for-platform-updates.injectable";
import type { UpdateChannel } from "../update-channels";
import selectedUpdateChannelInjectable from "../selected-update-channel.injectable";
import showNotificationInjectable from "../../show-notification/show-notification.injectable";
import updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable";
import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable";
import { runInAction } from "mobx";
import assert from "assert";
import applicationUpdateStatusChannelInjectable from "../../../common/application-update/application-update-status-channel.injectable";
import { sendToAgnosticChannelInjectionToken } from "../../../common/channel/send-to-agnostic-channel-injection-token";
const checkForUpdatesInjectable = getInjectable({
id: "check-for-updates",
instantiate: (di) => {
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const showNotification = di.inject(showNotificationInjectable);
const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken);
const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable);
const checkForPlatformUpdates = di.inject(
checkForPlatformUpdatesInjectable,
@ -37,19 +40,24 @@ const checkForUpdatesInjectable = getInjectable({
const checkForUpdatesStartingFromChannel =
checkForUpdatesStartingFromChannelFor(checkForPlatformUpdates);
showNotification("Checking for updates...");
sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "checking-for-updates" });
const { updateWasDiscovered, version, actualUpdateChannel } =
await checkForUpdatesStartingFromChannel(selectedUpdateChannel.value.get());
if (!updateWasDiscovered) {
showNotification("No new updates available");
sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "no-updates-available" });
}
runInAction(() => {
if (!updateWasDiscovered) {
discoveredVersionState.set(null);
} else {
// TODO: Unacceptable damage caused by strict mode
assert(version);
assert(actualUpdateChannel);
discoveredVersionState.set({
version,
updateChannel: actualUpdateChannel,

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { channelListenerInjectionToken } from "../../common/channel/channel-listener-injection-token";
import type { ApplicationUpdateStatusEventId } from "../../common/application-update/application-update-status-channel.injectable";
import applicationUpdateStatusChannelInjectable from "../../common/application-update/application-update-status-channel.injectable";
import showInfoNotificationInjectable from "../components/notifications/show-info-notification.injectable";
const applicationUpdateStatusListenerInjectable = getInjectable({
id: "application-update-status-listener",
instantiate: (di) => {
const channel = di.inject(applicationUpdateStatusChannelInjectable);
const showInfoNotification = di.inject(showInfoNotificationInjectable);
const eventHandlers: Record<ApplicationUpdateStatusEventId, { handle: (version: string) => void }> = {
"checking-for-updates": {
handle: () => {
showInfoNotification("Checking for updates...");
},
},
"no-updates-available": {
handle: () => {
showInfoNotification("No new updates available");
},
},
"download-for-update-started": {
handle: (version) => {
showInfoNotification(`Download for version ${version} started...`);
},
},
"download-for-update-failed": {
handle: () => {
showInfoNotification("Download of update failed");
},
},
};
return {
channel,
handler: ({ eventId, version }: { eventId: ApplicationUpdateStatusEventId; version: string }) => {
eventHandlers[eventId].handle(version);
},
};
},
injectionToken: channelListenerInjectionToken,
});
export default applicationUpdateStatusListenerInjectable;

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ipcChannelListenerInjectionToken } from "../../ipc-channel-listeners/ipc-channel-listener-injection-token";
import { notificationChannel } from "../../../common/notification/notification-channel";
import { Notifications } from "./index";
const notificationListenerInjectable = getInjectable({
id: "notification-listener",
instantiate: () => ({
channel: notificationChannel,
handle: (message: string) => {
Notifications.shortInfo(message);
},
}),
causesSideEffects: true,
injectionToken: ipcChannelListenerInjectionToken,
});
export default notificationListenerInjectable;

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { notificationsStore, NotificationStatus } from "./notifications.store";
const showInfoNotificationInjectable = getInjectable({
id: "show-info-notification",
instantiate: () => (message: string) =>
notificationsStore.add({
status: NotificationStatus.INFO,
timeout: 5000,
message,
}),
causesSideEffects: true,
});
export default showInfoNotificationInjectable;

View File

@ -43,8 +43,6 @@ import type { IpcRenderer } from "electron";
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
import { observable } from "mobx";
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
import notificationListenerInjectable from "./components/notifications/notification-listener.injectable";
import { notificationChannel } from "../common/notification/notification-channel";
export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
const {
@ -121,8 +119,6 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
di.override(getValueFromRegisteredChannelInjectable, () => () => Promise.resolve(undefined as never));
di.override(registerIpcChannelListenerInjectable, () => () => undefined);
di.override(notificationListenerInjectable, () => ({ channel: notificationChannel, handle: () => {} }));
overrideFsWithFakes(di);
di.override(focusWindowInjectable, () => () => {});