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

Make starting of application modular and unit testable (#5324)

* Introduce injection token as competition for injectable setup to have better control of timing of different setups

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

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

* Switch to using competition to setup app paths

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

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

* Switch to using competition for setupping IPC channel listeners

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

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

* Stop running setups in unit tests without need

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

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

* Switch to running injection token based setups over legacy DI setups in unit tests

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

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

* Consolidate naming for running setups

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

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

* Switch to running injection token based setups over legacy DI setups in application roots

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

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

* Adapt to typing changes in injectable

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

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

* Update injectable

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

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

* Introduce concept of runnable as a way to delegate runs to Open Closed Principle compliant runnables

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

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

* Deprecate vars in favor of injectables

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

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

* Consolidate loading of extensions to injectable instead of setup-code

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

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

* Flag injectable causing side effects

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

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

* Adapt injectable to auto register using a plugin instead of internal feature that no longer exists

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

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

* Simplify late registrations

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

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

* Introduce tokens for runnables of specific application events for Open Closed Principle

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

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

* Consolidate setup events to more granular timeslots

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

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

* Reimplement setup for catalog syncing using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for application menu using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for electron application name using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for immer using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for MobX strictness using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for application proxy using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for system certifications using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for system shutdown using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for main window visibility using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for application quit using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for after application is ready using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for application tray using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for deep linking using runnables instead of non-OCP in index.ts

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

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

* Reimplement setup for root frame using runnables instead of non-OCP in index.ts

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

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

* Remove multiple usages of shared global state

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

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

* Remove recently reimplemented stuff from index.ts

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

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

* Extract implementation for intent over technical phenomena (here electron events)

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

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

* Consolidate naming of event timing window

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

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

* Move directories for timing windows among peers

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

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

* Consolidate event naming

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

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

* Introduce function to remove duplication from things that are startable and stoppable

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

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

* Consolidate implementation of something startable and stoppable

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

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

* Consolidate naming

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

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

* Consolidate more startables and stoppables

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

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

* Introduce abstractions for electron application events

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

* Introduce more abstractions for electron

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

* Abstract even more Electron specifics to make them overridable in unit tests

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

* Override dependency for causing side effects

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

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

* Make running of many delegatees able to be hierarchical

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

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

* Bump async-fn

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

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

* Make startable-stoppable also restartable

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

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

* Extract "isIntegrationTesting" as dependency for having a side-effect

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

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

* Make temporal dependency apparent

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

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

* Consolidate all setupping related to single feature to same runnable

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

* Abstract command line arguments to make them overridable in tests

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

* Deprecate some globals

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

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

* Introduce injectable for __static directory to eliminate side effect on import

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

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

* Extract responsibilities from electron

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

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

* Extract async initialization from sync-injectable

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

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

* Accumulate more global overrides

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

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

* Make lifecycle of injectable store work as expected

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

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

* Extract responsibilities to delegatees

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

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

* Rename delegate for accuracy

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

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

* Rename another delegate for accuracy

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

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

* Rename yet another delegate for accuracy

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

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

* Add new global overrides

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

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

* Consolidate variable naming

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

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

* Rename injectable for accuracy

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

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

* Make difference between soft and hard quit of application apparent

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

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

* Make quit triggered by auto update manifest as hard quit instead of just soft quit

Soft quit is the stand-by quit where only the renderer quits. Hard quit is the full quit of also main.

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

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

* Introduce abstraction for publishing and subscribing between processes

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

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

* Revert "Introduce abstraction for publishing and subscribing between processes"

This reverts commit 46f2d5a5f28bddcf5ffe124b1c590b19e7b9a15f.

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

* Adapt code and unit tests to recent changes in application setup and event handling

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

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

* Group overrides by category

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

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

* Abstract event of application activation from electron

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

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

* Abstract event of device shutdown from electron

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

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

* Consolidate runnables related to Electron

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

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

* Make startableStoppables have ID for better error logging

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

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

* Make navigating to Helm Charts not blow up

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

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

* Add general override for behavioural tests

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

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

* Update snapshot

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

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

* Adapt vars after merge

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

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

* Fix code style

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

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

* Fix error about multiple states for React Router being present at once

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

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

* Kludge around test setup which is difficult because of circular dependencies

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

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

* Fix setupping of sentry after rebase

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

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

* Ensure that LensProxy is setupped before starting the main window

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

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

* Remove redundant import

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

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

* Consolidate to use injectable instead of var for static file directory

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

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

* Deprecate another var with injectable

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

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

* Tweak timing of runnables

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

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

* Extract tray icon path as injectable

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

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

* Make splash screen not blow up by providing compile-time environment variables

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

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

* Introduce a way to run many synchronous runnables

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

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

* Make runnables for before application is ready synchronous as this is technical limitation of Electron

See: https://github.com/electron/electron/issues/21370

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>

* Fix application quit by calling Electron's event.preventDefault in a very specific way and preventing async

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

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

* Fix missing injectable postfix in file name

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

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

* Tweak timeslot of a runnable

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

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

* Make it possible to not stop something that was never started

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

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

* Make quit of hidden Lens not blow up

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

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

* Extract responsibilities of WindowManager as injectables

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

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

* Consolidate code for application window

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

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

* Simplify directory structures for runnables

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

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

* Flag injectable causing side-effect

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

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

* Extract helpers for testing promises to test-utils

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

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

* Make script for running unit tests support targeting

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

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

* Make startable stoppable support async starting

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

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

* Introduce reactive way to get theme from operating system

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

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

* Update yarn.lock after rebase

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

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

* Fix code style

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

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

* Remove code-style changes that are not welcome

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

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

* Switch to using dependency over using explicit side-effect

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

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

* Simplify naming

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>

* Add empty mocks comply to lint

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

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

* Remove global state

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

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

* Extract responsibility of setting dependencies to own injectable

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

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

* Flag injectable causing side effects

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

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

* Extract responsibility to injectable

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

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

* Tweak typing

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

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

* Tweak naming of runnables

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

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

* Revert code-style changes

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

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

* Tweak types

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

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

* Extract injectable with side-effect for exact overriding

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

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

* Tweak name of timeslot to make it more apparent that new content for application load can be added

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

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

* Tweak name of the splash window for loading to avoid confusion with similarly named phenomenon

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

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

* Tweak comment

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

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

* Rename injectable for brevity

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

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

* Consolidate LensProxy related stuff to directory

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

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

* Rename injectables for accuracy

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

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

* Remove usage of extension for injectable for being YAGNI

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

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

* Prevent restarting a startableStoppable when already being started

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

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

* Rename callback in ApplicationBuilder for accuracy

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

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

* Fix conflicts after rebase

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

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

* Fix merge conflicts

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

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

* Revert switch to react-router-dom by installing history as dependency

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

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

* Make test more strict

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

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

* Make runnable give friendly error about incorrect hierarchy for easier debugging

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

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

* Fix code-style

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

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

* Fix code style

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

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

* Switch to using dependency instead of legacy-di

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

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

* Fix timing of injects

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

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

Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Janne Savolainen 2022-05-18 16:18:02 +03:00 committed by GitHub
parent b57d48e39e
commit 54b92aa9b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
302 changed files with 6388 additions and 2025 deletions

View File

@ -2,5 +2,4 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./metrics-providers";
export * from "./cluster-metadata-detectors";
export default {};

View File

@ -0,0 +1,5 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export default {};

View File

@ -28,7 +28,7 @@
"build:mac": "yarn run compile && electron-builder --mac --dir",
"build:win": "yarn run compile && electron-builder --win --dir",
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
"test:unit": "jest --watch --testPathIgnorePatterns integration",
"test:unit": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
"test:integration": "func() { jest ${1:-xyz} --watch --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"]; }; func",
"dist": "yarn run compile && electron-builder --publish onTag",
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
@ -204,9 +204,10 @@
"@hapi/subtext": "^7.0.3",
"@kubernetes/client-node": "^0.16.3",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "5.2.0",
"@ogre-tools/injectable": "5.2.0",
"@ogre-tools/injectable-react": "5.2.0",
"@ogre-tools/injectable": "7.0.0",
"@ogre-tools/injectable-react": "7.0.0",
"@ogre-tools/fp": "7.0.0",
"@ogre-tools/injectable-extension-for-auto-registration": "7.0.0",
"@sentry/electron": "^3.0.7",
"@sentry/integrations": "^6.19.3",
"@types/circular-dependency-plugin": "5.0.5",
@ -226,6 +227,7 @@
"got": "^11.8.3",
"grapheme-splitter": "^1.0.4",
"handlebars": "^4.7.7",
"history": "^4.10.1",
"http-proxy": "^1.18.1",
"immer": "^9.0.12",
"joi": "^17.6.0",
@ -276,7 +278,7 @@
"ws": "^8.5.0"
},
"devDependencies": {
"@async-fn/jest": "1.5.3",
"@async-fn/jest": "1.6.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",

View File

@ -25,7 +25,7 @@ describe("add-cluster - navigation using application menu", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeSetups(({ mainDi }) => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
});
@ -43,8 +43,8 @@ describe("add-cluster - navigation using application menu", () => {
});
describe("when navigating to add cluster using application menu", () => {
beforeEach(() => {
applicationBuilder.applicationMenu.click("file.add-cluster");
beforeEach(async () => {
await applicationBuilder.applicationMenu.click("file.add-cluster");
});
it("renders", () => {

View File

@ -19,7 +19,7 @@ describe("cluster - order of sidebar items", () => {
beforeEach(() => {
applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame();
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(testSidebarItemsInjectable);
});
});

View File

@ -36,7 +36,8 @@ describe("cluster - sidebar and tab navigation for core", () => {
rendererDi = applicationBuilder.dis.rendererDi;
applicationBuilder.setEnvironmentToClusterFrame();
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(
directoryForLensLocalStorageInjectable,
() => "/some-directory-for-lens-local-storage",
@ -46,7 +47,7 @@ describe("cluster - sidebar and tab navigation for core", () => {
describe("given core registrations", () => {
beforeEach(() => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(testRouteInjectable);
rendererDi.register(testRouteComponentInjectable);
rendererDi.register(testSidebarItemsInjectable);
@ -102,6 +103,7 @@ describe("cluster - sidebar and tab navigation for core", () => {
},
);
});
applicationBuilder.beforeRender(async ({ rendererDi }) => {
const sidebarStorage = rendererDi.inject(sidebarStorageInjectable);

View File

@ -33,7 +33,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
applicationBuilder.setEnvironmentToClusterFrame();
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(
directoryForLensLocalStorageInjectable,
() => "/some-directory-for-lens-local-storage",

View File

@ -25,7 +25,7 @@ describe("cluster - visibility of sidebar items", () => {
applicationBuilder.setEnvironmentToClusterFrame();
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(testRouteInjectable);
rendererDi.register(testRouteComponentInjectable);
rendererDi.register(testSidebarItemsInjectable);

View File

@ -22,7 +22,7 @@ describe("extensions - navigation using application menu", () => {
let focusWindowMock: jest.Mock;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeSetups(({ mainDi, rendererDi }) => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi, rendererDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
rendererDi.override(extensionsStoreInjectable, () => ({}) as unknown as ExtensionsStore);
rendererDi.override(fileSystemProvisionerStoreInjectable, () => ({}) as unknown as FileSystemProvisionerStore);
@ -46,8 +46,8 @@ describe("extensions - navigation using application menu", () => {
});
describe("when navigating to extensions using application menu", () => {
beforeEach(() => {
applicationBuilder.applicationMenu.click("root.extensions");
beforeEach(async () => {
await applicationBuilder.applicationMenu.click("root.extensions");
});
it("focuses the window", () => {

View File

@ -0,0 +1,458 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`helm-charts - navigation to Helm charts when navigating to Helm charts renders 1`] = `
<div>
<div
class="flex flex-col"
data-testid="cluster-sidebar"
>
<div
class="SidebarCluster"
>
<div
class="Avatar rounded loadingAvatar"
style="width: 40px; height: 40px;"
>
??
</div>
<div
class="loadingClusterName"
/>
</div>
<div
class="sidebarNav sidebar-active-status"
>
<div
class="SidebarItem"
data-id-test="workloads"
data-is-active-test="false"
data-test-id="workloads"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-workloads"
href="/"
>
<i
class="Icon svg focusable"
>
<span
class="icon"
/>
</i>
<span
class="link-text box grow"
>
Workloads
</span>
<i
class="Icon expand-icon box right material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-id-test="config"
data-is-active-test="false"
data-test-id="config"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-config"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="list"
>
list
</span>
</i>
<span
class="link-text box grow"
>
Config
</span>
</a>
</div>
<div
class="SidebarItem"
data-id-test="network"
data-is-active-test="false"
data-test-id="network"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-network"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="device_hub"
>
device_hub
</span>
</i>
<span
class="link-text box grow"
>
Network
</span>
<i
class="Icon expand-icon box right material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-id-test="storage"
data-is-active-test="false"
data-test-id="storage"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-storage"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="storage"
>
storage
</span>
</i>
<span
class="link-text box grow"
>
Storage
</span>
</a>
</div>
<div
class="SidebarItem"
data-id-test="helm"
data-is-active-test="true"
data-test-id="helm"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center expandable active"
data-testid="sidebar-item-link-for-helm"
href="/"
>
<i
class="Icon svg focusable"
>
<span
class="icon"
/>
</i>
<span
class="link-text box grow"
>
Helm
</span>
<i
class="Icon expand-icon box right material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
<div
class="SidebarItem"
data-id-test="user-management"
data-is-active-test="false"
data-test-id="user-management"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-user-management"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="security"
>
security
</span>
</i>
<span
class="link-text box grow"
>
Access Control
</span>
</a>
</div>
<div
class="SidebarItem"
data-id-test="custom-resources"
data-is-active-test="false"
data-test-id="custom-resources"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-custom-resources"
href="/"
>
<i
class="Icon material focusable"
>
<span
class="icon"
data-icon-name="extension"
>
extension
</span>
</i>
<span
class="link-text box grow"
>
Custom Resources
</span>
<i
class="Icon expand-icon box right material focusable"
>
<span
class="icon"
data-icon-name="keyboard_arrow_down"
>
keyboard_arrow_down
</span>
</i>
</a>
</div>
</div>
</div>
<div
class="TabLayout"
data-testid="tab-layout"
>
<div
class="Tabs center scrollable"
>
<div
class="Tab flex gaps align-center active"
data-is-active-test="true"
data-testid="tab-link-for-charts"
role="tab"
tabindex="0"
>
<div
class="label"
>
Charts
</div>
</div>
<div
class="Tab flex gaps align-center"
data-is-active-test="false"
data-testid="tab-link-for-releases"
role="tab"
tabindex="0"
>
<div
class="label"
>
Releases
</div>
</div>
</div>
<main>
<div
data-testid="page-for-helm-charts"
style="display: none;"
/>
<div
class="ItemListLayout flex column HelmCharts"
>
<div
class="header flex gaps align-center"
>
<div
class="Input SearchInput focused"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
placeholder="Search Helm Charts..."
spellcheck="false"
value=""
/>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="search"
>
search
</span>
</i>
</label>
<div
class="input-info flex gaps"
/>
</div>
</div>
<div
class="items box grow flex column"
>
<div
class="Table flex column HelmCharts box grow dark selectable scrollable sortable autoSize virtual"
>
<div
class="TableHead sticky nowrap topLine"
>
<div
class="TableCell icon nowrap"
>
<div
class="content"
/>
</div>
<div
class="TableCell name nowrap sorting"
id="name"
>
<div
class="content"
>
Name
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell description nowrap"
id="description"
>
<div
class="content"
>
Description
</div>
</div>
<div
class="TableCell version nowrap"
id="version"
>
<div
class="content"
>
Version
</div>
</div>
<div
class="TableCell app-version nowrap"
id="app-version"
>
<div
class="content"
>
App Version
</div>
</div>
<div
class="TableCell repository nowrap sorting"
id="repo"
>
<div
class="content"
>
Repository
</div>
<i
class="Icon sortIcon material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
<div
class="TableCell menu nowrap"
>
<div
class="content"
>
<i
class="Icon material interactive focusable"
id="menu_actions_17"
tabindex="0"
>
<span
class="icon"
data-icon-name="more_vert"
>
more_vert
</span>
</i>
</div>
</div>
</div>
<div
class="Spinner singleColor center"
/>
</div>
<div
class="AddRemoveButtons flex gaps"
/>
</div>
</div>
</main>
</div>
</div>
`;

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
describe("helm-charts - navigation to Helm charts", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
});
describe("when navigating to Helm charts", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder.setEnvironmentToClusterFrame();
rendered = await applicationBuilder.render();
applicationBuilder.helmCharts.navigate();
});
it("renders", () => {
expect(rendered.container).toMatchSnapshot();
});
it("shows page for Helm charts", () => {
const page = rendered.getByTestId("page-for-helm-charts");
expect(page).not.toBeNull();
});
});
});

View File

@ -31,7 +31,7 @@ describe("navigating between routes", () => {
describe("given route without path parameters", () => {
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(testRouteWithoutPathParametersInjectable);
rendererDi.register(testRouteWithoutPathParametersComponentInjectable);
});
@ -102,7 +102,7 @@ describe("navigating between routes", () => {
describe("given route with optional path parameters", () => {
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(routeWithOptionalPathParametersInjectable);
rendererDi.register(routeWithOptionalPathParametersComponentInjectable);
});

View File

@ -10,8 +10,6 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import currentPathInjectable from "../../renderer/routes/current-path.injectable";
import { routeInjectionToken } from "../../common/front-end-routing/route-injection-token";
import { computed } from "mobx";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { preferenceNavigationItemInjectionToken } from "../../renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable";
import routeIsActiveInjectable from "../../renderer/routes/route-is-active.injectable";
import { Preferences } from "../../renderer/components/+preferences";
@ -24,7 +22,6 @@ import { createObservableHistory } from "mobx-observable-history";
import navigateToPreferenceTabInjectable from "../../renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable";
import navigateToFrontPageInjectable from "../../common/front-end-routing/navigate-to-front-page.injectable";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - closing-preferences", () => {
let applicationBuilder: ApplicationBuilder;
@ -32,23 +29,13 @@ describe("preferences - closing-preferences", () => {
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.register(testPreferencesRouteInjectable);
rendererDi.register(testPreferencesRouteComponentInjectable);
rendererDi.register(testFrontPageRouteInjectable);
rendererDi.register(testFrontPageRouteComponentInjectable);
rendererDi.register(testNavigationItemInjectable);
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
rendererDi.override(navigateToFrontPageInjectable, (di) => {
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const testFrontPage = di.inject(testFrontPageRouteInjectable);
@ -65,7 +52,7 @@ describe("preferences - closing-preferences", () => {
let rendererDi: DiContainer;
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(observableHistoryInjectable, () => {
const historyFake = createMemoryHistory({
initialEntries: ["/some-test-path"],
@ -138,7 +125,7 @@ describe("preferences - closing-preferences", () => {
let rendererDi: DiContainer;
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(observableHistoryInjectable, () => {
const historyFake = createMemoryHistory({
initialEntries: ["/preferences/app"],

View File

@ -5,28 +5,13 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import navigateToProxyPreferencesInjectable from "../../common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to application preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in some child page of preferences, when rendered", () => {

View File

@ -5,28 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to editor preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
editorConfiguration: { minimap: {}, tabSize: 42, fontSize: 42 },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,30 +5,15 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import React from "react";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to extension specific preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,29 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import { observable } from "mobx";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to kubernetes preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,27 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to proxy preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -8,29 +8,14 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import navigateToTelemetryPreferencesInjectable from "../../common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable";
import sentryDnsUrlInjectable from "../../renderer/components/+preferences/sentry-dns-url.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to telemetry preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {
@ -134,7 +119,7 @@ describe("preferences - navigation to telemetry preferences", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(sentryDnsUrlInjectable, () => "some-sentry-dns-url");
});
@ -164,7 +149,7 @@ describe("preferences - navigation to telemetry preferences", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder.beforeSetups(({ rendererDi }) => {
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(sentryDnsUrlInjectable, () => null);
});

View File

@ -5,11 +5,7 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import { observable } from "mobx";
import defaultShellInjectable from "../../renderer/components/+preferences/default-shell.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to terminal preferences", () => {
let applicationBuilder: ApplicationBuilder;
@ -17,19 +13,8 @@ describe("preferences - navigation to terminal preferences", () => {
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
terminalConfig: { fontSize: 42 },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(defaultShellInjectable, () => "some-default-shell");
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});

View File

@ -6,10 +6,6 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
@ -18,20 +14,6 @@ describe("preferences - navigation using application menu", () => {
beforeEach(async () => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeSetups(({ rendererDi, mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
rendered = await applicationBuilder.render();
});
@ -46,8 +28,8 @@ describe("preferences - navigation using application menu", () => {
});
describe("when navigating to preferences using application menu", () => {
beforeEach(() => {
applicationBuilder.applicationMenu.click("root.preferences");
beforeEach(async () => {
await applicationBuilder.applicationMenu.click("root.preferences");
});
it("renders", () => {

View File

@ -13,7 +13,7 @@ describe("welcome - navigation using application menu", () => {
let rendered: RenderResult;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder().beforeSetups(({ mainDi }) => {
applicationBuilder = getApplicationBuilder().beforeApplicationStart(({ mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
});
@ -31,8 +31,8 @@ describe("welcome - navigation using application menu", () => {
});
describe("when navigating to welcome using application menu", () => {
beforeEach(() => {
applicationBuilder.applicationMenu.click("help.welcome");
beforeEach(async () => {
await applicationBuilder.applicationMenu.click("help.welcome");
});
it("renders", () => {

View File

@ -81,15 +81,13 @@ class TestStore extends BaseStore<TestStoreModel> {
describe("BaseStore", () => {
let store: TestStore;
beforeEach(async () => {
beforeEach(() => {
const mainDi = getDiForUnitTesting({ doGeneralOverrides: true });
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
mainDi.permitSideEffects(appVersionInjectable);
await mainDi.runSetups();
TestStore.resetInstance();
const mockOpts = {

View File

@ -8,7 +8,7 @@ import mockFs from "mock-fs";
import path from "path";
import fse from "fs-extra";
import type { Cluster } from "../cluster/cluster";
import { ClusterStore } from "../cluster-store/cluster-store";
import type { ClusterStore } from "../cluster-store/cluster-store";
import { Console } from "console";
import { stdout, stderr } from "process";
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
@ -21,6 +21,7 @@ import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import assert from "assert";
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
console = new Console(stdout, stderr);
@ -81,15 +82,14 @@ describe("cluster-store", () => {
mockFs();
mainDi.override(clusterStoreInjectable, (di) => ClusterStore.createInstance({ createCluster: di.inject(createClusterInjectionToken) }));
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
mainDi.override(directoryForTempInjectable, () => "some-temp-directory");
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
mainDi.permitSideEffects(appVersionInjectable);
mainDi.permitSideEffects(clusterStoreInjectable);
await mainDi.runSetups();
createCluster = mainDi.inject(createClusterInjectionToken);
mainDi.unoverride(clusterStoreInjectable);
});
afterEach(() => {
@ -104,10 +104,6 @@ describe("cluster-store", () => {
getCustomKubeConfigDirectoryInjectable,
);
// TODO: Remove these by removing Singleton base-class from BaseStore
ClusterStore.getInstance(false)?.unregisterIpcListener();
ClusterStore.resetInstance();
const mockOpts = {
"some-directory-for-user-data": {
"lens-cluster-store.json": JSON.stringify({}),
@ -116,7 +112,11 @@ describe("cluster-store", () => {
mockFs(mockOpts);
createCluster = mainDi.inject(createClusterInjectionToken);
clusterStore = mainDi.inject(clusterStoreInjectable);
clusterStore.unregisterIpcListener();
});
afterEach(() => {
@ -198,8 +198,6 @@ describe("cluster-store", () => {
describe("config with existing clusters", () => {
beforeEach(() => {
ClusterStore.resetInstance();
const mockOpts = {
"temp-kube-config": kubeconfig,
"some-directory-for-user-data": {
@ -238,6 +236,8 @@ describe("cluster-store", () => {
mockFs(mockOpts);
createCluster = mainDi.inject(createClusterInjectionToken);
clusterStore = mainDi.inject(clusterStoreInjectable);
});
@ -288,8 +288,6 @@ users:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
ClusterStore.resetInstance();
const mockOpts = {
"invalid-kube-config": invalidKubeconfig,
"valid-kube-config": kubeconfig,
@ -322,6 +320,8 @@ users:
mockFs(mockOpts);
createCluster = mainDi.inject(createClusterInjectionToken);
clusterStore = mainDi.inject(clusterStoreInjectable);
});
@ -338,7 +338,6 @@ users:
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => {
ClusterStore.resetInstance();
const mockOpts = {
"some-directory-for-user-data": {
"lens-cluster-store.json": JSON.stringify({
@ -364,6 +363,8 @@ users:
mockFs(mockOpts);
createCluster = mainDi.inject(createClusterInjectionToken);
clusterStore = mainDi.inject(clusterStoreInjectable);
});

View File

@ -18,8 +18,7 @@ import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.i
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import loggerInjectable from "../logger.injectable";
import type { Logger } from "../logger";
console.log("I am here as reminder against mockfs (and to fix console logging)");
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
@ -101,6 +100,8 @@ describe("HotbarStore", () => {
di.override(loggerInjectable, () => loggerMock);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
@ -121,12 +122,12 @@ describe("HotbarStore", () => {
});
describe("given no previous data in store, running all migrations", () => {
beforeEach(async () => {
beforeEach(() => {
mockFs();
await di.runSetups();
hotbarStore = di.inject(hotbarStoreInjectable);
hotbarStore.load();
});
describe("load", () => {
@ -283,9 +284,9 @@ describe("HotbarStore", () => {
});
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(async () => {
beforeEach(() => {
const configurationToBeMigrated = {
"some-electron-app-path-for-user-data": {
"some-directory-for-user-data": {
"lens-hotbar-store.json": JSON.stringify({
__internal__: {
migrations: {
@ -350,9 +351,9 @@ describe("HotbarStore", () => {
di.override(appVersionInjectable, () => "5.0.0-beta.10");
await di.runSetups();
hotbarStore = di.inject(hotbarStoreInjectable);
hotbarStore.load();
});
it("allows to retrieve a hotbar", () => {

View File

@ -33,10 +33,8 @@ import type { ClusterStoreModel } from "../cluster-store/cluster-store";
import { defaultThemeId } from "../vars";
import writeFileInjectable from "../fs/write-file.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable
from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable
from "../get-configuration-file-model/app-version/app-version.injectable";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
console = new Console(stdout, stderr);
@ -44,7 +42,7 @@ describe("user store tests", () => {
let userStore: UserStore;
let di: DiContainer;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
@ -55,8 +53,6 @@ describe("user store tests", () => {
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
await di.runSetups();
});
afterEach(() => {

View File

@ -8,6 +8,7 @@ import { appEventBus } from "./event-bus";
const appEventBusInjectable = getInjectable({
id: "app-event-bus",
instantiate: () => appEventBus,
causesSideEffects: true,
});
export default appEventBusInjectable;

View File

@ -0,0 +1,34 @@
/**
* 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 { AppPaths } from "./app-path-injection-token";
const appPathsStateInjectable = getInjectable({
id: "app-paths-state",
instantiate: () => {
let state: AppPaths;
return {
get: () =>{
if (!state) {
throw new Error("Tried to get app paths before state is setupped.");
}
return state;
},
set: (newState: AppPaths) => {
if (state) {
throw new Error("Tried to overwrite existing state of app paths.");
}
state = newState;
},
};
},
});
export default appPathsStateInjectable;

View File

@ -0,0 +1,15 @@
/**
* 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 { appPathsInjectionToken } from "./app-path-injection-token";
import appPathsStateInjectable from "./app-paths-state.injectable";
const appPathsInjectable = getInjectable({
id: "app-paths",
instantiate: (di) => di.inject(appPathsStateInjectable).get(),
injectionToken: appPathsInjectionToken,
});
export default appPathsInjectable;

View File

@ -2,27 +2,27 @@
* 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 type { AppPaths } from "./app-path-injection-token";
import { appPathsInjectionToken } from "./app-path-injection-token";
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
import type { PathName } from "./app-path-names";
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { DiContainer } from "@ogre-tools/injectable";
describe("app-paths", () => {
let mainDi: DiContainer;
let applicationBuilder: ApplicationBuilder;
let rendererDi: DiContainer;
let runSetups: () => Promise<void[]>;
let mainDi: DiContainer;
beforeEach(() => {
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
applicationBuilder = getApplicationBuilder();
mainDi = dis.mainDi;
rendererDi = dis.rendererDi;
runSetups = dis.runSetups;
rendererDi = applicationBuilder.dis.rendererDi;
mainDi = applicationBuilder.dis.mainDi;
const defaultAppPathsStub: AppPaths = {
appData: "some-app-data",
@ -40,9 +40,10 @@ describe("app-paths", () => {
recent: "some-recent",
temp: "some-temp",
videos: "some-videos",
userData: "some-irrelevant",
userData: "some-irrelevant-user-data",
};
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(
getElectronAppPathInjectable,
() =>
@ -60,10 +61,11 @@ describe("app-paths", () => {
mainDi.override(appNameInjectable, () => "some-app-name");
});
});
describe("normally", () => {
beforeEach(async () => {
await runSetups();
await applicationBuilder.render();
});
it("given in renderer, when injecting app paths, returns application specific app paths", () => {
@ -115,12 +117,14 @@ describe("app-paths", () => {
describe("when running integration tests", () => {
beforeEach(async () => {
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
mainDi.override(
directoryForIntegrationTestingInjectable,
() => "some-integration-testing-app-data",
);
});
await runSetups();
await applicationBuilder.render();
});
it("given in renderer, when injecting path for app data, has integration specific app data path", () => {

View File

@ -10,6 +10,7 @@ export type HasCategoryForEntity = (data: CatalogEntityData & CatalogEntityKindD
const hasCategoryForEntityInjectable = getInjectable({
id: "has-category-for-entity",
instantiate: (di): HasCategoryForEntity => {
const registry = di.inject(catalogCategoryRegistryInjectable);

View File

@ -3,11 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ClusterFrameHandler } from "./lens-views";
import { clusterFrameMap } from "./cluster-frames";
const clusterFramesInjectable = getInjectable({
id: "cluster-frames",
instantiate: () => new ClusterFrameHandler(),
instantiate: () => clusterFrameMap,
causesSideEffects: true,
});
export default clusterFramesInjectable;

View File

@ -9,10 +9,13 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection
const clusterStoreInjectable = getInjectable({
id: "cluster-store",
instantiate: (di) =>
ClusterStore.createInstance({
instantiate: (di) => {
ClusterStore.resetInstance();
return ClusterStore.createInstance({
createCluster: di.inject(createClusterInjectionToken),
}),
});
},
causesSideEffects: true,
});

View File

@ -13,8 +13,8 @@ import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers";
import type { KubeApiResource, KubeResource } from "../rbac";
import { apiResourceRecord, apiResources } from "../rbac";
import { VersionDetector } from "../../main/cluster-detectors/version-detector";
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import plimit from "p-limit";
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
@ -29,11 +29,13 @@ import type { Logger } from "../logger";
export interface ClusterDependencies {
readonly directoryForKubeConfigs: string;
readonly logger: Logger;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager | undefined;
createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
readonly detectorRegistry: DetectorRegistry;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces;
createVersionDetector: (cluster: Cluster) => VersionDetector;
}
/**
@ -441,7 +443,7 @@ export class Cluster implements ClusterModel, ClusterState {
@action
async refreshMetadata() {
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
const metadata = await DetectorRegistry.getInstance().detectForCluster(this);
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
const existingMetadata = this.metadata;
this.metadata = Object.assign(existingMetadata, metadata);
@ -504,7 +506,7 @@ export class Cluster implements ClusterModel, ClusterState {
protected async getConnectionStatus(): Promise<ClusterStatus> {
try {
const versionDetector = new VersionDetector(this);
const versionDetector = this.dependencies.createVersionDetector(this);
const versionData = await versionDetector.detect();
this.metadata.version = versionData.value;

View File

@ -11,7 +11,7 @@ import type { ClusterStore } from "../cluster-store/cluster-store";
import { pipeline } from "@ogre-tools/fp";
describe("verify-that-all-routes-have-component", () => {
it("verify that routes have route component", async () => {
it("verify that routes have route component", () => {
const rendererDi = getDiForUnitTesting({ doGeneralOverrides: true });
rendererDi.override(clusterStoreInjectable, () => ({

View File

@ -0,0 +1,18 @@
/**
* 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 fsInjectable from "./fs.injectable";
const ensureDirInjectable = getInjectable({
id: "ensure-dir",
// TODO: Remove usages of ensureDir from business logic.
// TODO: Read, Write, Watch etc. operations should do this internally.
instantiate: (di) => di.inject(fsInjectable).ensureDir,
causesSideEffects: true,
});
export default ensureDirInjectable;

View File

@ -40,8 +40,8 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
},
migrations,
});
makeObservable(this);
this.load();
}
@computed get activeHotbarId() {

View File

@ -0,0 +1,14 @@
/**
* 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 { broadcastMessage } from "./ipc";
const broadcastMessageInjectable = getInjectable({
id: "broadcast-message",
instantiate: () => broadcastMessage,
causesSideEffects: true,
});
export default broadcastMessageInjectable;

View File

@ -23,11 +23,9 @@ const mockFetch = fetch as FetchMock;
describe("forRemoteCluster", () => {
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
di.override(apiManagerInjectable, () => apiManager);
@ -87,11 +85,9 @@ describe("KubeApi", () => {
let request: KubeJsonApi;
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
request = new KubeJsonApi({
serverAddress: `http://127.0.0.1:9999`,
apiBase: "/api-kube",

View File

@ -0,0 +1,267 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "./run-many-for";
import { runManyFor } from "./run-many-for";
import { getPromiseStatus } from "../test-utils/get-promise-status";
describe("runManyFor", () => {
describe("given no hierarchy, when running many", () => {
let runMock: AsyncFnMock<(...args: unknown[]) => Promise<void>>;
let actualPromise: Promise<void>;
beforeEach(() => {
const rootDi = createContainer();
runMock = asyncFn();
const someInjectionTokenForRunnables = getInjectionToken<Runnable>({
id: "some-injection-token",
});
const someInjectable = getInjectable({
id: "some-injectable",
instantiate: () => ({ run: () => runMock("some-call") }),
injectionToken: someInjectionTokenForRunnables,
});
const someOtherInjectable = getInjectable({
id: "some-other-injectable",
instantiate: () => ({ run: () => runMock("some-other-call") }),
injectionToken: someInjectionTokenForRunnables,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManyFor(rootDi)(someInjectionTokenForRunnables);
actualPromise = runMany() as Promise<void>;
});
it("runs all runnables at the same time", () => {
expect(runMock.mock.calls).toEqual([
["some-call"],
["some-other-call"],
]);
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("when all runnables resolve, resolves", async () => {
await Promise.all([runMock.resolve(), runMock.resolve()]);
expect(await actualPromise).toBe(undefined);
});
});
describe("given hierarchy that is three levels deep, when running many", () => {
let runMock: AsyncFnMock<(...args: unknown[]) => Promise<void>>;
let actualPromise: Promise<void>;
beforeEach(() => {
const di = createContainer();
runMock = asyncFn();
const someInjectionTokenForRunnables = getInjectionToken<Runnable>({
id: "some-injection-token",
});
const someInjectable1 = getInjectable({
id: "some-injectable-1",
instantiate: (di) => ({
run: () => runMock("third-level-run"),
runAfter: di.inject(someInjectable2),
}),
injectionToken: someInjectionTokenForRunnables,
});
const someInjectable2 = getInjectable({
id: "some-injectable-2",
instantiate: (di) => ({
run: () => runMock("second-level-run"),
runAfter: di.inject(someInjectable3),
}),
injectionToken: someInjectionTokenForRunnables,
});
const someInjectable3 = getInjectable({
id: "some-injectable-3",
instantiate: () => ({ run: () => runMock("first-level-run") }),
injectionToken: someInjectionTokenForRunnables,
});
di.register(someInjectable1, someInjectable2, someInjectable3);
const runMany = runManyFor(di)(someInjectionTokenForRunnables);
actualPromise = runMany() as Promise<void>;
});
it("runs first level runnables", () => {
expect(runMock.mock.calls).toEqual([["first-level-run"]]);
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
describe("when first level runnables resolve", () => {
beforeEach(async () => {
runMock.mockClear();
await runMock.resolveSpecific(["first-level-run"]);
});
it("runs second level runnables", async () => {
expect(runMock.mock.calls).toEqual([["second-level-run"]]);
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
describe("when second level runnables resolve", () => {
beforeEach(async () => {
runMock.mockClear();
await runMock.resolveSpecific(["second-level-run"]);
});
it("runs final third level runnables", async () => {
expect(runMock.mock.calls).toEqual([["third-level-run"]]);
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
describe("when final third level runnables resolve", () => {
beforeEach(async () => {
await runMock.resolveSpecific(["third-level-run"]);
});
it("resolves", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(true);
});
});
});
});
});
it("given invalid hierarchy, when running runnables, throws", () => {
const rootDi = createContainer();
const runMock = asyncFn<(...args: unknown[]) => void>();
const someInjectionToken = getInjectionToken<Runnable>({
id: "some-injection-token",
});
const someOtherInjectionToken = getInjectionToken<Runnable>({
id: "some-other-injection-token",
});
const someInjectable = getInjectable({
id: "some-runnable-1",
instantiate: (di) => ({
run: () => runMock("some-runnable-1"),
runAfter: di.inject(someOtherInjectable),
}),
injectionToken: someInjectionToken,
});
const someOtherInjectable = getInjectable({
id: "some-runnable-2",
instantiate: () => ({
run: () => runMock("some-runnable-2"),
}),
injectionToken: someOtherInjectionToken,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManyFor(rootDi)(
someInjectionToken,
);
return expect(() => runMany()).rejects.toThrow(
"Tried to run runnable after other runnable which does not same injection token.",
);
});
describe("when running many with parameter", () => {
let runMock: AsyncFnMock<(...args: unknown[]) => Promise<void>>;
beforeEach(() => {
const rootDi = createContainer();
runMock = asyncFn();
const someInjectionTokenForRunnablesWithParameter = getInjectionToken<
Runnable<string>
>({
id: "some-injection-token",
});
const someInjectable = getInjectable({
id: "some-runnable-1",
instantiate: () => ({
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
});
const someOtherInjectable = getInjectable({
id: "some-runnable-2",
instantiate: () => ({
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManyFor(rootDi)(
someInjectionTokenForRunnablesWithParameter,
);
runMany("some-parameter");
});
it("runs all runnables using the parameter", () => {
expect(runMock.mock.calls).toEqual([
["run-of-some-runnable-1", "some-parameter"],
["run-of-some-runnable-2", "some-parameter"],
]);
});
});
});

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import type {
DiContainerForInjection,
InjectionToken,
} from "@ogre-tools/injectable";
import { filter, forEach, map, tap } from "lodash/fp";
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
export interface Runnable<TParameter = void> {
run: Run<TParameter>;
runAfter?: this;
}
type Run<Param> = (parameter: Param) => Promise<void> | void;
export type RunMany = <Param>(
injectionToken: InjectionToken<Runnable<Param>, void>
) => Run<Param>;
export function runManyFor(di: DiContainerForInjection): RunMany {
return (injectionToken) => async (parameter) => {
const allRunnables = di.injectMany(injectionToken);
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor(allRunnables);
const recursedRun = async (
runAfterRunnable: Runnable<any> | undefined = undefined,
) =>
await pipeline(
allRunnables,
tap(runnables => forEach(throwWithIncorrectHierarchy, runnables)),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map(async (runnable) => {
await runnable.run(parameter);
await recursedRun(runnable);
}),
(promises) => Promise.all(promises),
);
await recursedRun();
};
}

View File

@ -0,0 +1,197 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { RunnableSync } from "./run-many-sync-for";
import { runManySyncFor } from "./run-many-sync-for";
describe("runManySyncFor", () => {
describe("given hierarchy, when running many", () => {
let runMock: jest.Mock;
beforeEach(() => {
const rootDi = createContainer();
runMock = jest.fn();
const someInjectionTokenForRunnables = getInjectionToken<RunnableSync>({
id: "some-injection-token",
});
const someInjectable = getInjectable({
id: "some-injectable",
instantiate: () => ({ run: () => runMock("some-call") }),
injectionToken: someInjectionTokenForRunnables,
});
const someOtherInjectable = getInjectable({
id: "some-other-injectable",
instantiate: () => ({ run: () => runMock("some-other-call") }),
injectionToken: someInjectionTokenForRunnables,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManySyncFor(rootDi)(someInjectionTokenForRunnables);
runMany();
});
it("runs all runnables at the same time", () => {
expect(runMock.mock.calls).toEqual([
["some-call"],
["some-other-call"],
]);
});
});
describe("given hierarchy that is three levels deep, when running many", () => {
let runMock: jest.Mock<(arg: string) => void>;
beforeEach(() => {
const di = createContainer();
runMock = jest.fn();
const someInjectionTokenForRunnables = getInjectionToken<RunnableSync>({
id: "some-injection-token",
});
const someInjectable1 = getInjectable({
id: "some-injectable-1",
instantiate: (di) => ({
run: () => runMock("third-level-run"),
runAfter: di.inject(someInjectable2),
}),
injectionToken: someInjectionTokenForRunnables,
});
const someInjectable2 = getInjectable({
id: "some-injectable-2",
instantiate: (di) => ({
run: () => runMock("second-level-run"),
runAfter: di.inject(someInjectable3),
}),
injectionToken: someInjectionTokenForRunnables,
});
const someInjectable3 = getInjectable({
id: "some-injectable-3",
instantiate: () => ({ run: () => runMock("first-level-run") }),
injectionToken: someInjectionTokenForRunnables,
});
di.register(someInjectable1, someInjectable2, someInjectable3);
const runMany = runManySyncFor(di)(someInjectionTokenForRunnables);
runMany();
});
it("runs runnables in order", () => {
expect(runMock.mock.calls).toEqual([["first-level-run"], ["second-level-run"], ["third-level-run"]]);
});
});
it("given invalid hierarchy, when running runnables, throws", () => {
const rootDi = createContainer();
const runMock = jest.fn();
const someInjectionToken = getInjectionToken<RunnableSync>({
id: "some-injection-token",
});
const someOtherInjectionToken = getInjectionToken<RunnableSync>({
id: "some-other-injection-token",
});
const someInjectable = getInjectable({
id: "some-runnable-1",
instantiate: (di) => ({
run: () => runMock("some-runnable-1"),
runAfter: di.inject(someOtherInjectable),
}),
injectionToken: someInjectionToken,
});
const someOtherInjectable = getInjectable({
id: "some-runnable-2",
instantiate: () => ({
run: () => runMock("some-runnable-2"),
}),
injectionToken: someOtherInjectionToken,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManySyncFor(rootDi)(
someInjectionToken,
);
return expect(() => runMany()).rejects.toThrow(
"Tried to run runnable after other runnable which does not same injection token.",
);
});
describe("when running many with parameter", () => {
let runMock: jest.Mock<(arg: string, arg2: string) => void>;
beforeEach(() => {
const rootDi = createContainer();
runMock = jest.fn();
const someInjectionTokenForRunnablesWithParameter = getInjectionToken<
RunnableSync<string>
>({
id: "some-injection-token",
});
const someInjectable = getInjectable({
id: "some-runnable-1",
instantiate: () => ({
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
});
const someOtherInjectable = getInjectable({
id: "some-runnable-2",
instantiate: () => ({
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
});
rootDi.register(someInjectable, someOtherInjectable);
const runMany = runManySyncFor(rootDi)(
someInjectionTokenForRunnablesWithParameter,
);
runMany("some-parameter");
});
it("runs all runnables using the parameter", () => {
expect(runMock.mock.calls).toEqual([
["run-of-some-runnable-1", "some-parameter"],
["run-of-some-runnable-2", "some-parameter"],
]);
});
});
});

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import type {
DiContainerForInjection,
InjectionToken,
} from "@ogre-tools/injectable";
import { filter, forEach, map, tap } from "lodash/fp";
import type { Runnable } from "./run-many-for";
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
export interface RunnableSync<TParameter = void> {
run: RunSync<TParameter>;
runAfter?: this;
}
type RunSync<Param> = (parameter: Param) => void;
export type RunManySync = <Param>(
injectionToken: InjectionToken<Runnable<Param>, void>
) => RunSync<Param>;
export function runManySyncFor(di: DiContainerForInjection): RunManySync {
return (injectionToken) => async (parameter) => {
const allRunnables = di.injectMany(injectionToken);
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor(allRunnables);
const recursedRun = (
runAfterRunnable: RunnableSync<any> | undefined = undefined,
) =>
pipeline(
allRunnables,
tap(runnables => forEach(throwWithIncorrectHierarchy, runnables)),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map((runnable) => {
runnable.run(parameter);
recursedRun(runnable);
}),
);
recursedRun();
};
}

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Runnable } from "./run-many-for";
import type { RunnableSync } from "./run-many-sync-for";
export const throwWithIncorrectHierarchyFor =
(allRunnables: Runnable<any>[] | RunnableSync<any>[]) =>
(runnable: Runnable<any> | RunnableSync<any>) => {
if (runnable.runAfter && !allRunnables.includes(runnable.runAfter)) {
throw new Error(
"Tried to run runnable after other runnable which does not same injection token.",
);
}
};

View File

@ -0,0 +1,5 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export const flushPromises = () => new Promise(setImmediate);

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { flushPromises } from "./flush-promises";
export const getPromiseStatus = async (promise: Promise<unknown>) => {
const status = { fulfilled: false };
promise.finally(() => {
status.fulfilled = true;
});
await flushPromises();
return status;
};

View File

@ -0,0 +1,31 @@
/**
* 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";
const environmentVariablesInjectable = getInjectable({
id: "environment-variables",
instantiate: () => {
// IMPORTANT: The syntax needs to be exactly this in order to make environment variable values
// hard-coded at compile-time by Webpack.
const NODE_ENV = process.env.NODE_ENV;
const JEST_WORKER_ID = process.env.JEST_WORKER_ID;
const CICD = process.env.CICD;
return {
// Compile-time environment variables
NODE_ENV,
JEST_WORKER_ID,
CICD,
// Runtime environment variables
LENS_DISABLE_GPU: process.env.LENS_DISABLE_GPU,
};
},
causesSideEffects: true,
});
export default environmentVariablesInjectable;

View File

@ -0,0 +1,235 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { getStartableStoppable } from "./get-startable-stoppable";
import { getPromiseStatus } from "../test-utils/get-promise-status";
import { flushPromises } from "../test-utils/flush-promises";
describe("getStartableStoppable", () => {
let stopMock: AsyncFnMock<() => Promise<void>>;
let startMock: AsyncFnMock<() => Promise<() => Promise<void>>>;
let actual: { stop: () => Promise<void>; start: () => Promise<void>; started: boolean };
beforeEach(() => {
stopMock = asyncFn();
startMock = asyncFn();
actual = getStartableStoppable("some-id", startMock);
});
it("does not start yet", () => {
expect(startMock).not.toHaveBeenCalled();
});
it("does not stop yet", () => {
expect(stopMock).not.toHaveBeenCalled();
});
it("when stopping before ever starting, throws", () => {
expect(actual.stop).rejects.toThrow("Tried to stop \"some-id\", but it has not started yet.");
});
it("is not started", () => {
expect(actual.started).toBe(false);
});
describe("when started", () => {
let startPromise: Promise<void>;
beforeEach(() => {
startPromise = actual.start();
});
it("starts starting", () => {
expect(startMock).toHaveBeenCalled();
});
it("starting does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(startPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("is not started yet", () => {
expect(actual.started).toBe(false);
});
describe("when started again before the start has finished", () => {
let error: Error;
beforeEach(() => {
startMock.mockClear();
actual.start().catch((e) => { error = e; });
});
it("does not start starting again", () => {
expect(startMock).not.toHaveBeenCalled();
});
it("throws", () => {
expect(error.message).toBe("Tried to start \"some-id\", but it is already being started.");
});
});
describe("when starting finishes", () => {
beforeEach(async () => {
await startMock.resolve(stopMock);
});
it("is started", () => {
expect(actual.started).toBe(true);
});
it("starting resolves", async () => {
const promiseStatus = await getPromiseStatus(startPromise);
expect(promiseStatus.fulfilled).toBe(true);
});
it("when started again, throws", () => {
expect(actual.start).rejects.toThrow("Tried to start \"some-id\", but it has already started.");
});
it("does not stop yet", () => {
expect(stopMock).not.toHaveBeenCalled();
});
describe("when stopped", () => {
let stopPromise: Promise<void>;
beforeEach(() => {
stopPromise = actual.stop();
});
it("starts stopping", () => {
expect(stopMock).toHaveBeenCalled();
});
it("stopping does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(stopPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("is not stopped yet", () => {
expect(actual.started).toBe(true);
});
describe("when stopping finishes", () => {
beforeEach(async () => {
await stopMock.resolve();
});
it("is not started", () => {
expect(actual.started).toBe(false);
});
it("stopping resolves", async () => {
const promiseStatus = await getPromiseStatus(stopPromise);
expect(promiseStatus.fulfilled).toBe(true);
});
it("when stopped again, throws", () => {
expect(actual.stop).rejects.toThrow("Tried to stop \"some-id\", but it has already stopped.");
});
describe("when started again", () => {
beforeEach(
() => {
startMock.mockClear();
actual.start();
});
it("starts", () => {
expect(startMock).toHaveBeenCalled();
});
it("is not started yet", () => {
expect(actual.started).toBe(false);
});
describe("when starting finishes", () => {
beforeEach(async () => {
await startMock.resolve(stopMock);
});
it("is started", () => {
expect(actual.started).toBe(true);
});
it("when stopped again, starts stopping again", async () => {
stopMock.mockClear();
actual.stop();
await flushPromises();
expect(stopMock).toHaveBeenCalled();
});
});
});
});
});
});
describe("when stopped before starting finishes", () => {
let stopPromise: Promise<void>;
beforeEach(() => {
stopPromise = actual.stop();
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(stopPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("is not started yet", () => {
expect(actual.started).toBe(false);
});
describe("when starting finishes", () => {
beforeEach(async () => {
await startMock.resolve(stopMock);
});
it("starts stopping", () => {
expect(stopMock).toHaveBeenCalled();
});
it("is not stopped yet", () => {
expect(actual.started).toBe(true);
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(stopPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
describe("when stopping finishes", () => {
beforeEach(async () => {
await stopMock.resolve();
});
it("is stopped", () => {
expect(actual.started).toBe(false);
});
it("resolves", async () => {
const promiseStatus = await getPromiseStatus(stopPromise);
expect(promiseStatus.fulfilled).toBe(true);
});
});
});
});
});
});

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
type Stopper = () => Promise<void> | void;
type Starter = () => Promise<Stopper> | Stopper;
export const getStartableStoppable = (
id: string,
startAndGetStopCallback: Starter,
) => {
let stop: Stopper;
let stopped = false;
let started = false;
let starting = false;
let startingPromise: Promise<Stopper> | Stopper;
return {
get started() {
return started;
},
start: async () => {
if (starting) {
throw new Error(`Tried to start "${id}", but it is already being started.`);
}
starting = true;
if (started) {
throw new Error(`Tried to start "${id}", but it has already started.`);
}
startingPromise = startAndGetStopCallback();
stop = await startingPromise;
stopped = false;
started = true;
starting = false;
},
stop: async () => {
await startingPromise;
if (stopped) {
throw new Error(`Tried to stop "${id}", but it has already stopped.`);
}
if (!started) {
throw new Error(`Tried to stop "${id}", but it has not started yet.`);
}
await stop();
started = false;
stopped = true;
},
};
};

View File

@ -10,21 +10,48 @@ import packageInfo from "../../package.json";
import type { ThemeId } from "../renderer/themes/store";
import { lazyInitialized } from "./utils/lazy-initialized";
/**
* @deprecated Switch to using isMacInjectable
*/
export const isMac = process.platform === "darwin";
/**
* @deprecated Switch to using isWindowsInjectable
*/
export const isWindows = process.platform === "win32";
/**
* @deprecated Switch to using isLinuxInjectable
*/
export const isLinux = process.platform === "linux";
export const isDebugging = ["true", "1", "yes", "y", "on"].includes((process.env.DEBUG ?? "").toLowerCase());
export const isSnap = !!process.env.SNAP;
export const isProduction = process.env.NODE_ENV === "production";
/**
* @deprecated Switch to using isTestEnvInjectable
*/
export const isTestEnv = !!process.env.JEST_WORKER_ID;
/**
* @deprecated Switch to using isProductionInjectable
*/
export const isProduction = process.env.NODE_ENV === "production";
/**
* @deprecated Switch to using isDevelopmentInjectable
*/
export const isDevelopment = !isTestEnv && !isProduction;
export const isPublishConfigured = Object.keys(packageInfo.build).includes("publish");
export const integrationTestingArg = "--integration-testing";
export const isIntegrationTesting = process.argv.includes(integrationTestingArg);
export const productName = packageInfo.productName;
/**
* @deprecated Switch to using appNameInjectable
*/
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
export const publicPath = "/build/" as string;
export const defaultThemeId: ThemeId = "lens-dark";
export const defaultFontSize = 12;
@ -101,12 +128,6 @@ export const kubectlBinaryName = getBinaryName("kubectl");
* @deprecated for being explicit side effect.
*/
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
export const staticFilesDirectory = path.resolve(
!isProduction
? process.cwd()
: process.resourcesPath,
"static",
);
// Apis
export const apiPrefix = "/api"; // local router apis

View File

@ -3,11 +3,18 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { isDevelopment } from "../vars";
import isProductionInjectable from "./is-production.injectable";
import isTestEnvInjectable from "./is-test-env.injectable";
const isDevelopmentInjectable = getInjectable({
id: "is-development",
instantiate: () => isDevelopment,
instantiate: (di) => {
const isProduction = di.inject(isProductionInjectable);
const isTestEnv = di.inject(isTestEnvInjectable);
return !isTestEnv && !isProduction;
},
});
export default isDevelopmentInjectable;

View File

@ -0,0 +1,18 @@
/**
* 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 commandLineArgumentsInjectable from "../../main/utils/command-line-arguments.injectable";
const isIntegrationTestingInjectable = getInjectable({
id: "is-integration-testing",
instantiate: (di) => {
const commandLineArguments = di.inject(commandLineArgumentsInjectable);
return commandLineArguments.includes("--integration-testing");
},
});
export default isIntegrationTestingInjectable;

View File

@ -3,12 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { isLinux } from "../vars";
import platformInjectable from "./platform.injectable";
const isLinuxInjectable = getInjectable({
id: "is-linux",
instantiate: () => isLinux,
causesSideEffects: true,
instantiate: (di) => {
const platform = di.inject(platformInjectable);
return platform === "linux";
},
});
export default isLinuxInjectable;

View File

@ -3,12 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { isMac } from "../vars";
import platformInjectable from "./platform.injectable";
const isMacInjectable = getInjectable({
id: "is-mac",
instantiate: () => isMac,
causesSideEffects: true,
instantiate: (di) => {
const platform = di.inject(platformInjectable);
return platform === "darwin";
},
});
export default isMacInjectable;

View File

@ -0,0 +1,18 @@
/**
* 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 environmentVariablesInjectable from "../utils/environment-variables.injectable";
const isProductionInjectable = getInjectable({
id: "is-production",
instantiate: (di) => {
const { NODE_ENV: nodeEnv } = di.inject(environmentVariablesInjectable);
return nodeEnv === "production";
},
});
export default isProductionInjectable;

View File

@ -0,0 +1,18 @@
/**
* 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 environmentVariablesInjectable from "../utils/environment-variables.injectable";
const isTestEnvInjectable = getInjectable({
id: "is-test-env",
instantiate: (di) => {
const { JEST_WORKER_ID: jestWorkerId } = di.inject(environmentVariablesInjectable);
return !!jestWorkerId;
},
});
export default isTestEnvInjectable;

View File

@ -3,12 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { isWindows } from "../vars";
import platformInjectable from "./platform.injectable";
const isWindowsInjectable = getInjectable({
id: "is-windows",
instantiate: () => isWindows,
causesSideEffects: true,
instantiate: (di) => {
const platform = di.inject(platformInjectable);
return platform === "win32";
},
});
export default isWindowsInjectable;

View File

@ -0,0 +1,22 @@
/**
* 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 isProductionInjectable from "./is-production.injectable";
const lensResourcesDirInjectable = getInjectable({
id: "lens-resources-dir",
instantiate: (di) => {
const isProduction = di.inject(isProductionInjectable);
return !isProduction
? process.cwd()
: process.resourcesPath;
},
causesSideEffects: true,
});
export default lensResourcesDirInjectable;

View File

@ -0,0 +1,13 @@
/**
* 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";
const platformInjectable = getInjectable({
id: "platform",
instantiate: () => process.platform,
causesSideEffects: true,
});
export default platformInjectable;

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
import lensResourcesDirInjectable from "./lens-resources-dir.injectable";
const staticFilesDirectoryInjectable = getInjectable({
id: "static-files-directory",
instantiate: (di) => {
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const lensResourcesDir = di.inject(lensResourcesDirInjectable);
return getAbsolutePath(lensResourcesDir, "static");
},
});
export default staticFilesDirectoryInjectable;

View File

@ -0,0 +1,18 @@
/**
* 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 { WeblinkStore } from "./weblink-store";
const weblinkStoreInjectable = getInjectable({
id: "weblink-store",
instantiate: () => {
WeblinkStore.resetInstance();
return WeblinkStore.createInstance();
},
});
export default weblinkStoreInjectable;

View File

@ -9,8 +9,8 @@ import { stdout, stderr } from "process";
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
import { runInAction } from "mobx";
import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
console = new Console(stdout, stderr);
@ -109,18 +109,16 @@ describe("ExtensionLoader", () => {
let extensionLoader: ExtensionLoader;
let updateExtensionStateMock: jest.Mock;
beforeEach(async () => {
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
updateExtensionStateMock = jest.fn();
dis.mainDi.override(updateExtensionsStateInjectable, () => updateExtensionStateMock);
di.override(updateExtensionsStateInjectable, () => updateExtensionStateMock);
await dis.runSetups();
extensionLoader = dis.mainDi.inject(extensionLoaderInjectable);
extensionLoader = di.inject(extensionLoaderInjectable);
});
afterEach(() => {

View File

@ -17,4 +17,4 @@ export const asLegacyGlobalFunctionForExtensionApi = ((
) as unknown as (...args: any[]) => any;
return injected(...args);
}) as Inject<false>;
}) as Inject;

View File

@ -43,4 +43,4 @@ export const asLegacyGlobalForExtensionApi = ((
return propertyValue;
},
},
)) as Inject<false>;
)) as Inject;

View File

@ -33,5 +33,11 @@ export const getLegacyGlobalDiForExtensionApi = () => {
};
export function getEnvironmentSpecificLegacyGlobalDiForExtensionApi(environment: Environments) {
return legacyGlobalDis.get(environment);
const di = legacyGlobalDis.get(environment);
if (!di) {
throw new Error("Tried to get DI container using legacy globals in environment which doesn't exist");
}
return di;
}

View File

@ -12,6 +12,7 @@ import extensionInstallationStateStoreInjectable from "../extension-installation
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
const extensionDiscoveryInjectable = getInjectable({
id: "extension-discovery",
@ -37,6 +38,8 @@ const extensionDiscoveryInjectable = getInjectable({
extensionPackageRootDirectory: di.inject(
extensionPackageRootDirectoryInjectable,
),
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
}),
});

View File

@ -49,7 +49,7 @@ const mockedFse = fse as jest.Mocked<typeof fse>;
describe("ExtensionDiscovery", () => {
let extensionDiscovery: ExtensionDiscovery;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
@ -57,8 +57,6 @@ describe("ExtensionDiscovery", () => {
mockFs();
await di.runSetups();
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
});

View File

@ -16,7 +16,7 @@ import logger from "../../main/logger";
import type { ExtensionsStore } from "../extensions-store/extensions-store";
import type { ExtensionLoader } from "../extension-loader";
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
import { isProduction, staticFilesDirectory } from "../../common/vars";
import { isProduction } from "../../common/vars";
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
import type { PackageJson } from "type-fest";
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
@ -34,6 +34,7 @@ interface Dependencies {
installExtension: (name: string) => Promise<void>;
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
extensionPackageRootDirectory: string;
staticFilesDirectory: string;
}
export interface InstalledExtension {
@ -112,7 +113,7 @@ export class ExtensionDiscovery {
}
get inTreeFolderPath(): string {
return path.resolve(staticFilesDirectory, "../extensions");
return path.resolve(this.dependencies.staticFilesDirectory, "../extensions");
}
get nodeModulesPath(): string {

View File

@ -11,6 +11,7 @@ import type { CatalogEntityRegistry as MainCatalogEntityRegistry } from "../main
import type { CatalogEntityRegistry as RendererCatalogEntityRegistry } from "../renderer/api/catalog/entity/registry";
import type { GetExtensionPageParameters } from "../renderer/routes/get-extension-page-parameters.injectable";
import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import type { NavigateForExtension } from "../main/start-main-application/lens-window/navigate-for-extension.injectable";
export interface LensExtensionDependencies {
readonly fileSystemProvisionerStore: FileSystemProvisionerStore;
@ -18,6 +19,7 @@ export interface LensExtensionDependencies {
export interface LensMainExtensionDependencies extends LensExtensionDependencies {
readonly entityRegistry: MainCatalogEntityRegistry;
readonly navigate: NavigateForExtension;
}
export interface LensRendererExtensionDependencies extends LensExtensionDependencies {

View File

@ -4,7 +4,6 @@
*/
import { LensExtension, lensExtensionDependencies } from "./lens-extension";
import { WindowManager } from "../main/window-manager";
import type { CatalogEntity } from "../common/catalog";
import type { IObservableArray } from "mobx";
import type { MenuRegistration } from "../main/menu/menu-registration";
@ -32,7 +31,7 @@ export class LensMainExtension extends LensExtension<LensMainExtensionDependenci
terminalShellEnvModifier?: ShellEnvModifier;
async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) {
return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId);
await this[lensExtensionDependencies].navigate(this.id, pageId, params, frameId);
}
addCatalogSource(id: string, source: IObservableArray<CatalogEntity>) {

View File

@ -3,8 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { WindowManager } from "../../main/window-manager";
import {
Environments,
getEnvironmentSpecificLegacyGlobalDiForExtensionApi,
} from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import navigateInjectable from "../../main/start-main-application/lens-window/navigate.injectable";
export function navigate(url: string) {
return WindowManager.getInstance().navigate(url);
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.main);
const navigate = di.inject(navigateInjectable);
return navigate(url);
}

View File

@ -19,6 +19,8 @@ import listNamespacesInjectable from "../../common/cluster/list-namespaces.injec
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
import type { ClusterContextHandler } from "../context-handler/context-handler";
import { parse } from "url";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
console = new Console(process.stdout, process.stderr); // fix mockFS
@ -26,7 +28,7 @@ describe("create clusters", () => {
let cluster: Cluster;
let createCluster: (model: ClusterModel) => Cluster;
beforeEach(async () => {
beforeEach(() => {
jest.clearAllMocks();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -55,8 +57,8 @@ describe("create clusters", () => {
}),
});
await di.runSetups();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
di.override(createContextHandlerInjectable, () => (cluster) => ({

View File

@ -5,13 +5,14 @@
import { UserStore } from "../../common/user-store";
import type { ClusterContextHandler } from "../context-handler/context-handler";
import type { PrometheusService } from "../prometheus";
import { PrometheusProvider, PrometheusProviderRegistry } from "../prometheus";
import type { PrometheusService, PrometheusProviderRegistry } from "../prometheus";
import { PrometheusProvider } from "../prometheus";
import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import prometheusProviderRegistryInjectable from "../prometheus/prometheus-provider-registry.injectable";
jest.mock("electron", () => ({
app: {
@ -74,8 +75,9 @@ const clusterStub = {
describe("ContextHandler", () => {
let createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
let prometheusProviderRegistry: PrometheusProviderRegistry;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs({
@ -84,15 +86,12 @@ describe("ContextHandler", () => {
di.override(createKubeAuthProxyInjectable, () => ({} as any));
await di.runSetups();
prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
createContextHandler = di.inject(createContextHandlerInjectable);
PrometheusProviderRegistry.createInstance();
});
afterEach(() => {
PrometheusProviderRegistry.resetInstance();
UserStore.resetInstance();
mockFs.restore();
});
@ -104,17 +103,16 @@ describe("ContextHandler", () => {
[0, 2],
[0, 3],
])("should throw from %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
}
for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
expect(() => {
@ -135,17 +133,16 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
}
for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
// TODO: Unit test shouldn't access protected or private methods
@ -166,17 +163,16 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
}
// TODO: Unit test shouldn't access protected or private methods
@ -197,23 +193,22 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
const beforeSuccesses = Math.floor(successes / 2);
const afterSuccesses = successes - beforeSuccesses;
for (let i = 0; i < beforeSuccesses; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
}
for (let i = 0; i < afterSuccesses; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
// TODO: Unit test shouldn't access protected or private methods
@ -225,12 +220,11 @@ describe("ContextHandler", () => {
});
it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
// TODO: Unit test shouldn't access protected or private methods
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };

View File

@ -57,6 +57,8 @@ import path from "path";
import spawnInjectable from "../child-process/spawn.injectable";
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
console = new Console(stdout, stderr);
@ -99,6 +101,9 @@ describe("kube auth proxy tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(spawnInjectable, () => mockSpawn);
di.permitSideEffects(getConfigurationFileModelInjectable);
@ -106,8 +111,6 @@ describe("kube auth proxy tests", () => {
mockFs(mockMinikubeConfig);
await di.runSetups();
createCluster = di.inject(createClusterInjectionToken);
createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);

View File

@ -2,7 +2,6 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
import mockFs from "mock-fs";
@ -20,6 +19,7 @@ import { parse } from "url";
import loggerInjectable from "../../common/logger.injectable";
import type { Logger } from "../../common/logger";
import assert from "assert";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
console = new Console(process.stdout, process.stderr); // fix mockFS
@ -33,6 +33,7 @@ describe("kubeconfig manager tests", () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
loggerMock = {
warn: jest.fn(),
@ -68,8 +69,6 @@ describe("kubeconfig manager tests", () => {
}),
});
await di.runSetups();
di.override(createContextHandlerInjectable, () => (cluster) => ({
restartServer: jest.fn(),
stopServer: jest.fn(),

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isLongRunningRequest } from "../lens-proxy";
import { isLongRunningRequest } from "../lens-proxy/lens-proxy";
describe("isLongRunningRequest", () => {
it("returns true on watches", () => {

View File

@ -26,11 +26,9 @@ jest.mock("electron", () => ({
describe("static-file-route", () => {
let handleStaticFileRoute: Route<Buffer, "/{path*}">;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
handleStaticFileRoute = di.inject(staticFileRouteInjectable);
});

View File

@ -3,11 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import electronAppInjectable from "../get-electron-app-path/electron-app/electron-app.injectable";
import packageInfo from "../../../../package.json";
import isDevelopmentInjectable from "../../../common/vars/is-development.injectable";
const appNameInjectable = getInjectable({
id: "app-name",
instantiate: (di) => di.inject(electronAppInjectable).name,
instantiate: (di) => {
const isDevelopment = di.inject(isDevelopmentInjectable);
return `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
},
causesSideEffects: true,
});
export default appNameInjectable;

View File

@ -1,71 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type {
DiContainerForSetup } from "@ogre-tools/injectable";
import {
getInjectable,
} from "@ogre-tools/injectable";
import {
appPathsInjectionToken,
appPathsIpcChannel,
} from "../../common/app-paths/app-path-injection-token";
import registerChannelInjectable from "./register-channel/register-channel.injectable";
import { getAppPaths } from "./get-app-paths";
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
import appNameInjectable from "./app-name/app-name.injectable";
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
const appPathsInjectable = getInjectable({
id: "app-paths",
setup: async (di) => {
const directoryForIntegrationTesting = await di.inject(
directoryForIntegrationTestingInjectable,
);
if (directoryForIntegrationTesting) {
await setupPathForAppDataInIntegrationTesting(di, directoryForIntegrationTesting);
}
await setupPathForUserData(di);
await registerAppPathsChannel(di);
},
instantiate: (di) =>
getAppPaths({ getAppPath: di.inject(getElectronAppPathInjectable) }),
injectionToken: appPathsInjectionToken,
});
export default appPathsInjectable;
const registerAppPathsChannel = async (di: DiContainerForSetup) => {
const registerChannel = await di.inject(registerChannelInjectable);
const appPaths = await di.inject(appPathsInjectable);
registerChannel(appPathsIpcChannel, () => appPaths);
};
const setupPathForUserData = async (di: DiContainerForSetup) => {
const setElectronAppPath = await di.inject(setElectronAppPathInjectable);
const appName = await di.inject(appNameInjectable);
const getAppPath = await di.inject(getElectronAppPathInjectable);
const joinPaths = await di.inject(joinPathsInjectable);
const appDataPath = getAppPath("appData");
setElectronAppPath("userData", joinPaths(appDataPath, appName));
};
// Todo: this kludge is here only until we have a proper place to setup integration testing.
const setupPathForAppDataInIntegrationTesting = async (di: DiContainerForSetup, appDataPath: string) => {
const setElectronAppPath = await di.inject(setElectronAppPathInjectable);
setElectronAppPath("appData", appDataPath);
};

View File

@ -3,10 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import environmentVariablesInjectable from "../../../common/utils/environment-variables.injectable";
const directoryForIntegrationTestingInjectable = getInjectable({
id: "directory-for-integration-testing",
instantiate: () => process.env.CICD,
instantiate: (di) => {
const environmentVariables = di.inject(environmentVariablesInjectable);
return environmentVariables.CICD;
},
});
export default directoryForIntegrationTestingInjectable;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import electronAppInjectable from "./electron-app/electron-app.injectable";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
import { getElectronAppPath } from "./get-electron-app-path";
const getElectronAppPathInjectable = getInjectable({

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import electronAppInjectable from "./electron-app/electron-app.injectable";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { App } from "electron";
@ -13,7 +13,7 @@ import { joinPathsFake } from "../../../common/test-utils/join-paths-fake";
describe("get-electron-app-path", () => {
let getElectronAppPath: (name: string) => string;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: false });
const appStub = {
@ -35,8 +35,6 @@ describe("get-electron-app-path", () => {
di.override(registerChannelInjectable, () => () => undefined);
di.override(joinPathsInjectable, () => joinPathsFake);
await di.runSetups();
getElectronAppPath = di.inject(getElectronAppPathInjectable) as (name: string) => string;
});

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { PathName } from "../../../common/app-paths/app-path-names";
import electronAppInjectable from "../get-electron-app-path/electron-app/electron-app.injectable";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
const setElectronAppPathInjectable = getInjectable({
id: "set-electron-app-path",

View File

@ -0,0 +1,58 @@
/**
* 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 { AppPaths } from "../../common/app-paths/app-path-injection-token";
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
import appNameInjectable from "./app-name/app-name.injectable";
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import { pathNames } from "../../common/app-paths/app-path-names";
import { fromPairs, map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import { appPathsIpcChannel } from "../../common/app-paths/app-path-injection-token";
import registerChannelInjectable from "./register-channel/register-channel.injectable";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
import { beforeElectronIsReadyInjectionToken } from "../start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
const setupAppPathsInjectable = getInjectable({
id: "setup-app-paths",
instantiate: (di) => {
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
const appName = di.inject(appNameInjectable);
const getAppPath = di.inject(getElectronAppPathInjectable);
const appPathsState = di.inject(appPathsStateInjectable);
const registerChannel = di.inject(registerChannelInjectable);
const directoryForIntegrationTesting = di.inject(directoryForIntegrationTestingInjectable);
const joinPaths = di.inject(joinPathsInjectable);
return {
run: () => {
if (directoryForIntegrationTesting) {
setElectronAppPath("appData", directoryForIntegrationTesting);
}
const appDataPath = getAppPath("appData");
setElectronAppPath("userData", joinPaths(appDataPath, appName));
const appPaths = pipeline(
pathNames,
map(name => [name, getAppPath(name)]),
fromPairs,
) as AppPaths;
appPathsState.set(appPaths);
registerChannel(appPathsIpcChannel, () => appPaths);
},
};
},
injectionToken: beforeElectronIsReadyInjectionToken,
});
export default setupAppPathsInjectable;

View File

@ -41,11 +41,15 @@ autoUpdater.logger = {
debug: message => logger.debug(`[AUTO-UPDATE]: electron-updater: %s`, message),
};
interface Dependencies {
isAutoUpdateEnabled: () => boolean;
}
/**
* starts the automatic update checking
* @param interval milliseconds between interval to check on, defaults to 2h
*/
export const startUpdateChecking = once(function (interval = 1000 * 60 * 60 * 2): void {
export const startUpdateChecking = ({ isAutoUpdateEnabled } : Dependencies) => once(function (interval = 1000 * 60 * 60 * 2): void {
if (!isAutoUpdateEnabled() || isTestEnv) {
return;
}

View File

@ -10,16 +10,16 @@ import type { Cluster } from "../../../common/cluster/cluster";
import { computeDiff as computeDiffFor, configToModels } from "../kubeconfig-sync/manager";
import mockFs from "mock-fs";
import fs from "fs";
import { ClusterManager } from "../../cluster-manager";
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import getConfigurationFileModelInjectable
from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable
from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import clusterManagerInjectable from "../../cluster-manager.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
jest.mock("electron", () => ({
app: {
@ -45,12 +45,8 @@ describe("kubeconfig-sync.source tests", () => {
mockFs();
await di.runSetups();
computeDiff = computeDiffFor({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken),
});
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(clusterStoreInjectable, () =>
ClusterStore.createInstance({ createCluster: () => null as never }),
@ -59,14 +55,17 @@ describe("kubeconfig-sync.source tests", () => {
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
di.inject(clusterStoreInjectable);
computeDiff = computeDiffFor({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
});
ClusterManager.createInstance();
di.inject(clusterStoreInjectable);
});
afterEach(() => {
mockFs.restore();
ClusterManager.resetInstance();
ClusterStore.resetInstance();
});

View File

@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import { KubeconfigSyncManager } from "./manager";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import clusterManagerInjectable from "../../cluster-manager.injectable";
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
const kubeconfigSyncManagerInjectable = getInjectable({
@ -14,6 +15,7 @@ const kubeconfigSyncManagerInjectable = getInjectable({
instantiate: (di) => new KubeconfigSyncManager({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}),
});

View File

@ -17,7 +17,8 @@ import { bytesToUnits, getOrInsertWith, iter, noop } from "../../../common/utils
import logger from "../../logger";
import type { KubeConfig } from "@kubernetes/client-node";
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
import { catalogEntityFromCluster, ClusterManager } from "../../cluster-manager";
import type { ClusterManager } from "../../cluster-manager";
import { catalogEntityFromCluster } from "../../cluster-manager";
import { UserStore } from "../../../common/user-store";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { createHash } from "crypto";
@ -51,9 +52,10 @@ const ignoreGlobs = [
const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
interface Dependencies {
interface KubeconfigSyncManagerDependencies {
readonly directoryForKubeConfigs: string;
readonly entityRegistry: CatalogEntityRegistry;
readonly clusterManager: ClusterManager;
createCluster: (model: ClusterModel) => Cluster;
}
@ -64,7 +66,7 @@ export class KubeconfigSyncManager {
protected syncing = false;
protected syncListDisposer?: Disposer;
constructor(protected readonly dependencies: Dependencies) {
constructor(protected readonly dependencies: KubeconfigSyncManagerDependencies) {
makeObservable(this);
}
@ -165,8 +167,14 @@ export function configToModels(rootConfig: KubeConfig, filePath: string): Update
type RootSourceValue = [Cluster, CatalogEntity];
type RootSource = ObservableMap<string, RootSourceValue>;
interface ComputeDiffDependencies {
directoryForKubeConfigs: string;
createCluster: (model: ClusterModel) => Cluster;
clusterManager: ClusterManager;
}
// exported for testing
export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Pick<Dependencies, "createCluster" | "directoryForKubeConfigs">) => (contents: string, source: RootSource, filePath: string): void => {
export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterManager }: ComputeDiffDependencies) => (contents: string, source: RootSource, filePath: string): void => {
runInAction(() => {
try {
const { config, error } = loadConfigFromString(contents);
@ -186,7 +194,7 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Pick<Dep
// remove and disconnect clusters that were removed from the config
if (!model) {
// remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting
ClusterManager.getInstance().deleting.delete(value[0].id);
clusterManager.deleting.delete(value[0].id);
value[0].disconnect();
source.delete(contextName);
@ -241,7 +249,7 @@ interface DiffChangedConfigArgs {
maxAllowedFileReadSize: number;
}
const diffChangedConfigFor = (dependencies: Dependencies) => ({ filePath, source, stats, maxAllowedFileReadSize }: DiffChangedConfigArgs): Disposer => {
const diffChangedConfigFor = (dependencies: ComputeDiffDependencies) => ({ filePath, source, stats, maxAllowedFileReadSize }: DiffChangedConfigArgs): Disposer => {
logger.debug(`${logPrefix} file changed`, { filePath });
if (stats.size >= maxAllowedFileReadSize) {
@ -297,7 +305,7 @@ const diffChangedConfigFor = (dependencies: Dependencies) => ({ filePath, source
return cleanup;
};
const watchFileChanges = (filePath: string, dependencies: Dependencies): [IComputedValue<CatalogEntity[]>, Disposer] => {
const watchFileChanges = (filePath: string, dependencies: ComputeDiffDependencies): [IComputedValue<CatalogEntity[]>, Disposer] => {
const rootSource = observable.map<string, ObservableMap<string, RootSourceValue>>();
const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1]))));

View File

@ -0,0 +1,19 @@
/**
* 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 { syncWeblinks } from "./weblinks";
import weblinkStoreInjectable from "../../common/weblink-store.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const syncWeblinksInjectable = getInjectable({
id: "sync-weblinks",
instantiate: (di) => syncWeblinks({
weblinkStore: di.inject(weblinkStoreInjectable),
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
}),
});
export default syncWeblinksInjectable;

View File

@ -4,9 +4,9 @@
*/
import { computed, observable, reaction } from "mobx";
import { WeblinkStore } from "../../common/weblink-store";
import type { WeblinkStore } from "../../common/weblink-store";
import { WebLink } from "../../common/catalog-entities";
import { catalogEntityRegistry } from "../catalog";
import type { CatalogEntityRegistry } from "../catalog";
import got from "got";
import type { Disposer } from "../../common/utils";
import { random } from "lodash";
@ -28,9 +28,12 @@ async function validateLink(link: WebLink) {
}
}
interface Dependencies {
weblinkStore: WeblinkStore;
catalogEntityRegistry: CatalogEntityRegistry;
}
export function syncWeblinks() {
const weblinkStore = WeblinkStore.getInstance();
export const syncWeblinks = ({ weblinkStore, catalogEntityRegistry }: Dependencies) => () => {
const webLinkEntities = observable.map<string, [WebLink, Disposer]>();
function periodicallyCheckLink(link: WebLink): Disposer {
@ -87,4 +90,4 @@ export function syncWeblinks() {
}, { fireImmediately: true });
catalogEntityRegistry.addComputedSource("weblinks", computed(() => Array.from(webLinkEntities.values(), ([link]) => link)));
}
};

View File

@ -0,0 +1,22 @@
/**
* 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 { startCatalogSyncToRenderer } from "../catalog-pusher";
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const catalogSyncToRendererInjectable = getInjectable({
id: "catalog-sync-to-renderer",
instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
return getStartableStoppable("catalog-sync", () =>
startCatalogSyncToRenderer(catalogEntityRegistry),
);
},
});
export default catalogSyncToRendererInjectable;

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { afterRootFrameIsReadyInjectionToken } from "../start-main-application/runnable-tokens/after-root-frame-is-ready-injection-token";
import catalogSyncToRendererInjectable from "./catalog-sync-to-renderer.injectable";
const startCatalogSyncInjectable = getInjectable({
id: "start-catalog-sync",
instantiate: (di) => {
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
return {
run: async () => {
await catalogSyncToRenderer.start();
},
};
},
injectionToken: afterRootFrameIsReadyInjectionToken,
});
export default startCatalogSyncInjectable;

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogSyncToRendererInjectable from "./catalog-sync-to-renderer.injectable";
import { beforeQuitOfFrontEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-front-end-injection-token";
const stopCatalogSyncInjectable = getInjectable({
id: "stop-catalog-sync",
instantiate: (di) => {
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
return {
run: async () => {
if (catalogSyncToRenderer.started) {
await catalogSyncToRenderer.stop();
}
},
};
},
injectionToken: beforeQuitOfFrontEndInjectionToken,
});
export default stopCatalogSyncInjectable;

View File

@ -8,6 +8,7 @@ import { CatalogEntityRegistry } from "./entity-registry";
const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry",
instantiate: (di) => new CatalogEntityRegistry({
hasCategoryForEntity: di.inject(hasCategoryForEntityInjectable),
}),

View File

@ -5,7 +5,7 @@
import type { RequestPromiseOptions } from "request-promise-native";
import type { Cluster } from "../../common/cluster/cluster";
import { k8sRequest } from "../k8s-request";
import type { K8sRequest } from "../k8s-request.injectable";
export interface ClusterDetectionResult {
value: string | number | boolean;
@ -15,11 +15,11 @@ export interface ClusterDetectionResult {
export abstract class BaseClusterDetector {
abstract readonly key: string;
constructor(public readonly cluster: Cluster) {}
constructor(public readonly cluster: Cluster, private _k8sRequest: K8sRequest) {}
abstract detect(): Promise<ClusterDetectionResult | null>;
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
return k8sRequest(this.cluster, path, options);
return this._k8sRequest(this.cluster, path, options);
}
}

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { VersionDetector } from "./version-detector";
import k8sRequestInjectable from "../k8s-request.injectable";
import type { Cluster } from "../../common/cluster/cluster";
const createVersionDetectorInjectable = getInjectable({
id: "create-version-detector",
instantiate: (di) => {
const k8sRequest = di.inject(k8sRequestInjectable);
return (cluster: Cluster) =>
new VersionDetector(cluster, k8sRequest);
},
});
export default createVersionDetectorInjectable;

View File

@ -0,0 +1,16 @@
/**
* 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 { DetectorRegistry } from "./detector-registry";
import k8sRequestInjectable from "../k8s-request.injectable";
const detectorRegistryInjectable = getInjectable({
id: "detector-registry",
instantiate: (di) =>
new DetectorRegistry({ k8sRequest: di.inject(k8sRequestInjectable) }),
});
export default detectorRegistryInjectable;

View File

@ -5,13 +5,19 @@
import { observable } from "mobx";
import type { ClusterMetadata } from "../../common/cluster-types";
import { Singleton } from "../../common/utils";
import type { Cluster } from "../../common/cluster/cluster";
import type { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector";
import type { K8sRequest } from "../k8s-request.injectable";
export type DetectorConstructor = new (cluster: Cluster) => BaseClusterDetector;
interface Dependencies {
k8sRequest: K8sRequest;
}
export type DetectorConstructor = new (cluster: Cluster, k8sRequest: K8sRequest) => BaseClusterDetector;
export class DetectorRegistry {
constructor(private dependencies: Dependencies) {}
export class DetectorRegistry extends Singleton {
registry = observable.array<DetectorConstructor>([], { deep: false });
add(detectorClass: DetectorConstructor): this {
@ -24,7 +30,7 @@ export class DetectorRegistry extends Singleton {
const results: { [key: string]: ClusterDetectionResult } = {};
for (const detectorClass of this.registry) {
const detector = new detectorClass(cluster);
const detector = new detectorClass(cluster, this.dependencies.k8sRequest);
try {
const data = await detector.detect();

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ClusterManager } from "./cluster-manager";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
import catalogEntityRegistryInjectable from "./catalog/entity-registry.injectable";
const clusterManagerInjectable = getInjectable({
id: "cluster-manager",
instantiate: (di) => {
const clusterManager = new ClusterManager({
store: di.inject(clusterStoreInjectable),
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
});
clusterManager.init();
return clusterManager;
},
});
export default clusterManagerInjectable;

View File

@ -9,52 +9,55 @@ import { action, makeObservable, observable, observe, reaction, toJS } from "mob
import type { Cluster } from "../common/cluster/cluster";
import logger from "./logger";
import { apiKubePrefix } from "../common/vars";
import { getClusterIdFromHost, isErrnoException, Singleton } from "../common/utils";
import { catalogEntityRegistry } from "./catalog";
import { getClusterIdFromHost, isErrnoException } from "../common/utils";
import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster";
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster";
import { ipcMainOn } from "../common/ipc";
import { once } from "lodash";
import { ClusterStore } from "../common/cluster-store/cluster-store";
import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { ClusterId } from "../common/cluster-types";
import type { CatalogEntityRegistry } from "./catalog";
const logPrefix = "[CLUSTER-MANAGER]:";
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
export class ClusterManager extends Singleton {
private store = ClusterStore.getInstance();
interface Dependencies {
store: ClusterStore;
catalogEntityRegistry: CatalogEntityRegistry;
}
export class ClusterManager {
deleting = observable.set<ClusterId>();
@observable visibleCluster: ClusterId | undefined = undefined;
constructor() {
super();
constructor(private dependencies: Dependencies) {
makeObservable(this);
}
init = once(() => {
// reacting to every cluster's state change and total amount of items
reaction(
() => this.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.store.clustersList),
() => this.dependencies.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false },
);
// reacting to every cluster's preferences change and total amount of items
reaction(
() => this.store.clustersList.map(c => toJS(c.preferences)),
() => this.updateCatalog(this.store.clustersList),
() => this.dependencies.store.clustersList.map(c => toJS(c.preferences)),
() => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false },
);
reaction(
() => catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
() => this.dependencies.catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
entities => this.syncClustersFromCatalog(entities),
);
reaction(() => [
catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
this.dependencies.catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
this.visibleCluster,
] as const, ([entities, visibleCluster]) => {
for (const entity of entities) {
@ -68,7 +71,7 @@ export class ClusterManager extends Singleton {
observe(this.deleting, change => {
if (change.type === "add") {
this.updateEntityStatus(catalogEntityRegistry.findById(change.newValue) as KubernetesCluster);
this.updateEntityStatus(this.dependencies.catalogEntityRegistry.findById(change.newValue) as KubernetesCluster);
}
});
@ -86,13 +89,13 @@ export class ClusterManager extends Singleton {
}
protected updateEntityFromCluster(cluster: Cluster) {
const index = catalogEntityRegistry.items.findIndex((entity) => entity.getId() === cluster.id);
const index = this.dependencies.catalogEntityRegistry.items.findIndex((entity) => entity.getId() === cluster.id);
if (index === -1) {
return;
}
const entity = catalogEntityRegistry.items[index] as KubernetesCluster;
const entity = this.dependencies.catalogEntityRegistry.items[index] as KubernetesCluster;
this.updateEntityStatus(entity, cluster);
@ -133,7 +136,7 @@ export class ClusterManager extends Singleton {
cluster.preferences.icon = undefined;
}
catalogEntityRegistry.items.splice(index, 1, entity);
this.dependencies.catalogEntityRegistry.items.splice(index, 1, entity);
}
@action
@ -180,7 +183,7 @@ export class ClusterManager extends Singleton {
@action
protected syncClustersFromCatalog(entities: KubernetesCluster[]) {
for (const entity of entities) {
const cluster = this.store.getById(entity.getId());
const cluster = this.dependencies.store.getById(entity.getId());
if (!cluster) {
const model = {
@ -195,7 +198,7 @@ export class ClusterManager extends Singleton {
* Add the bare minimum of data to ClusterStore. And especially no
* preferences, as those might be configured by the entity's source
*/
this.store.addCluster(model);
this.dependencies.store.addCluster(model);
} catch (error) {
if (isErrnoException(error) && error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) {
logger.warn(`${logPrefix} kubeconfig file disappeared`, model);
@ -234,7 +237,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOffline = () => {
logger.info(`${logPrefix} network is offline`);
this.store.clustersList.forEach((cluster) => {
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.online = false;
cluster.accessible = false;
@ -245,7 +248,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOnline = () => {
logger.info(`${logPrefix} network is online`);
this.store.clustersList.forEach((cluster) => {
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e);
}
@ -253,12 +256,12 @@ export class ClusterManager extends Singleton {
};
stop() {
this.store.clusters.forEach((cluster: Cluster) => {
this.dependencies.store.clusters.forEach((cluster: Cluster) => {
cluster.disconnect();
});
}
getClusterForRequest(req: http.IncomingMessage): Cluster | undefined {
getClusterForRequest = (req: http.IncomingMessage): Cluster | undefined => {
if (!req.headers.host) {
return undefined;
}
@ -266,7 +269,7 @@ export class ClusterManager extends Singleton {
// lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.url && req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1];
const cluster = this.store.getById(clusterId);
const cluster = this.dependencies.store.getById(clusterId);
if (cluster) {
// we need to swap path prefix so that request is proxied to kube api
@ -276,8 +279,8 @@ export class ClusterManager extends Singleton {
return cluster;
}
return this.store.getById(getClusterIdFromHost(req.headers.host));
}
return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host));
};
}
export function catalogEntityFromCluster(cluster: Cluster) {

Some files were not shown because too many files have changed in this diff Show More