mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce way to install update directly from tray. Make application updater unit testable... (#5433)
* Extract product name as injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tray items comply with Open Closed Principle Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Replace duplicated overrides with global Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add behaviour for navigating to preferences using tray Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a tray item for updating application Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant indirection Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injectable for package.json being side-effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate file to directory containing feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using injectable for limiting side effect Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing injection token for implementation of tray item Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove resetting state for update is ready to be installed for being unclear Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make label of tray item reactive Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract updating is enabled to separate injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Expand scope of behaviour for updating using tray also contain checking for updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of updates from multiple update channels Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Start installing updates automatically when quitting application Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show application window when checking of updates has happened Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show notifications and dialog for downloading update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement naive notifications for version updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement downloading of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for channel abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typing after enabling strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for a state that is shared between environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract states of application update to be usable from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Handle failing download of update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for window visibility actually work Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate code for sending messages between processes to a window Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Split bloated dependency in smaller pieces Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make state of download progress accessible from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename files for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move channel abstraction to more global directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Enhance typing of channels and sync-box Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate channel abstraction types Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update asyncFn to support strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix snapshot after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injection token for channels to allow injecting all of them at once Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * 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> * Rename property for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak code style Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make notifications unit testable in behaviours Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add implementation for asking boolean over processes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize responsibilities for checking updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize tests for installing update under separate scenarios Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make stuff happening when root frame is rendered unit testable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce periodical check for updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Allow downgrading app versions Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for checking of updates in application menu Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make test less prone to fail for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant boilerplate Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tests for specific migrations less prone to failing for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move shared stuff under common Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using single source of truth for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract tests for installing update from different update channels to separate scenario Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using release channel of installed application version as default value for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate usage of channel abstraction to same implementation Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Channel abstraction support return values Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix direct calling of runnables Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Synchronize initial values of sync boxes when window starts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak message of question from user Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate names of directories Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unimplemented scenario from test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Improve name of test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant overrides Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Animate deterministic in unit tests Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify even more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more and more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add todo for cleaning unacceptable code encountered Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Improve name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make unit test more strict Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Enhance name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce dependency to get random IDs Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make asking of boolean value from user not require explicit ID for question Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify code for asking of boolean value from user Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make setting of initial state for sync boxes not trigger irrelevant messaging to main Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a channel have default type for sent and returned message Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce higher order function to log errors in decorated functions Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Export type for error logging Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak test name Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce higher order function for suppressing errors Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate some explicit error handlings to proper level of abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make higher order function for logging errors support asynchronous rejecting with non error instance Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make overridden version of application exactly the one required by unit test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Mark injectable causing side effects Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert not required changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for asserting a promise more strict Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make dependencies readonly Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication for disposers Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement initial values for sync-boxes Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Separate concept of message and request channels Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce tests for requesting from channel in renderer Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement requesting from renderer in main Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert "Implement requesting from renderer in main" This reverts commit d3e7899d7900516f3dbfacdb317a453202318305. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak typing of request channel listeners to get rid of unexpected undefined Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unused variable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak timing of sentry setup Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Require messages for MessageChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Require requests and responses for RequestChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make different MessageChannels not require explicit "extends JsonObject" Note: Non-escaped lint breaks type here for forcing interface over type. Reasonable effort brought no understanding for what is the relevant difference between the two. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a primitive argument an object for readability Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make typing of higher order function for error suppression not lie Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Serialize messages in channels to make IPC not blow up Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a way to make intentional orphan promises uniform, controllable and deliberate Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make downloading of update and what follows more deliberate as orphan promise Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move another utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix incorrect name of file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Round percentage of update download progress in tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix rebase conflicts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix CheckForUpdate type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
229 files changed
+8652
-1217
No files matched your search
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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 electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
|
||||
import type { UpdateChannel } from "../../../common/application-update/update-channels";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { UpdateCheckResult } from "electron-updater";
|
||||
|
||||
export type CheckForUpdatesResult = {
|
||||
updateWasDiscovered: false;
|
||||
} | {
|
||||
updateWasDiscovered: true;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type CheckForPlatformUpdates = (updateChannel: UpdateChannel, opts: { allowDowngrade: boolean }) => Promise<CheckForUpdatesResult>;
|
||||
|
||||
const checkForPlatformUpdatesInjectable = getInjectable({
|
||||
id: "check-for-platform-updates",
|
||||
|
||||
instantiate: (di): CheckForPlatformUpdates => {
|
||||
const electronUpdater = di.inject(electronUpdaterInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (updateChannel, { allowDowngrade }) => {
|
||||
electronUpdater.channel = updateChannel.id;
|
||||
electronUpdater.autoDownload = false;
|
||||
electronUpdater.allowDowngrade = allowDowngrade;
|
||||
|
||||
let result: UpdateCheckResult;
|
||||
|
||||
try {
|
||||
result = await electronUpdater.checkForUpdates();
|
||||
} catch (error) {
|
||||
logger.error("[UPDATE-APP/CHECK-FOR-UPDATES]", error);
|
||||
|
||||
return {
|
||||
updateWasDiscovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { updateInfo, cancellationToken } = result;
|
||||
|
||||
if (!cancellationToken) {
|
||||
return {
|
||||
updateWasDiscovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
updateWasDiscovered: true,
|
||||
version: updateInfo.version,
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default checkForPlatformUpdatesInjectable;
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { AppUpdater, UpdateCheckResult } from "electron-updater";
|
||||
import type { CheckForPlatformUpdates } from "./check-for-platform-updates.injectable";
|
||||
import checkForPlatformUpdatesInjectable from "./check-for-platform-updates.injectable";
|
||||
import type { UpdateChannel, UpdateChannelId } from "../../../common/application-update/update-channels";
|
||||
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { Logger } from "../../../common/logger";
|
||||
|
||||
describe("check-for-platform-updates", () => {
|
||||
let checkForPlatformUpdates: CheckForPlatformUpdates;
|
||||
let electronUpdaterFake: AppUpdater;
|
||||
let checkForUpdatesMock: AsyncFnMock<() => UpdateCheckResult>;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
|
||||
checkForUpdatesMock = asyncFn();
|
||||
|
||||
electronUpdaterFake = {
|
||||
channel: undefined,
|
||||
autoDownload: undefined,
|
||||
allowDowngrade: undefined,
|
||||
|
||||
checkForUpdates: checkForUpdatesMock,
|
||||
} as unknown as AppUpdater;
|
||||
|
||||
di.override(electronUpdaterInjectable, () => electronUpdaterFake);
|
||||
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
di.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
||||
|
||||
checkForPlatformUpdates = di.inject(checkForPlatformUpdatesInjectable);
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testUpdateChannel: UpdateChannel = {
|
||||
id: "some-update-channel" as UpdateChannelId,
|
||||
label: "Some update channel",
|
||||
moreStableUpdateChannel: null,
|
||||
};
|
||||
|
||||
actualPromise = checkForPlatformUpdates(testUpdateChannel, { allowDowngrade: true });
|
||||
});
|
||||
|
||||
it("sets update channel", () => {
|
||||
expect(electronUpdaterFake.channel).toBe("some-update-channel");
|
||||
});
|
||||
|
||||
it("sets flag for allowing downgrade", () => {
|
||||
expect(electronUpdaterFake.allowDowngrade).toBe(true);
|
||||
});
|
||||
|
||||
it("disables auto downloading for being controlled", () => {
|
||||
expect(electronUpdaterFake.autoDownload).toBe(false);
|
||||
});
|
||||
|
||||
it("checks for updates", () => {
|
||||
expect(checkForUpdatesMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when checking for updates resolves with update, resolves with the discovered update", async () => {
|
||||
await checkForUpdatesMock.resolve({
|
||||
updateInfo: {
|
||||
version: "some-version",
|
||||
},
|
||||
|
||||
cancellationToken: "some-cancellation-token",
|
||||
} as unknown as UpdateCheckResult);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ updateWasDiscovered: true, version: "some-version" });
|
||||
});
|
||||
|
||||
it("when checking for updates resolves without update, resolves with update not being discovered", async () => {
|
||||
await checkForUpdatesMock.resolve({
|
||||
updateInfo: {
|
||||
version: "some-version-that-matches-to-current-installed-version",
|
||||
},
|
||||
|
||||
cancellationToken: null,
|
||||
} as unknown as UpdateCheckResult);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ updateWasDiscovered: false });
|
||||
});
|
||||
|
||||
describe("when checking for updates rejects", () => {
|
||||
let errorStub: Error;
|
||||
|
||||
beforeEach(() => {
|
||||
errorStub = new Error("Some error");
|
||||
|
||||
checkForUpdatesMock.reject(errorStub);
|
||||
});
|
||||
|
||||
it("logs errors", () => {
|
||||
expect(logErrorMock).toHaveBeenCalledWith("[UPDATE-APP/CHECK-FOR-UPDATES]", errorStub);
|
||||
});
|
||||
|
||||
it("resolves with update not being discovered", async () => {
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ updateWasDiscovered: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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 { 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 discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable";
|
||||
import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable";
|
||||
import updatesAreBeingDiscoveredInjectable from "../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable";
|
||||
import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
|
||||
import assert from "assert";
|
||||
import processCheckingForUpdatesInjectable from "./check-for-updates/process-checking-for-updates.injectable";
|
||||
import { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
|
||||
const checkForUpdatesTrayItemInjectable = getInjectable({
|
||||
id: "check-for-updates-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
|
||||
const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable);
|
||||
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
|
||||
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
|
||||
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
|
||||
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "check-for-updates",
|
||||
parentId: null,
|
||||
orderNumber: 30,
|
||||
|
||||
label: computed(() => {
|
||||
if (downloadingUpdateState.value.get()) {
|
||||
const discoveredVersion = discoveredVersionState.value.get();
|
||||
|
||||
assert(discoveredVersion);
|
||||
|
||||
const roundedPercentage = Math.round(progressOfUpdateDownload.value.get().percentage);
|
||||
|
||||
return `Downloading update ${discoveredVersion.version} (${roundedPercentage}%)...`;
|
||||
}
|
||||
|
||||
if (checkingForUpdatesState.value.get()) {
|
||||
return "Checking for updates...";
|
||||
}
|
||||
|
||||
return "Check for updates";
|
||||
}),
|
||||
|
||||
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
|
||||
|
||||
visible: computed(() => updatingIsEnabled),
|
||||
|
||||
click: pipeline(
|
||||
async () => {
|
||||
await processCheckingForUpdates();
|
||||
|
||||
await showApplicationWindow();
|
||||
},
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Checking for updates failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default checkForUpdatesTrayItemInjectable;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { ApplicationUpdateStatusChannelMessage } from "../../../common/application-update/application-update-status-channel.injectable";
|
||||
import { messageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import applicationUpdateStatusChannelInjectable from "../../../common/application-update/application-update-status-channel.injectable";
|
||||
|
||||
const broadcastChangeInUpdatingStatusInjectable = getInjectable({
|
||||
id: "broadcast-change-in-updating-status",
|
||||
|
||||
instantiate: (di) => {
|
||||
const messageToChannel = di.inject(messageToChannelInjectionToken);
|
||||
const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable);
|
||||
|
||||
return (data: ApplicationUpdateStatusChannelMessage) => {
|
||||
messageToChannel(applicationUpdateStatusChannel, data);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default broadcastChangeInUpdatingStatusInjectable;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 { UpdateChannel } from "../../../common/application-update/update-channels";
|
||||
import checkForPlatformUpdatesInjectable from "../check-for-platform-updates/check-for-platform-updates.injectable";
|
||||
import updateCanBeDowngradedInjectable from "./update-can-be-downgraded.injectable";
|
||||
|
||||
export type CheckForUpdatesFromChannelResult = {
|
||||
updateWasDiscovered: false;
|
||||
} | {
|
||||
updateWasDiscovered: true;
|
||||
version: string;
|
||||
actualUpdateChannel: UpdateChannel;
|
||||
};
|
||||
|
||||
const checkForUpdatesStartingFromChannelInjectable = getInjectable({
|
||||
id: "check-for-updates-starting-from-channel",
|
||||
|
||||
instantiate: (di) => {
|
||||
const checkForPlatformUpdates = di.inject(
|
||||
checkForPlatformUpdatesInjectable,
|
||||
);
|
||||
|
||||
const updateCanBeDowngraded = di.inject(updateCanBeDowngradedInjectable);
|
||||
|
||||
const _recursiveCheck = async (
|
||||
updateChannel: UpdateChannel,
|
||||
): Promise<CheckForUpdatesFromChannelResult> => {
|
||||
const result = await checkForPlatformUpdates(updateChannel, {
|
||||
allowDowngrade: updateCanBeDowngraded.get(),
|
||||
});
|
||||
|
||||
if (result.updateWasDiscovered) {
|
||||
return {
|
||||
updateWasDiscovered: true,
|
||||
version: result.version,
|
||||
actualUpdateChannel: updateChannel,
|
||||
};
|
||||
}
|
||||
|
||||
if (updateChannel.moreStableUpdateChannel) {
|
||||
return await _recursiveCheck(updateChannel.moreStableUpdateChannel);
|
||||
}
|
||||
|
||||
return { updateWasDiscovered: false };
|
||||
};
|
||||
|
||||
return _recursiveCheck;
|
||||
},
|
||||
});
|
||||
|
||||
export default checkForUpdatesStartingFromChannelInjectable;
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.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 askBooleanInjectable from "../../ask-boolean/ask-boolean.injectable";
|
||||
import quitAndInstallUpdateInjectable from "../../electron-app/features/quit-and-install-update.injectable";
|
||||
import downloadUpdateInjectable from "../download-update/download-update.injectable";
|
||||
import broadcastChangeInUpdatingStatusInjectable from "./broadcast-change-in-updating-status.injectable";
|
||||
import checkForUpdatesStartingFromChannelInjectable from "./check-for-updates-starting-from-channel.injectable";
|
||||
import withOrphanPromiseInjectable from "../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
|
||||
|
||||
const processCheckingForUpdatesInjectable = getInjectable({
|
||||
id: "process-checking-for-updates",
|
||||
|
||||
instantiate: (di) => {
|
||||
const askBoolean = di.inject(askBooleanInjectable);
|
||||
const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable);
|
||||
const downloadUpdate = di.inject(downloadUpdateInjectable);
|
||||
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
|
||||
const broadcastChangeInUpdatingStatus = di.inject(broadcastChangeInUpdatingStatusInjectable);
|
||||
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
|
||||
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
|
||||
const checkForUpdatesStartingFromChannel = di.inject(checkForUpdatesStartingFromChannelInjectable);
|
||||
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
|
||||
|
||||
return async () => {
|
||||
broadcastChangeInUpdatingStatus({ eventId: "checking-for-updates" });
|
||||
|
||||
runInAction(() => {
|
||||
checkingForUpdatesState.set(true);
|
||||
});
|
||||
|
||||
const result = await checkForUpdatesStartingFromChannel(selectedUpdateChannel.value.get());
|
||||
|
||||
if (!result.updateWasDiscovered) {
|
||||
broadcastChangeInUpdatingStatus({ eventId: "no-updates-available" });
|
||||
|
||||
runInAction(() => {
|
||||
discoveredVersionState.set(null);
|
||||
checkingForUpdatesState.set(false);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { version, actualUpdateChannel } = result;
|
||||
|
||||
broadcastChangeInUpdatingStatus({
|
||||
eventId: "download-for-update-started",
|
||||
version,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
discoveredVersionState.set({
|
||||
version,
|
||||
updateChannel: actualUpdateChannel,
|
||||
});
|
||||
|
||||
checkingForUpdatesState.set(false);
|
||||
});
|
||||
|
||||
withOrphanPromise(async () => {
|
||||
const { downloadWasSuccessful } = await downloadUpdate();
|
||||
|
||||
if (!downloadWasSuccessful) {
|
||||
broadcastChangeInUpdatingStatus({
|
||||
eventId: "download-for-update-failed",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const userWantsToInstallUpdate = await askBoolean({
|
||||
title: "Update Available",
|
||||
|
||||
question: `Version ${version} of Lens IDE is available and ready to be installed. Would you like to update now?\n\n` +
|
||||
`Lens should restart automatically, if it doesn't please restart manually. Installed extensions might require updating.`,
|
||||
});
|
||||
|
||||
if (userWantsToInstallUpdate) {
|
||||
quitAndInstallUpdate();
|
||||
}
|
||||
})();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default processCheckingForUpdatesInjectable;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import { SemVer } from "semver";
|
||||
|
||||
const updateCanBeDowngradedInjectable = getInjectable({
|
||||
id: "update-can-be-downgraded",
|
||||
|
||||
instantiate: (di) => {
|
||||
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
|
||||
const appVersion = di.inject(appVersionInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const semVer = new SemVer(appVersion);
|
||||
|
||||
return (
|
||||
semVer.prerelease[0] !==
|
||||
selectedUpdateChannel.value.get().id
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default updateCanBeDowngradedInjectable;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { ProgressInfo } from "electron-updater";
|
||||
import type { ProgressOfDownload } from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
|
||||
|
||||
export type DownloadPlatformUpdate = (
|
||||
onDownloadProgress: (arg: ProgressOfDownload) => void
|
||||
) => Promise<{ downloadWasSuccessful: boolean }>;
|
||||
|
||||
const downloadPlatformUpdateInjectable = getInjectable({
|
||||
id: "download-platform-update",
|
||||
|
||||
instantiate: (di): DownloadPlatformUpdate => {
|
||||
const electronUpdater = di.inject(electronUpdaterInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (onDownloadProgress) => {
|
||||
onDownloadProgress({ percentage: 0 });
|
||||
|
||||
const updateDownloadProgress = ({ percent: percentage }: ProgressInfo) =>
|
||||
onDownloadProgress({ percentage });
|
||||
|
||||
electronUpdater.on("download-progress", updateDownloadProgress);
|
||||
|
||||
try {
|
||||
await electronUpdater.downloadUpdate();
|
||||
} catch(error) {
|
||||
logger.error("[UPDATE-APP/DOWNLOAD]", error);
|
||||
|
||||
return { downloadWasSuccessful: false };
|
||||
} finally {
|
||||
electronUpdater.off("download-progress", updateDownloadProgress);
|
||||
}
|
||||
|
||||
return { downloadWasSuccessful: true };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default downloadPlatformUpdateInjectable;
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
|
||||
import type { DownloadPlatformUpdate } from "./download-platform-update.injectable";
|
||||
import downloadPlatformUpdateInjectable from "./download-platform-update.injectable";
|
||||
import type { AppUpdater } from "electron-updater";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { Logger } from "../../../common/logger";
|
||||
|
||||
describe("download-platform-update", () => {
|
||||
let downloadPlatformUpdate: DownloadPlatformUpdate;
|
||||
let downloadUpdateMock: AsyncFnMock<() => void>;
|
||||
let electronUpdaterFake: AppUpdater;
|
||||
let electronUpdaterOnMock: jest.Mock;
|
||||
let electronUpdaterOffMock: jest.Mock;
|
||||
let di: DiContainer;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting();
|
||||
|
||||
downloadUpdateMock = asyncFn();
|
||||
electronUpdaterOnMock = jest.fn();
|
||||
electronUpdaterOffMock = jest.fn();
|
||||
|
||||
electronUpdaterFake = {
|
||||
channel: undefined,
|
||||
autoDownload: undefined,
|
||||
|
||||
on: electronUpdaterOnMock,
|
||||
off: electronUpdaterOffMock,
|
||||
|
||||
downloadUpdate: downloadUpdateMock,
|
||||
} as unknown as AppUpdater;
|
||||
|
||||
di.override(electronUpdaterInjectable, () => electronUpdaterFake);
|
||||
|
||||
logErrorMock = jest.fn();
|
||||
di.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
||||
|
||||
downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable);
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<{ downloadWasSuccessful: boolean }>;
|
||||
let onDownloadProgressMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
onDownloadProgressMock = jest.fn();
|
||||
|
||||
actualPromise = downloadPlatformUpdate(onDownloadProgressMock);
|
||||
});
|
||||
|
||||
it("calls for downloading of update", () => {
|
||||
expect(downloadUpdateMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("starts progress of download from 0", () => {
|
||||
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 0 });
|
||||
});
|
||||
|
||||
describe("when downloading progresses", () => {
|
||||
beforeEach(() => {
|
||||
onDownloadProgressMock.mockClear();
|
||||
|
||||
const [, callback] = electronUpdaterOnMock.mock.calls.find(
|
||||
([event]) => event === "download-progress",
|
||||
);
|
||||
|
||||
callback({
|
||||
percent: 42,
|
||||
total: 0,
|
||||
delta: 0,
|
||||
transferred: 0,
|
||||
bytesPerSecond: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("updates progress of the download", () => {
|
||||
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 42 });
|
||||
});
|
||||
|
||||
describe("when downloading resolves", () => {
|
||||
beforeEach(async () => {
|
||||
onDownloadProgressMock.mockClear();
|
||||
|
||||
await downloadUpdateMock.resolve();
|
||||
});
|
||||
|
||||
it("resolves with success", async () => {
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ downloadWasSuccessful: true });
|
||||
});
|
||||
|
||||
it("does not reset progress of download yet", () => {
|
||||
expect(onDownloadProgressMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("stops watching for download progress", () => {
|
||||
expect(electronUpdaterOffMock).toHaveBeenCalledWith(
|
||||
"download-progress",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("when starting download again, resets progress of download", () => {
|
||||
downloadPlatformUpdate(onDownloadProgressMock);
|
||||
|
||||
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("when downloading rejects", () => {
|
||||
let errorStub: Error;
|
||||
|
||||
beforeEach(() => {
|
||||
errorStub = new Error("Some error");
|
||||
|
||||
downloadUpdateMock.reject(errorStub);
|
||||
});
|
||||
|
||||
it("logs error", () => {
|
||||
expect(logErrorMock).toHaveBeenCalledWith("[UPDATE-APP/DOWNLOAD]", errorStub);
|
||||
});
|
||||
|
||||
it("stops watching for download progress", () => {
|
||||
expect(electronUpdaterOffMock).toHaveBeenCalledWith(
|
||||
"download-progress",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves with failure", async () => {
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ downloadWasSuccessful: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 downloadPlatformUpdateInjectable from "../download-platform-update/download-platform-update.injectable";
|
||||
import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable";
|
||||
import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable";
|
||||
import { action, runInAction } from "mobx";
|
||||
import type { ProgressOfDownload } from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
|
||||
import progressOfUpdateDownloadInjectable from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
|
||||
|
||||
const downloadUpdateInjectable = getInjectable({
|
||||
id: "download-update",
|
||||
|
||||
instantiate: (di) => {
|
||||
const downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable);
|
||||
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
|
||||
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
|
||||
const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable);
|
||||
|
||||
const updateDownloadProgress = action((progressOfDownload: ProgressOfDownload) => {
|
||||
progressOfUpdateDownload.set(progressOfDownload);
|
||||
});
|
||||
|
||||
return async () => {
|
||||
runInAction(() => {
|
||||
progressOfUpdateDownload.set({ percentage: 0 });
|
||||
downloadingUpdateState.set(true);
|
||||
});
|
||||
|
||||
const { downloadWasSuccessful } = await downloadPlatformUpdate(
|
||||
updateDownloadProgress,
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
if (!downloadWasSuccessful) {
|
||||
discoveredVersionState.set(null);
|
||||
}
|
||||
|
||||
downloadingUpdateState.set(false);
|
||||
});
|
||||
|
||||
return { downloadWasSuccessful };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default downloadUpdateInjectable;
|
||||
@@ -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 { computed } from "mobx";
|
||||
import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token";
|
||||
import quitAndInstallUpdateInjectable from "../electron-app/features/quit-and-install-update.injectable";
|
||||
import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable";
|
||||
import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable";
|
||||
import { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable";
|
||||
|
||||
const installApplicationUpdateTrayItemInjectable = getInjectable({
|
||||
id: "install-update-tray-item",
|
||||
|
||||
instantiate: (di) => {
|
||||
const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable);
|
||||
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
|
||||
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
return {
|
||||
id: "install-update",
|
||||
parentId: null,
|
||||
orderNumber: 50,
|
||||
|
||||
label: computed(() => {
|
||||
const versionToBeInstalled = discoveredVersionState.value.get()?.version;
|
||||
|
||||
return `Install update ${versionToBeInstalled}`;
|
||||
}),
|
||||
|
||||
enabled: computed(() => true),
|
||||
|
||||
visible: computed(
|
||||
() => !!discoveredVersionState.value.get() && !downloadingUpdateState.value.get(),
|
||||
),
|
||||
|
||||
click: pipeline(
|
||||
quitAndInstallUpdate,
|
||||
|
||||
withErrorLoggingFor(() => "[TRAY]: Update installation failed."),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
});
|
||||
|
||||
export default installApplicationUpdateTrayItemInjectable;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
|
||||
import processCheckingForUpdatesInjectable from "../check-for-updates/process-checking-for-updates.injectable";
|
||||
|
||||
const periodicalCheckForUpdatesInjectable = getInjectable({
|
||||
id: "periodical-check-for-updates",
|
||||
|
||||
instantiate: (di) => {
|
||||
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
|
||||
|
||||
return getStartableStoppable("periodical-check-for-updates", () => {
|
||||
const TWO_HOURS = 1000 * 60 * 60 * 2;
|
||||
|
||||
// Note: intentional orphan promise to make checking for updates happen in the background
|
||||
processCheckingForUpdates();
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
// Note: intentional orphan promise to make checking for updates happen in the background
|
||||
processCheckingForUpdates();
|
||||
}, TWO_HOURS);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default periodicalCheckForUpdatesInjectable;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
||||
import { afterRootFrameIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/after-root-frame-is-ready-injection-token";
|
||||
import updatingIsEnabledInjectable from "../updating-is-enabled.injectable";
|
||||
|
||||
const startCheckingForUpdatesInjectable = getInjectable({
|
||||
id: "start-checking-for-updates",
|
||||
|
||||
instantiate: (di) => {
|
||||
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
|
||||
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
if (updatingIsEnabled) {
|
||||
await periodicalCheckForUpdates.start();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: afterRootFrameIsReadyInjectionToken,
|
||||
});
|
||||
|
||||
export default startCheckingForUpdatesInjectable;
|
||||
+27
@@ -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 { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-front-end-injection-token";
|
||||
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
|
||||
|
||||
const stopCheckingForUpdatesInjectable = getInjectable({
|
||||
id: "stop-checking-for-updates",
|
||||
|
||||
instantiate: (di) => {
|
||||
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
if (periodicalCheckForUpdates.started) {
|
||||
await periodicalCheckForUpdates.stop();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfFrontEndInjectionToken,
|
||||
});
|
||||
|
||||
export default stopCheckingForUpdatesInjectable;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 packageJsonInjectable from "../../common/vars/package-json.injectable";
|
||||
import { has } from "lodash/fp";
|
||||
|
||||
// TOOO: Rename to something less technical
|
||||
const publishIsConfiguredInjectable = getInjectable({
|
||||
id: "publish-is-configured",
|
||||
|
||||
instantiate: (di) => {
|
||||
const packageJson = di.inject(packageJsonInjectable);
|
||||
|
||||
return has("build.publish", packageJson);
|
||||
},
|
||||
});
|
||||
|
||||
export default publishIsConfiguredInjectable;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 electronUpdaterIsActiveInjectable from "../electron-app/features/electron-updater-is-active.injectable";
|
||||
import publishIsConfiguredInjectable from "./publish-is-configured.injectable";
|
||||
|
||||
const updatingIsEnabledInjectable = getInjectable({
|
||||
id: "updating-is-enabled",
|
||||
|
||||
instantiate: (di) => {
|
||||
const electronUpdaterIsActive = di.inject(electronUpdaterIsActiveInjectable);
|
||||
const publishIsConfigured = di.inject(publishIsConfiguredInjectable);
|
||||
|
||||
return electronUpdaterIsActive && publishIsConfigured;
|
||||
},
|
||||
});
|
||||
|
||||
export default updatingIsEnabledInjectable;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||
import watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable";
|
||||
|
||||
const startWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
|
||||
id: "start-watching-if-update-should-happen-on-quit",
|
||||
|
||||
instantiate: (di) => {
|
||||
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
||||
|
||||
return {
|
||||
run: () => {
|
||||
watchIfUpdateShouldHappenOnQuit.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
export default startWatchingIfUpdateShouldHappenOnQuitInjectable;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable";
|
||||
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||
|
||||
const stopWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
|
||||
id: "stop-watching-if-update-should-happen-on-quit",
|
||||
|
||||
instantiate: (di) => {
|
||||
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
||||
|
||||
return {
|
||||
run: () => {
|
||||
watchIfUpdateShouldHappenOnQuit.stop();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
});
|
||||
|
||||
export default stopWatchingIfUpdateShouldHappenOnQuitInjectable;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 { autorun } from "mobx";
|
||||
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
|
||||
import setUpdateOnQuitInjectable from "../../electron-app/features/set-update-on-quit.injectable";
|
||||
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
import type { UpdateChannel } from "../../../common/application-update/update-channels";
|
||||
import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable";
|
||||
|
||||
const watchIfUpdateShouldHappenOnQuitInjectable = getInjectable({
|
||||
id: "watch-if-update-should-happen-on-quit",
|
||||
|
||||
instantiate: (di) => {
|
||||
const setUpdateOnQuit = di.inject(setUpdateOnQuitInjectable);
|
||||
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
|
||||
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
|
||||
|
||||
return getStartableStoppable("watch-if-update-should-happen-on-quit", () =>
|
||||
autorun(() => {
|
||||
const sufficientlyStableUpdateChannels =
|
||||
getSufficientlyStableUpdateChannels(selectedUpdateChannel.value.get());
|
||||
|
||||
const discoveredVersion = discoveredVersionState.value.get();
|
||||
|
||||
const updateIsDiscoveredFromChannel = discoveredVersion?.updateChannel;
|
||||
|
||||
const updateOnQuit = updateIsDiscoveredFromChannel
|
||||
? sufficientlyStableUpdateChannels.includes(
|
||||
updateIsDiscoveredFromChannel,
|
||||
)
|
||||
: false;
|
||||
|
||||
setUpdateOnQuit(updateOnQuit);
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const getSufficientlyStableUpdateChannels = (updateChannel: UpdateChannel): UpdateChannel[] => {
|
||||
if (!updateChannel.moreStableUpdateChannel) {
|
||||
return [updateChannel];
|
||||
}
|
||||
|
||||
return [
|
||||
updateChannel,
|
||||
|
||||
...getSufficientlyStableUpdateChannels(updateChannel.moreStableUpdateChannel),
|
||||
];
|
||||
};
|
||||
|
||||
export default watchIfUpdateShouldHappenOnQuitInjectable;
|
||||
Reference in new issue
Block a user