diff --git a/src/behaviours/update-app/installing-update-using-tray.test.ts b/src/behaviours/update-app/installing-update-using-tray.test.ts index 7d94eea490..d4ad3931a0 100644 --- a/src/behaviours/update-app/installing-update-using-tray.test.ts +++ b/src/behaviours/update-app/installing-update-using-tray.test.ts @@ -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; let setUpdateOnQuitMock: jest.Mock; let showApplicationWindowMock: jest.Mock; - let showNotificationMock: jest.Mock; + let showInfoNotificationMock: jest.Mock; let askBooleanMock: AsyncFnMock; 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(); diff --git a/src/common/application-update/application-update-status-channel.injectable.ts b/src/common/application-update/application-update-status-channel.injectable.ts new file mode 100644 index 0000000000..8d92ca4070 --- /dev/null +++ b/src/common/application-update/application-update-status-channel.injectable.ts @@ -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; diff --git a/src/common/application-update/discovered-update-version/discovered-update-version.injectable.ts b/src/common/application-update/discovered-update-version/discovered-update-version.injectable.ts index 1817e49b57..75f6d42dd6 100644 --- a/src/common/application-update/discovered-update-version/discovered-update-version.injectable.ts +++ b/src/common/application-update/discovered-update-version/discovered-update-version.injectable.ts @@ -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", ); }, diff --git a/src/common/notification/notification-channel.ts b/src/common/notification/notification-channel.ts deleted file mode 100644 index f09b7bf1f2..0000000000 --- a/src/common/notification/notification-channel.ts +++ /dev/null @@ -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("notification:message"); diff --git a/src/main/show-notification/show-notification.injectable.ts b/src/main/show-notification/show-notification.injectable.ts deleted file mode 100644 index 323323e184..0000000000 --- a/src/main/show-notification/show-notification.injectable.ts +++ /dev/null @@ -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; diff --git a/src/main/update-app/check-for-updates-tray-item.injectable.ts b/src/main/update-app/check-for-updates-tray-item.injectable.ts index 5e67354d29..41f6391dc8 100644 --- a/src/main/update-app/check-for-updates-tray-item.injectable.ts +++ b/src/main/update-app/check-for-updates-tray-item.injectable.ts @@ -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; } diff --git a/src/main/update-app/check-for-updates/check-for-updates.injectable.ts b/src/main/update-app/check-for-updates/check-for-updates.injectable.ts index 02ed818359..8d06439566 100644 --- a/src/main/update-app/check-for-updates/check-for-updates.injectable.ts +++ b/src/main/update-app/check-for-updates/check-for-updates.injectable.ts @@ -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, diff --git a/src/renderer/application-update/application-update-status-listener.injectable.ts b/src/renderer/application-update/application-update-status-listener.injectable.ts new file mode 100644 index 0000000000..9bbaca6391 --- /dev/null +++ b/src/renderer/application-update/application-update-status-listener.injectable.ts @@ -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 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; diff --git a/src/renderer/components/notifications/notification-listener.injectable.ts b/src/renderer/components/notifications/notification-listener.injectable.ts deleted file mode 100644 index bc606682d9..0000000000 --- a/src/renderer/components/notifications/notification-listener.injectable.ts +++ /dev/null @@ -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; diff --git a/src/renderer/components/notifications/show-info-notification.injectable.ts b/src/renderer/components/notifications/show-info-notification.injectable.ts new file mode 100644 index 0000000000..47c91715cc --- /dev/null +++ b/src/renderer/components/notifications/show-info-notification.injectable.ts @@ -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; diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index 1b9f1a6c39..d4765706a7 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -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, () => () => {});