Refactor and transform "Application update", "Preferences" and "Application menu" into Features (#6437)

* Move some code for application update to feature directory

The rest of the code could not be moved yet because of work-in-progress refactorings for OCP compliance.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce helper to get a global override for a function-injectable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract global override-files from bloated getDiForUnitTesting

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Move some more code for application update to feature directory

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce competition for top bar items to achieve OCP-compliance

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for opening context menu using the new competition

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for navigating to home as OCP

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for navigating to back as OCP

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for navigating to forward as OCP

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for application update as OCP

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Format code to make ongoing refactoring a bit easier

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add missing unit tests for top bar extendability using extension API

This makes ongoing refactoring easier.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Replace implementation for old top-bar items for losing competition

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract top menu item for window controls as OCP

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce reusable component for spacing between other components

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce helper component to render list of React elements

Features:
- Placeholder for empty list
- Separators between items
- No boilerplate for "key" prop in React

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix double-clicking and dragging of window from top bar

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix stuff broken in rebase

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshots after rebase

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make fake time have a default value for "now"

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Migrate some application menu items to injectables

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate separators of application menu in single file

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove duplication from separators in application menu

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract some operation system actions from application menu as injectables

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract menu item for quitting application as injectable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce way to type narrow a string property

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make the di's for unit testing able to auto-register also named exports

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make global override less strict to simplify setup of many unit tests

There's a better solution for this in the horizon, as this overridden thing is better faked than stubbed.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify a test

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce way to create hierarchical composites from a flat array

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Implement hierarchy of application menu items using "many-root" composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Migrate more application menu items to injectables

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify hierarchy of application menu items using "single-root" composite

Also solve composed typing of application menu by using Discriminated Unions of TypeScript, see: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshot

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify creation of composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make composite able to have custom handler for missing parent ids

This will be useful next for application menu items, where a missing parent id cannot be fatal.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Defend against self-referencing composites

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Reintroduce non-fatal handling of orphan application menu items

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make faked, yet weak, typing a bit stronger

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify getting of composite paths

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make registrator for application menu items support all known scenarios

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add logging for unrecognizable application menu item types

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename id of menu item to keep it discoverable by existing extensions

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate code to check for updates closer to feature

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce reusable horizontal line

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for preferences as a Feature

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for terminal preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for editor preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for proxy preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for telemetry preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for application preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move code related to helm chart preferences under related Feature in preparation for competition

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for kubernetes preference tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for application preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for editor preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for kubernetes preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for proxy preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing observer to make sure component updates

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for telemetry preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for terminal preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak UI for preferences

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>

* Tweak more UI of preferences

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert "Introduce reusable horizontal line"

This reverts commit 4d8c147fe0f1a14bd884f73cf345e7d3a28b954a.

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>

* Introduce utility to find exactly one item from array

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for preferences navigation

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move code under a Feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove usage of old code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove dead code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add extensions tab group to preferences

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce way to find out if composite has a descendant

This will serve eg. hiding of empty preference tab groups.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make tab groups and tabs in preferences not render when there is no content

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove code made redundant with hiding of preference tabs without content

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Tweak UI for preference navigation tab groups

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce test helper to abstract discovery of HTML elements

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>

* Consolidate discovery of HTML elements in some tests

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt application builder to changes in preference navigation

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt test setup to changes in preference navigation

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove uninteresting technical tests that are covered by behavioural ones

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate discovery of HTML elements in a test

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove test ID made redundant by consolidating discovery of HTML elements

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplication from preference pages

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix import

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make queries in element discovery return matching attribute values for easier testing

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make element discovery able to do nested discovery

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make element discovery able to discover without value for attribute

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Implement registrator for preference items

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make styling less brittle by not relying on static HTML-element structures with CSS-rules

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add todo

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove "group" from preference types, as it is exact replica of "item"

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshots

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Replace usages of react-component factory with actual components for simplicity

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Start considering application preferences as default tab

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate naming

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make sense in horizontal lines

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Clean public interface of a normalize composite

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate name of function

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate directory structure of composite

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move utility functions to common

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename a preference item type to better communicate intent in UI

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move shared UI component to more common place

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate sizing of horizontal line to t-shirts

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move application update related preferences under application update feature

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make winner of competition to use original route for preferences

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make HTML element discovery require less parameters

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract "composable-responsibilities" for Discriminable, Labelable, Orderable, and Showable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshot

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove dead code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move code under a feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate navigating to preferences

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move code under a feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Relocate code under a sub-feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add TODO

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove dead code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix merge conflicts

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots after rebase

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix import path after rebase

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplication from exhaustiveness checks for discriminating unions

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add TODO

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update link to a more recent article

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify static Showability of a PreferenceItem

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Move general function to general directory

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract responsibility of "separability"

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make separator in Map-component know left and right item

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove additional separators when separated items are not shown for having no content

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshots

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

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix lint error

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt integration test to recent changes

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt more integration tests to recent changes

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make composite not care about in formatting of ids

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Adapt application builder and tests to array-like paths over string-like paths

Array-like paths do not have weakness for special characters as part of id, such as ".".

Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make composite not care about formatting of ids

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Adapt application builder and tests to array-like paths over string-like paths

Array-like paths do not have weakness for special characters as part of id, such as ".".

Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate output of get-composite-path to match find-composite's input

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make global overrides for functions log args of the call for devability

Also make the thrown error suggest how to fix the problem.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make attempts to log error throw in unit tests

Errors cannot be allowed to happen without a unit test explicitly causing it. Errors cannot be allowed to happen without author of unit test knowing it.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make composite not know about how children are transformed

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove some duplication from tests of composite

Also make the thrown error suggest how to fix the problem.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make composite unit test an unrealistic test scenario about undefined ids

Also make the thrown error suggest how to fix the problem.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Kill dead code

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify unit tests for composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate tests to now point-free composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify "Showable"

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make non-shown application menu items not break composite structure

This was made apparent by adding related unit tests for all known environments.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify usages of Orderable and Showable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract being maybe Showable as explicit composable responsibility

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make more showables maybe showable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate logic for application menu for Windows to be based on composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Start using named export for composite

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename type for accuracy

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove unnecessary type and value

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make composable responsibilities readonly to nudge towards immutability

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove a bit of duplication to create TS-constants

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make a comment and test name make more sense

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify fallthrough in a switch/case

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Replace inline styles with proper CSS

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Switch to correct type to indicate "object which might not contain a property"

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify overriding of platform in a unit test

Also make typing of platforms more strict, and remove some magic strings.
Also add a TODO for further OCP-ification.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate some "maybe-types" and arguments using them

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshots

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Iku-tursoandJanne Savolainen authored and GitHub committed 2022-10-26 11:16:23 -04:00
1 parent 38feab813c
commit 37eb236948
418 files changed
+39647 -27024

No files matched your search

@@ -1,60 +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 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;
@@ -1,128 +0,0 @@
/**
* 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, ReleaseChannel } 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 ReleaseChannel,
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 });
});
});
});
});
@@ -1,81 +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 { 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 () => {
const { updateIsReadyToBeInstalled } = await processCheckingForUpdates("tray");
if (updateIsReadyToBeInstalled) {
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;
@@ -1,51 +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 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;
@@ -1,82 +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 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 downloadUpdateInjectable from "../download-update/download-update.injectable";
import checkForUpdatesStartingFromChannelInjectable from "./check-for-updates-starting-from-channel.injectable";
import emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time";
const processCheckingForUpdatesInjectable = getInjectable({
id: "process-checking-for-updates",
instantiate: (di) => {
const downloadUpdate = di.inject(downloadUpdateInjectable);
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const checkForUpdatesStartingFromChannel = di.inject(checkForUpdatesStartingFromChannelInjectable);
const emitEvent = di.inject(emitEventInjectable);
return async (source: string) => {
emitEvent({
name: "app",
action: "checking-for-updates",
params: { currentDateTime: getCurrentDateTime(), source },
});
runInAction(() => {
checkingForUpdatesState.set(true);
});
const result = await checkForUpdatesStartingFromChannel(selectedUpdateChannel.value.get());
if (!result.updateWasDiscovered) {
runInAction(() => {
discoveredVersionState.set(null);
checkingForUpdatesState.set(false);
});
return { updateIsReadyToBeInstalled: false };
}
const { version, actualUpdateChannel } = result;
const previouslyDiscovered = discoveredVersionState.value.get();
if (version === previouslyDiscovered?.version) {
runInAction(() => {
checkingForUpdatesState.set(false);
});
return { updateIsReadyToBeInstalled: true };
}
emitEvent({
name: "app",
action: "update-was-discovered",
params: { version, currentDateTime: getCurrentDateTime() },
});
runInAction(() => {
discoveredVersionState.set({
version,
updateChannel: actualUpdateChannel,
});
checkingForUpdatesState.set(false);
});
const { downloadWasSuccessful } = await downloadUpdate();
return { updateIsReadyToBeInstalled: downloadWasSuccessful };
};
},
});
export default processCheckingForUpdatesInjectable;
@@ -1,24 +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 { computed } from "mobx";
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
import releaseChannelInjectable from "../../../common/vars/release-channel.injectable";
const updateCanBeDowngradedInjectable = getInjectable({
id: "update-can-be-downgraded",
instantiate: (di) => {
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const releaseChannel = di.inject(releaseChannelInjectable);
return computed(() => (
selectedUpdateChannel.value.get().id === "latest"
&& releaseChannel.get() !== "latest"
));
},
});
export default updateCanBeDowngradedInjectable;
@@ -1,45 +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 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;
@@ -1,155 +0,0 @@
/**
* 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 });
});
});
});
});
});
@@ -1,57 +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 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";
import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time";
import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.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 updateDownloadedDate = di.inject(updateDownloadedDateTimeInjectable);
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) {
progressOfUpdateDownload.set({ percentage: 0, failed: "Download of update failed" });
discoveredVersionState.set(null);
}
const currentDateTime = getCurrentDateTime();
updateDownloadedDate.set(currentDateTime);
downloadingUpdateState.set(false);
});
return { downloadWasSuccessful };
};
},
});
export default downloadUpdateInjectable;
@@ -1,37 +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 { afterApplicationIsLoadedInjectionToken } from "../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
import { getCurrentDateTime } from "../../common/utils/date/get-current-date-time";
import buildVersionInjectable from "../vars/build-version/build-version.injectable";
const emitCurrentVersionToAnalyticsInjectable = getInjectable({
id: "emit-current-version-to-analytics",
instantiate: (di) => {
const emitEvent = di.inject(emitEventInjectable);
const buildVersion = di.inject(buildVersionInjectable);
return {
id: "emit-current-version-to-analytics",
run: () => {
emitEvent({
name: "app",
action: "current-version",
params: {
version: buildVersion.get(),
currentDateTime: getCurrentDateTime(),
},
});
},
};
},
injectionToken: afterApplicationIsLoadedInjectionToken,
});
export default emitCurrentVersionToAnalyticsInjectable;
@@ -1,54 +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 { computed } from "mobx";
import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token";
import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.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";
import quitAndInstallUpdateInjectable from "./quit-and-install-update.injectable";
import updateIsReadyToBeInstalledInjectable from "./update-is-ready-to-be-installed.injectable";
const installApplicationUpdateTrayItemInjectable = getInjectable({
id: "install-update-tray-item",
instantiate: (di) => {
const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
const updateIsReadyToBeInstalled = di.inject(updateIsReadyToBeInstalledInjectable);
return {
id: "install-update",
parentId: null,
orderNumber: 50,
label: computed(() => {
const versionToBeInstalled = discoveredVersionState.value.get()?.version;
return `Install update ${versionToBeInstalled}`;
}),
enabled: computed(() => true),
visible: updateIsReadyToBeInstalled,
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;
@@ -1,36 +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 { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
import processCheckingForUpdatesInjectable from "../check-for-updates/process-checking-for-updates.injectable";
import withOrphanPromiseInjectable from "../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
const periodicalCheckForUpdatesInjectable = getInjectable({
id: "periodical-check-for-updates",
instantiate: (di) => {
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
const processCheckingForUpdates = withOrphanPromise(di.inject(processCheckingForUpdatesInjectable));
return getStartableStoppable("periodical-check-for-updates", () => {
const TWO_HOURS = 1000 * 60 * 60 * 2;
processCheckingForUpdates("periodic");
const intervalId = setInterval(() => {
processCheckingForUpdates("periodic");
}, TWO_HOURS);
return () => {
clearInterval(intervalId);
};
});
},
causesSideEffects: true,
});
export default periodicalCheckForUpdatesInjectable;
@@ -1,30 +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 periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
import updatingIsEnabledInjectable from "../updating-is-enabled.injectable";
import { afterApplicationIsLoadedInjectionToken } from "../../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
const startCheckingForUpdatesInjectable = getInjectable({
id: "start-checking-for-updates",
instantiate: (di) => {
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
return {
id: "start-checking-for-updates",
run: async () => {
if (updatingIsEnabled && !periodicalCheckForUpdates.started) {
await periodicalCheckForUpdates.start();
}
},
};
},
injectionToken: afterApplicationIsLoadedInjectionToken,
});
export default startCheckingForUpdatesInjectable;
@@ -1,28 +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 periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
const stopCheckingForUpdatesInjectable = getInjectable({
id: "stop-checking-for-updates",
instantiate: (di) => {
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
return {
id: "stop-checking-for-updates",
run: async () => {
if (periodicalCheckForUpdates.started) {
await periodicalCheckForUpdates.stop();
}
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopCheckingForUpdatesInjectable;
@@ -1,13 +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 applicationInformationInjectable from "../../common/vars/application-information.injectable";
const publishIsConfiguredInjectable = getInjectable({
id: "publish-is-configured",
instantiate: (di) => Boolean(di.inject(applicationInformationInjectable).build.publish?.length),
});
export default publishIsConfiguredInjectable;
@@ -1,45 +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 electronQuitAndInstallUpdateInjectable from "../electron-app/features/electron-quit-and-install-update.injectable";
import { getCurrentDateTime } from "../../common/utils/date/get-current-date-time";
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable";
const quitAndInstallUpdateInjectable = getInjectable({
id: "quit-and-install-update",
instantiate: (di) => {
const electronQuitAndInstallUpdate = di.inject(
electronQuitAndInstallUpdateInjectable,
);
const emitEvent = di.inject(emitEventInjectable);
const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable);
return () => {
const discoveredVersion = discoveredUpdateVersion.value.get();
if (!discoveredVersion) {
throw new Error("Tried to install update but no update was discovered.");
}
emitEvent({
name: "app",
action: "start-installing-update",
params: {
version: discoveredVersion.version,
updateChannel: discoveredVersion.updateChannel.id,
currentDateTime: getCurrentDateTime(),
},
});
electronQuitAndInstallUpdate();
};
},
});
export default quitAndInstallUpdateInjectable;
@@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { restartAndInstallUpdateChannel } from "../../../common/application-update/restart-and-install-update-channel";
import { getMessageChannelListenerInjectable } from "../../../common/utils/channel/message-channel-listener-injection-token";
import quitAndInstallUpdateInjectable from "../quit-and-install-update.injectable";
const restartAndInstallUpdateListenerInjectable = getMessageChannelListenerInjectable({
id: "restart",
channel: restartAndInstallUpdateChannel,
handler: (di) => di.inject(quitAndInstallUpdateInjectable),
});
export default restartAndInstallUpdateListenerInjectable;
@@ -1,34 +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 { computed } from "mobx";
import getTrayIconPathInjectable from "../../tray/menu-icon/get-tray-icon-path.injectable";
import { trayIconInjectionToken } from "../../tray/menu-icon/tray-icon-injection-token";
import updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable";
import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable";
const checkingForUpdatesTrayIconInjectable = getInjectable({
id: "checking-for-updates-tray-icon",
instantiate: (di) => {
const getTrayIconPath = di.inject(getTrayIconPathInjectable);
const updatesAreBeingDiscovered = di.inject(updatesAreBeingDiscoveredInjectable);
const updateIsBeingDownloaded = di.inject(updateIsBeingDownloadedInjectable);
return {
iconPath: getTrayIconPath("checking-for-updates"),
priority: 1,
shouldBeShown: computed(
() =>
updatesAreBeingDiscovered.value.get() ||
updateIsBeingDownloaded.value.get(),
),
};
},
injectionToken: trayIconInjectionToken,
});
export default checkingForUpdatesTrayIconInjectable;
@@ -1,27 +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 getTrayIconPathInjectable from "../../tray/menu-icon/get-tray-icon-path.injectable";
import { trayIconInjectionToken } from "../../tray/menu-icon/tray-icon-injection-token";
import updateIsReadyToBeInstalledInjectable from "../update-is-ready-to-be-installed.injectable";
const updateIsReadyToBeInstalledTrayIconInjectable = getInjectable({
id: "update-is-ready-to-be-installed-tray-icon",
instantiate: (di) => {
const getTrayIconPath = di.inject(getTrayIconPathInjectable);
const updateIsReadyToBeInstalled = di.inject(updateIsReadyToBeInstalledInjectable);
return {
iconPath: getTrayIconPath("update-available"),
priority: 1,
shouldBeShown: updateIsReadyToBeInstalled,
};
},
injectionToken: trayIconInjectionToken,
});
export default updateIsReadyToBeInstalledTrayIconInjectable;
@@ -1,25 +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 { computed } from "mobx";
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";
const updateIsReadyToBeInstalledInjectable = getInjectable({
id: "update-is-ready-to-be-installed",
instantiate: (di) => {
const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable);
const updateIsBeingDownloaded = di.inject(updateIsBeingDownloadedInjectable);
return computed(
() =>
!!discoveredUpdateVersion.value.get() &&
!updateIsBeingDownloaded.value.get(),
);
},
});
export default updateIsReadyToBeInstalledInjectable;
@@ -1,20 +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 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;
@@ -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 { 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 {
id: "start-watching-if-update-should-happen-on-quit",
run: () => {
watchIfUpdateShouldHappenOnQuit.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startWatchingIfUpdateShouldHappenOnQuitInjectable;
@@ -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 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 {
id: "stop-watching-if-update-should-happen-on-quit",
run: () => {
watchIfUpdateShouldHappenOnQuit.stop();
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopWatchingIfUpdateShouldHappenOnQuitInjectable;
@@ -1,47 +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 { 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 { ReleaseChannel, 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 updateIsDiscoveredFromChannel = discoveredVersionState.value.get()?.updateChannel;
setUpdateOnQuit((
updateIsDiscoveredFromChannel
? sufficientlyStableUpdateChannels.includes(updateIsDiscoveredFromChannel.id)
: false
));
}),
);
},
});
const getSufficientlyStableUpdateChannels = (updateChannel: UpdateChannel): ReleaseChannel[] => {
if (!updateChannel.moreStableUpdateChannel) {
return [updateChannel.id];
}
return [
updateChannel.id,
...getSufficientlyStableUpdateChannels(updateChannel.moreStableUpdateChannel),
];
};
export default watchIfUpdateShouldHappenOnQuitInjectable;
@@ -6,12 +6,12 @@ import { getInjectable } from "@ogre-tools/injectable";
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
import loggerInjectable from "../../../../common/logger.injectable";
import clusterManagerInjectable from "../../../cluster/manager.injectable";
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
const setupIpcMainHandlersInjectable = getInjectable({
id: "setup-ipc-main-handlers",
@@ -19,7 +19,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const clusterManager = di.inject(clusterManagerInjectable);
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
const applicationMenuItemComposite = di.inject(applicationMenuItemCompositeInjectable);
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
@@ -31,7 +31,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
logger.debug("[APP-MAIN] initializing ipc main handlers");
setupIpcMainHandlers({
applicationMenuItems,
applicationMenuItemComposite,
clusterManager,
catalogEntityRegistry,
clusterStore,
@@ -15,16 +15,19 @@ import { pushCatalogToRenderer } from "../../../catalog-pusher";
import type { ClusterManager } from "../../../cluster/manager";
import { ResourceApplier } from "../../../resource-applier";
import type { IComputedValue } from "mobx";
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
import { openFilePickingDialogChannel } from "../../../../common/ipc/dialog";
import { getNativeThemeChannel } from "../../../../common/ipc/native-theme";
import type { Theme } from "../../../theme/operating-system-theme-state.injectable";
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
import type { ApplicationMenuItemTypes } from "../../../../features/application-menu/main/menu-items/application-menu-item-injection-token";
import type { Composite } from "../../../../common/utils/composite/get-composite/get-composite";
import { getApplicationMenuTemplate } from "../../../../features/application-menu/main/populate-application-menu.injectable";
import type { MenuItemRoot } from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
interface Dependencies {
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
applicationMenuItemComposite: IComputedValue<Composite<ApplicationMenuItemTypes | MenuItemRoot>>;
clusterManager: ClusterManager;
catalogEntityRegistry: CatalogEntityRegistry;
clusterStore: ClusterStore;
@@ -33,7 +36,7 @@ interface Dependencies {
}
export const setupIpcMainHandlers = ({
applicationMenuItems,
applicationMenuItemComposite,
clusterManager,
catalogEntityRegistry,
clusterStore,
@@ -124,9 +127,8 @@ export const setupIpcMainHandlers = ({
ipcMainHandle(broadcastMainChannel, (event, channel, ...args) => broadcastMessage(channel, ...args));
ipcMainOn(windowOpenAppMenuAsContextMenuChannel, async (event) => {
const appMenu = applicationMenuItems.get();
const menu = Menu.buildFromTemplate(appMenu);
const electronTemplate = getApplicationMenuTemplate(applicationMenuItemComposite.get());
const menu = Menu.buildFromTemplate(electronTemplate);
menu.popup({
...BrowserWindow.fromWebContents(event.sender),
+7 -18
View File
@@ -5,7 +5,7 @@
import { kebabCase, noop, chunk } from "lodash/fp";
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
import { createContainer } from "@ogre-tools/injectable";
import { createContainer, isInjectable } from "@ogre-tools/injectable";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
@@ -31,7 +31,6 @@ import setupLensProxyInjectable from "./start-main-application/runnables/setup-l
import setupShellInjectable from "./start-main-application/runnables/setup-shell.injectable";
import setupSyncingOfWeblinksInjectable from "./start-main-application/runnables/setup-syncing-of-weblinks.injectable";
import stopServicesAndExitAppInjectable from "./stop-services-and-exit-app.injectable";
import applicationMenuInjectable from "./menu/application-menu.injectable";
import isDevelopmentInjectable from "../common/vars/is-development.injectable";
import setupSystemCaInjectable from "./start-main-application/runnables/setup-system-ca.injectable";
import setupDeepLinkingInjectable from "./electron-app/runnables/setup-deep-linking.injectable";
@@ -59,15 +58,11 @@ import syncThemeFromOperatingSystemInjectable from "./electron-app/features/sync
import platformInjectable from "../common/vars/platform.injectable";
import electronQuitAndInstallUpdateInjectable from "./electron-app/features/electron-quit-and-install-update.injectable";
import electronUpdaterIsActiveInjectable from "./electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "./application-update/publish-is-configured.injectable";
import checkForPlatformUpdatesInjectable from "./application-update/check-for-platform-updates/check-for-platform-updates.injectable";
import baseBundledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable";
import setUpdateOnQuitInjectable from "./electron-app/features/set-update-on-quit.injectable";
import downloadPlatformUpdateInjectable from "./application-update/download-platform-update/download-platform-update.injectable";
import startCatalogSyncInjectable from "./catalog-sync-to-renderer/start-catalog-sync.injectable";
import startKubeConfigSyncInjectable from "./start-main-application/runnables/kube-config-sync/start-kube-config-sync.injectable";
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
import periodicalCheckForUpdatesInjectable from "./application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
import execFileInjectable from "../common/fs/exec-file.injectable";
import normalizedPlatformArchitectureInjectable from "../common/vars/normalized-platform-architecture.injectable";
import getHelmChartVersionsInjectable from "./helm/helm-service/get-helm-chart-versions.injectable";
@@ -100,8 +95,12 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
di.preventSideEffects();
const injectables: Injectable<any, any, any>[] = (global as any).mainInjectablePaths.map(
(filePath: string) => require(filePath).default,
const injectables: Injectable<any, any, any>[] = (
global as any
).mainInjectablePaths.flatMap((filePath: string) =>
Object.values(require(filePath)).filter(
(maybeInjectable: any) => isInjectable(maybeInjectable),
),
);
runInAction(() => {
@@ -152,10 +151,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
di.override(stopServicesAndExitAppInjectable, () => () => {});
di.override(lensResourcesDirInjectable, () => "/irrelevant");
di.override(applicationMenuInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(periodicalCheckForUpdatesInjectable, () => ({ start: () => {}, stop: () => {}, started: false }));
overrideFunctionalInjectables(di, [
getHelmChartVersionsInjectable,
getHelmChartValuesInjectable,
@@ -252,20 +247,14 @@ const overrideElectronFeatures = (di: DiContainer) => {
di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(electronQuitAndInstallUpdateInjectable, () => () => {});
di.override(setUpdateOnQuitInjectable, () => () => {});
di.override(downloadPlatformUpdateInjectable, () => async () => ({ downloadWasSuccessful: true }));
di.override(focusApplicationInjectable, () => () => {});
di.override(checkForPlatformUpdatesInjectable, () => () => {
throw new Error("Tried to check for platform updates without explicit override.");
});
di.override(
getElectronAppPathInjectable,
() => (name: string) => `/some-electron-app-path-for-${kebabCase(name)}`,
);
di.override(setElectronAppPathInjectable, () => () => {});
di.override(publishIsConfiguredInjectable, () => false);
di.override(electronUpdaterIsActiveInjectable, () => false);
};
@@ -1,333 +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 { docsUrl, supportUrl } from "../../common/vars";
import { broadcastMessage } from "../../common/ipc";
import type { MenuItemConstructorOptions } from "electron";
import { webContents } from "electron";
import loggerInjectable from "../../common/logger.injectable";
import electronMenuItemsInjectable from "./electron-menu-items.injectable";
import updatingIsEnabledInjectable from "../application-update/updating-is-enabled.injectable";
import navigateToPreferencesInjectable from "../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
import navigateToExtensionsInjectable from "../../common/front-end-routing/routes/extensions/navigate-to-extensions.injectable";
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToWelcomeInjectable from "../../common/front-end-routing/routes/welcome/navigate-to-welcome.injectable";
import navigateToAddClusterInjectable from "../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable";
import isMacInjectable from "../../common/vars/is-mac.injectable";
import { computed } from "mobx";
import showAboutInjectable from "./show-about.injectable";
import reloadCurrentApplicationWindowInjectable from "../start-main-application/lens-window/reload-current-application-window.injectable";
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
import processCheckingForUpdatesInjectable from "../application-update/check-for-updates/process-checking-for-updates.injectable";
import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable";
import appNameInjectable from "../../common/vars/app-name.injectable";
import productNameInjectable from "../../common/vars/product-name.injectable";
function ignoreIf(check: boolean, menuItems: MenuItemOpts[]) {
return check ? [] : menuItems;
}
export interface MenuItemOpts extends MenuItemConstructorOptions {
submenu?: MenuItemConstructorOptions[];
}
const applicationMenuItemsInjectable = getInjectable({
id: "application-menu-items",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const appName = di.inject(appNameInjectable);
const productName = di.inject(productNameInjectable);
const isMac = di.inject(isMacInjectable);
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
const electronMenuItems = di.inject(electronMenuItemsInjectable);
const showAbout = di.inject(showAboutInjectable);
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
const reloadApplicationWindow = di.inject(reloadCurrentApplicationWindowInjectable);
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
const navigateToExtensions = di.inject(navigateToExtensionsInjectable);
const navigateToCatalog = di.inject(navigateToCatalogInjectable);
const navigateToWelcome = di.inject(navigateToWelcomeInjectable);
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
logger.info(`[MENU]: autoUpdateEnabled=${updatingIsEnabled}`);
return computed((): MenuItemOpts[] => {
const macAppMenu: MenuItemOpts = {
label: appName,
id: "root",
submenu: [
{
label: `About ${productName}`,
id: "about",
click() {
showAbout();
},
},
...ignoreIf(!updatingIsEnabled, [
{
id: "check-for-updates",
label: "Check for updates",
click() {
processCheckingForUpdates("application-menu").then(() => showApplicationWindow());
},
},
]),
{ type: "separator" },
{
label: "Preferences",
accelerator: "CmdOrCtrl+,",
id: "preferences",
click() {
navigateToPreferences();
},
},
{
label: "Extensions",
accelerator: "CmdOrCtrl+Shift+E",
id: "extensions",
click() {
navigateToExtensions();
},
},
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{
label: "Quit",
accelerator: "Cmd+Q",
id: "quit",
click() {
stopServicesAndExitApp();
},
},
],
};
const fileMenu: MenuItemOpts = {
label: "File",
id: "file",
submenu: [
{
label: "Add Cluster",
accelerator: "CmdOrCtrl+Shift+A",
id: "add-cluster",
click() {
navigateToAddCluster();
},
},
...ignoreIf(isMac, [
{ type: "separator" },
{
label: "Preferences",
id: "preferences",
accelerator: "Ctrl+,",
click() {
navigateToPreferences();
},
},
{
label: "Extensions",
accelerator: "Ctrl+Shift+E",
click() {
navigateToExtensions();
},
},
]),
{ type: "separator" },
...ignoreIf(!isMac, [
{
role: "close",
label: "Close Window",
accelerator: "Shift+Cmd+W",
},
]),
...ignoreIf(isMac, [
{
label: "Exit",
accelerator: "Alt+F4",
id: "quit",
click() {
stopServicesAndExitApp();
},
},
]),
],
};
const editMenu: MenuItemOpts = {
label: "Edit",
id: "edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: "delete" },
{ type: "separator" },
{ role: "selectAll" },
],
};
const viewMenu: MenuItemOpts = {
label: "View",
id: "view",
submenu: [
{
label: "Catalog",
accelerator: "Shift+CmdOrCtrl+C",
id: "catalog",
click() {
navigateToCatalog();
},
},
{
label: "Command Palette...",
accelerator: "Shift+CmdOrCtrl+P",
id: "command-palette",
click(_m, _b, event) {
/**
* Don't broadcast unless it was triggered by menu iteration so that
* there aren't double events in renderer
*
* NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554
*/
if (!event?.triggeredByAccelerator) {
broadcastMessage("command-palette:open");
}
},
},
{ type: "separator" },
{
label: "Back",
accelerator: "CmdOrCtrl+[",
id: "go-back",
click() {
webContents
.getAllWebContents()
.filter((wc) => wc.getType() === "window")
.forEach((wc) => wc.goBack());
},
},
{
label: "Forward",
accelerator: "CmdOrCtrl+]",
id: "go-forward",
click() {
webContents
.getAllWebContents()
.filter((wc) => wc.getType() === "window")
.forEach((wc) => wc.goForward());
},
},
{
label: "Reload",
accelerator: "CmdOrCtrl+R",
id: "reload",
click() {
reloadApplicationWindow();
},
},
{ role: "toggleDevTools" },
{ type: "separator" },
{ role: "resetZoom" },
{ role: "zoomIn" },
{ role: "zoomOut" },
{ type: "separator" },
{ role: "togglefullscreen" },
],
};
const helpMenu: MenuItemOpts = {
role: "help",
id: "help",
submenu: [
{
label: "Welcome",
id: "welcome",
click() {
navigateToWelcome();
},
},
{
label: "Documentation",
id: "documentation",
click: async () => {
openLinkInBrowser(docsUrl).catch((error) => {
logger.error("[MENU]: failed to open browser", { error });
});
},
},
{
label: "Support",
id: "support",
click: async () => {
openLinkInBrowser(supportUrl).catch((error) => {
logger.error("[MENU]: failed to open browser", { error });
});
},
},
...ignoreIf(isMac, [
{
label: `About ${productName}`,
id: "about",
click() {
showAbout();
},
},
...ignoreIf(!updatingIsEnabled, [
{
label: "Check for updates",
click() {
processCheckingForUpdates("periodic").then(() =>
showApplicationWindow(),
);
},
},
]),
]),
],
};
// Prepare menu items order
const appMenu = new Map([
["mac", macAppMenu],
["file", fileMenu],
["edit", editMenu],
["view", viewMenu],
["help", helpMenu],
]);
// Modify menu from extensions-api
for (const menuItem of electronMenuItems.get()) {
const parentMenu = appMenu.get(menuItem.parentId);
if (!parentMenu) {
logger.error(
`[MENU]: cannot register menu item for parentId=${menuItem.parentId}, parent item doesn't exist`,
{ menuItem },
);
continue;
}
(parentMenu.submenu ??= []).push(menuItem);
}
if (!isMac) {
appMenu.delete("mac");
}
return [...appMenu.values()];
});
},
});
export default applicationMenuItemsInjectable;
@@ -1,25 +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 { autorun } from "mobx";
import { buildMenu } from "./menu";
import applicationMenuItemsInjectable from "./application-menu-items.injectable";
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
const applicationMenuInjectable = getInjectable({
id: "application-menu",
instantiate: (di) => {
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
return getStartableStoppable("build-of-application-menu", () =>
autorun(() => buildMenu(applicationMenuItems.get()), {
delay: 100,
}),
);
},
});
export default applicationMenuInjectable;
@@ -1,20 +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 { computed } from "mobx";
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
const electronMenuItemsInjectable = getInjectable({
id: "electron-menu-items",
instantiate: (di) => {
const extensions = di.inject(mainExtensionsInjectable);
return computed(() =>
extensions.get().flatMap((extension) => extension.appMenus));
},
});
export default electronMenuItemsInjectable;
-116
View File
@@ -1,116 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { LensMainExtension } from "../../extensions/lens-main-extension";
import electronMenuItemsInjectable from "./electron-menu-items.injectable";
import type { IComputedValue } from "mobx";
import { computed, ObservableMap, runInAction } from "mobx";
import type { MenuRegistration } from "./menu-registration";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
describe("electron-menu-items", () => {
let di: DiContainer;
let electronMenuItems: IComputedValue<MenuRegistration[]>;
let extensionsStub: ObservableMap<string, LensMainExtension>;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
extensionsStub = new ObservableMap();
di.override(
mainExtensionsInjectable,
() => computed(() => [...extensionsStub.values()]),
);
electronMenuItems = di.inject(electronMenuItemsInjectable);
});
it("does not have any items yet", () => {
expect(electronMenuItems.get()).toHaveLength(0);
});
describe("when extension is enabled", () => {
beforeEach(() => {
const someExtension = new SomeTestExtension({
id: "some-extension-id",
appMenus: [{ parentId: "some-parent-id-from-first-extension" }],
});
runInAction(() => {
extensionsStub.set("some-extension-id", someExtension);
});
});
it("has menu items", () => {
expect(electronMenuItems.get()).toEqual([
{
parentId: "some-parent-id-from-first-extension",
},
]);
});
it("when disabling extension, does not have menu items", () => {
extensionsStub.delete("some-extension-id");
expect(electronMenuItems.get()).toHaveLength(0);
});
describe("when other extension is enabled", () => {
beforeEach(() => {
const someOtherExtension = new SomeTestExtension({
id: "some-extension-id",
appMenus: [{ parentId: "some-parent-id-from-second-extension" }],
});
extensionsStub.set("some-other-extension-id", someOtherExtension);
});
it("has menu items for both extensions", () => {
expect(electronMenuItems.get()).toEqual([
{
parentId: "some-parent-id-from-first-extension",
},
{
parentId: "some-parent-id-from-second-extension",
},
]);
});
it("when extension is disabled, still returns menu items for extensions that are enabled", () => {
runInAction(() => {
extensionsStub.delete("some-other-extension-id");
});
expect(electronMenuItems.get()).toEqual([
{
parentId: "some-parent-id-from-first-extension",
},
]);
});
});
});
});
class SomeTestExtension extends LensMainExtension {
constructor({ id, appMenus }: {
id: string;
appMenus: MenuRegistration[];
}) {
super({
id,
absolutePath: "irrelevant",
isBundled: false,
isCompatible: false,
isEnabled: false,
manifest: { name: id, version: "some-version", engines: { lens: "^5.5.0" }},
manifestPath: "irrelevant",
});
this.appMenus = appMenus;
}
}
-9
View File
@@ -1,9 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MenuItemConstructorOptions } from "electron";
export interface MenuRegistration extends MenuItemConstructorOptions {
parentId: string;
}
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Menu } from "electron";
import type { MenuItemOpts } from "./application-menu-items.injectable";
export type MenuTopId = "mac" | "file" | "edit" | "view" | "help";
export function buildMenu(applicationMenuItems: MenuItemOpts[]) {
Menu.setApplicationMenu(
Menu.buildFromTemplate(applicationMenuItems),
);
}
-45
View File
@@ -1,45 +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 showMessagePopupInjectable from "../electron-app/features/show-message-popup.injectable";
import isWindowsInjectable from "../../common/vars/is-windows.injectable";
import appNameInjectable from "../../common/vars/app-name.injectable";
import productNameInjectable from "../../common/vars/product-name.injectable";
import buildVersionInjectable from "../vars/build-version/build-version.injectable";
import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable";
import applicationCopyrightInjectable from "../../common/vars/application-copyright.injectable";
const showAboutInjectable = getInjectable({
id: "show-about",
instantiate: (di) => {
const buildVersion = di.inject(buildVersionInjectable);
const extensionApiVersion = di.inject(extensionApiVersionInjectable);
const showMessagePopup = di.inject(showMessagePopupInjectable);
const isWindows = di.inject(isWindowsInjectable);
const appName = di.inject(appNameInjectable);
const productName = di.inject(productNameInjectable);
const applicationCopyright = di.inject(applicationCopyrightInjectable);
return () => {
const appInfo = [
`${appName}: ${buildVersion.get()}`,
`Extension API: ${extensionApiVersion}`,
`Electron: ${process.versions.electron}`,
`Chrome: ${process.versions.chrome}`,
`Node: ${process.versions.node}`,
applicationCopyright,
];
showMessagePopup(
`${isWindows ? " ".repeat(2) : ""}${appName}`,
productName,
appInfo.join("\r\n"),
);
};
},
});
export default showAboutInjectable;
@@ -1,28 +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 applicationMenuInjectable from "./application-menu.injectable";
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
const startApplicationMenuInjectable = getInjectable({
id: "start-application-menu",
instantiate: (di) => {
const applicationMenu = di.inject(
applicationMenuInjectable,
);
return {
id: "start-application-menu",
run: async () => {
await applicationMenu.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startApplicationMenuInjectable;
@@ -1,28 +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 applicationMenuInjectable from "./application-menu.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
const stopApplicationMenuInjectable = getInjectable({
id: "stop-application-menu",
instantiate: (di) => {
const applicationMenu = di.inject(
applicationMenuInjectable,
);
return {
id: "stop-application-menu",
run: async () => {
await applicationMenu.stop();
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopApplicationMenuInjectable;
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import showApplicationWindowInjectable from "../../../start-main-application/lens-window/show-application-window.injectable";
import showAboutInjectable from "../../../menu/show-about.injectable";
import showAboutInjectable from "../../../../features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable";
import { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
import { computed } from "mobx";
import withErrorLoggingInjectable from "../../../../common/utils/with-error-logging/with-error-logging.injectable";
@@ -1,43 +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 { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token";
import navigateToPreferencesInjectable from "../../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
import { computed } from "mobx";
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 openPreferencesTrayItemInjectable = getInjectable({
id: "open-preferences-tray-item",
instantiate: (di) => {
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
return {
id: "open-preferences",
parentId: null,
label: computed(() => "Preferences"),
orderNumber: 20,
enabled: computed(() => true),
visible: computed(() => true),
click: pipeline(
navigateToPreferences,
withErrorLoggingFor(() => "[TRAY]: Opening of preferences 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 openPreferencesTrayItemInjectable;
@@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import defaultUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/default-update-channel.injectable";
import { beforeApplicationIsLoadingInjectionToken } from "../../start-main-application/runnable-tokens/before-application-is-loading-injection-token";
import initReleaseChannelInjectable from "../release-channel/init.injectable";
import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
const initDefaultUpdateChannelInjectable = getInjectable({
id: "init-default-update-channel",