mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Refactor and transform "Application update", "Preferences" and "Application menu" into Features (#6437)
* Move some code for application update to feature directory The rest of the code could not be moved yet because of work-in-progress refactorings for OCP compliance. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce helper to get a global override for a function-injectable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract global override-files from bloated getDiForUnitTesting Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move some more code for application update to feature directory Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce competition for top bar items to achieve OCP-compliance Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for opening context menu using the new competition Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to home as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to back as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to forward as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for application update as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Format code to make ongoing refactoring a bit easier Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add missing unit tests for top bar extendability using extension API This makes ongoing refactoring easier. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace implementation for old top-bar items for losing competition Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for window controls as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce reusable component for spacing between other components Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce helper component to render list of React elements Features: - Placeholder for empty list - Separators between items - No boilerplate for "key" prop in React Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix double-clicking and dragging of window from top bar Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix stuff broken in rebase Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots after rebase Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make fake time have a default value for "now" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate some application menu items to injectables Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate separators of application menu in single file Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove duplication from separators in application menu Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract some operation system actions from application menu as injectables Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract menu item for quitting application as injectable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce way to type narrow a string property Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make the di's for unit testing able to auto-register also named exports Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make global override less strict to simplify setup of many unit tests There's a better solution for this in the horizon, as this overridden thing is better faked than stubbed. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify a test Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce way to create hierarchical composites from a flat array Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Implement hierarchy of application menu items using "many-root" composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate more application menu items to injectables Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify hierarchy of application menu items using "single-root" composite Also solve composed typing of application menu by using Discriminated Unions of TypeScript, see: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshot Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify creation of composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite able to have custom handler for missing parent ids This will be useful next for application menu items, where a missing parent id cannot be fatal. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Defend against self-referencing composites Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Reintroduce non-fatal handling of orphan application menu items Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make faked, yet weak, typing a bit stronger Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify getting of composite paths Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make registrator for application menu items support all known scenarios Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add logging for unrecognizable application menu item types Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Rename id of menu item to keep it discoverable by existing extensions Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate code to check for updates closer to feature Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce reusable horizontal line Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for preferences as a Feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for terminal preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for editor preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for proxy preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for telemetry preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for application preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code related to helm chart preferences under related Feature in preparation for competition Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for kubernetes preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for application preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for editor preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for kubernetes preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for proxy preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing observer to make sure component updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for telemetry preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for terminal preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak UI for preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more UI of preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert "Introduce reusable horizontal line" This reverts commit 4d8c147fe0f1a14bd884f73cf345e7d3a28b954a. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce utility to find exactly one item from array Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for preferences navigation Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a Feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove usage of old code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add extensions tab group to preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce way to find out if composite has a descendant This will serve eg. hiding of empty preference tab groups. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make tab groups and tabs in preferences not render when there is no content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove code made redundant with hiding of preference tabs without content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Tweak UI for preference navigation tab groups Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce test helper to abstract discovery of HTML elements Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate discovery of HTML elements in some tests Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt application builder to changes in preference navigation Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt test setup to changes in preference navigation Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove uninteresting technical tests that are covered by behavioural ones Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate discovery of HTML elements in a test Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove test ID made redundant by consolidating discovery of HTML elements Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication from preference pages Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix import Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make queries in element discovery return matching attribute values for easier testing Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make element discovery able to do nested discovery Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make element discovery able to discover without value for attribute Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Implement registrator for preference items Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make styling less brittle by not relying on static HTML-element structures with CSS-rules Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add todo Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove "group" from preference types, as it is exact replica of "item" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace usages of react-component factory with actual components for simplicity Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Start considering application preferences as default tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make sense in horizontal lines Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Clean public interface of a normalize composite Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate name of function Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate directory structure of composite Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move utility functions to common Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename a preference item type to better communicate intent in UI Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move shared UI component to more common place Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate sizing of horizontal line to t-shirts Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move application update related preferences under application update feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make winner of competition to use original route for preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make HTML element discovery require less parameters Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract "composable-responsibilities" for Discriminable, Labelable, Orderable, and Showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshot Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate navigating to preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate code under a sub-feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix merge conflicts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix import path after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication from exhaustiveness checks for discriminating unions Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update link to a more recent article Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify static Showability of a PreferenceItem Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move general function to general directory Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract responsibility of "separability" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make separator in Map-component know left and right item Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove additional separators when separated items are not shown for having no content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix lint error Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt integration test to recent changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt more integration tests to recent changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make composite not care about in formatting of ids Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt application builder and tests to array-like paths over string-like paths Array-like paths do not have weakness for special characters as part of id, such as ".". Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite not care about formatting of ids Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt application builder and tests to array-like paths over string-like paths Array-like paths do not have weakness for special characters as part of id, such as ".". Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate output of get-composite-path to match find-composite's input Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make global overrides for functions log args of the call for devability Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make attempts to log error throw in unit tests Errors cannot be allowed to happen without a unit test explicitly causing it. Errors cannot be allowed to happen without author of unit test knowing it. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite not know about how children are transformed Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove some duplication from tests of composite Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite unit test an unrealistic test scenario about undefined ids Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Kill dead code Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify unit tests for composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate tests to now point-free composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify "Showable" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make non-shown application menu items not break composite structure This was made apparent by adding related unit tests for all known environments. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify usages of Orderable and Showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract being maybe Showable as explicit composable responsibility Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make more showables maybe showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate logic for application menu for Windows to be based on composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Start using named export for composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Rename type for accuracy Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove unnecessary type and value Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composable responsibilities readonly to nudge towards immutability Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove a bit of duplication to create TS-constants Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a comment and test name make more sense Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify fallthrough in a switch/case Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace inline styles with proper CSS Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Switch to correct type to indicate "object which might not contain a property" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify overriding of platform in a unit test Also make typing of platforms more strict, and remove some magic strings. Also add a TODO for further OCP-ification. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate some "maybe-types" and arguments using them Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
38feab813c
commit
37eb236948
418 files changed
+39647
-27024
No files matched your search
@@ -34,7 +34,7 @@ import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
||||
import defaultUpdateChannelInjectable from "../application-update/selected-update-channel/default-update-channel.injectable";
|
||||
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
|
||||
import type { UpdateChannel } from "../update-channels";
|
||||
import type { SyncBox } from "../../utils/sync-box/sync-box-injection-token";
|
||||
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
|
||||
|
||||
export type DiscoveredUpdateVersion = SyncBox<{ version: string; updateChannel: UpdateChannel } | null>;
|
||||
|
||||
const discoveredUpdateVersionInjectable = getInjectable({
|
||||
id: "discovered-update-version",
|
||||
|
||||
instantiate: (di) => {
|
||||
const createSyncBox = di.inject(createSyncBoxInjectable);
|
||||
|
||||
return createSyncBox(
|
||||
"discovered-update-version",
|
||||
null,
|
||||
) as DiscoveredUpdateVersion;
|
||||
},
|
||||
|
||||
injectionToken: syncBoxInjectionToken,
|
||||
});
|
||||
|
||||
export default discoveredUpdateVersionInjectable;
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
|
||||
import type { SyncBox } from "../../utils/sync-box/sync-box-injection-token";
|
||||
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
|
||||
|
||||
export interface ProgressOfDownload {
|
||||
percentage: number;
|
||||
failed?: string;
|
||||
}
|
||||
|
||||
export type ProgressOfUpdateDownload = SyncBox<ProgressOfDownload>;
|
||||
|
||||
const progressOfUpdateDownloadInjectable = getInjectable({
|
||||
id: "progress-of-update-download-state",
|
||||
|
||||
instantiate: (di) => {
|
||||
const createSyncBox = di.inject(createSyncBoxInjectable);
|
||||
|
||||
return createSyncBox<ProgressOfDownload>("progress-of-update-download", { percentage: 0 });
|
||||
},
|
||||
|
||||
injectionToken: syncBoxInjectionToken,
|
||||
});
|
||||
|
||||
export default progressOfUpdateDownloadInjectable;
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
export type RestartAndInstallUpdateChannel = MessageChannel<void>;
|
||||
|
||||
export const restartAndInstallUpdateChannel: RestartAndInstallUpdateChannel = {
|
||||
id: "restart-and-install-update-channel",
|
||||
};
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { createInitializableState } from "../../initializable-state/create";
|
||||
import releaseChannelInjectable from "../../vars/release-channel.injectable";
|
||||
import { updateChannels } from "../update-channels";
|
||||
|
||||
const defaultUpdateChannelInjectable = createInitializableState({
|
||||
id: "default-update-channel",
|
||||
init: (di) => updateChannels[di.inject(releaseChannelInjectable).get()],
|
||||
});
|
||||
|
||||
export default defaultUpdateChannelInjectable;
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { action, computed, observable } from "mobx";
|
||||
import type { UpdateChannel, ReleaseChannel } from "../update-channels";
|
||||
import { updateChannels } from "../update-channels";
|
||||
import defaultUpdateChannelInjectable from "./default-update-channel.injectable";
|
||||
|
||||
export interface SelectedUpdateChannel {
|
||||
value: IComputedValue<UpdateChannel>;
|
||||
setValue: (channelId?: ReleaseChannel) => void;
|
||||
}
|
||||
|
||||
const selectedUpdateChannelInjectable = getInjectable({
|
||||
id: "selected-update-channel",
|
||||
|
||||
instantiate: (di): SelectedUpdateChannel => {
|
||||
const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable);
|
||||
const state = observable.box<UpdateChannel>(undefined, { deep: false });
|
||||
|
||||
return {
|
||||
value: computed(() => state.get() ?? defaultUpdateChannel.get()),
|
||||
|
||||
setValue: action((channelId) => {
|
||||
const targetUpdateChannel =
|
||||
channelId && updateChannels[channelId]
|
||||
? updateChannels[channelId]
|
||||
: defaultUpdateChannel.get();
|
||||
|
||||
state.set(targetUpdateChannel);
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default selectedUpdateChannelInjectable;
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
|
||||
export type ReleaseChannel = "alpha" | "beta" | "latest";
|
||||
|
||||
const latestChannel: UpdateChannel = {
|
||||
id: "latest",
|
||||
label: "Stable",
|
||||
moreStableUpdateChannel: null,
|
||||
};
|
||||
|
||||
const betaChannel: UpdateChannel = {
|
||||
id: "beta",
|
||||
label: "Beta",
|
||||
moreStableUpdateChannel: latestChannel,
|
||||
};
|
||||
|
||||
const alphaChannel: UpdateChannel = {
|
||||
id: "alpha",
|
||||
label: "Alpha",
|
||||
moreStableUpdateChannel: betaChannel,
|
||||
};
|
||||
|
||||
export const updateChannels = {
|
||||
latest: latestChannel,
|
||||
beta: betaChannel,
|
||||
alpha: alphaChannel,
|
||||
};
|
||||
|
||||
export interface UpdateChannel {
|
||||
readonly id: ReleaseChannel;
|
||||
readonly label: string;
|
||||
readonly moreStableUpdateChannel: UpdateChannel | null;
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
|
||||
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
|
||||
|
||||
const updateDownloadedDateTimeInjectable = getInjectable({
|
||||
id: "update-downloaded-date-time",
|
||||
|
||||
instantiate: (di) => {
|
||||
const createSyncBox = di.inject(createSyncBoxInjectable);
|
||||
|
||||
return createSyncBox<string | null>(
|
||||
"update-downloaded-date-time",
|
||||
null,
|
||||
);
|
||||
},
|
||||
|
||||
injectionToken: syncBoxInjectionToken,
|
||||
});
|
||||
|
||||
export default updateDownloadedDateTimeInjectable;
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
|
||||
import type { SyncBox } from "../../utils/sync-box/sync-box-injection-token";
|
||||
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
|
||||
|
||||
export type UpdateIsBeingDownloaded = SyncBox<boolean>;
|
||||
|
||||
const updateIsBeingDownloadedInjectable = getInjectable({
|
||||
id: "update-is-being-downloaded",
|
||||
|
||||
instantiate: (di) => {
|
||||
const createSyncBox = di.inject(createSyncBoxInjectable);
|
||||
|
||||
return createSyncBox("update-is-being-downloaded", false);
|
||||
},
|
||||
|
||||
injectionToken: syncBoxInjectionToken,
|
||||
});
|
||||
|
||||
export default updateIsBeingDownloadedInjectable;
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createSyncBoxInjectable from "../../utils/sync-box/create-sync-box.injectable";
|
||||
import type { SyncBox } from "../../utils/sync-box/sync-box-injection-token";
|
||||
import { syncBoxInjectionToken } from "../../utils/sync-box/sync-box-injection-token";
|
||||
|
||||
export type UpdatesAreBeingDiscovered = SyncBox<boolean>;
|
||||
|
||||
const updatesAreBeingDiscoveredInjectable = getInjectable({
|
||||
id: "updates-are-being-discovered",
|
||||
|
||||
instantiate: (di) => {
|
||||
const createSyncBox = di.inject(createSyncBoxInjectable);
|
||||
|
||||
return createSyncBox("updates-are-being-discovered", false);
|
||||
},
|
||||
|
||||
injectionToken: syncBoxInjectionToken,
|
||||
});
|
||||
|
||||
export default updatesAreBeingDiscoveredInjectable;
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token";
|
||||
import { GeneralEntity } from "../../index";
|
||||
import { buildURL } from "../../../utils/buildUrl";
|
||||
import appPreferencesRouteInjectable from "../../../front-end-routing/routes/preferences/app/app-preferences-route.injectable";
|
||||
|
||||
const preferencesCatalogEntityInjectable = getInjectable({
|
||||
id: "general-catalog-entity-for-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const route = di.inject(appPreferencesRouteInjectable);
|
||||
const url = buildURL(route.path);
|
||||
|
||||
return new GeneralEntity({
|
||||
metadata: {
|
||||
uid: "preferences-entity",
|
||||
name: "Preferences",
|
||||
source: "app",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
path: url,
|
||||
icon: {
|
||||
material: "settings",
|
||||
background: "#3d90ce",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "active",
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
injectionToken: generalCatalogEntityInjectionToken,
|
||||
});
|
||||
|
||||
export default preferencesCatalogEntityInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const appPreferencesRouteInjectable = getInjectable({
|
||||
id: "app-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/app",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default appPreferencesRouteInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import appPreferencesRouteInjectable from "./app-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToAppPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-app-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(appPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToAppPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const editorPreferencesRouteInjectable = getInjectable({
|
||||
id: "editor-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/editor",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default editorPreferencesRouteInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import editorPreferencesRouteInjectable from "./editor-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToEditorPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-editor-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(editorPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToEditorPreferencesInjectable;
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
import type { Route } from "../../../front-end-route-injection-token";
|
||||
|
||||
interface ExtensionPreferenceRouteParams {
|
||||
extensionId: string;
|
||||
tabId?: string;
|
||||
}
|
||||
|
||||
const extensionPreferencesRouteInjectable = getInjectable({
|
||||
id: "extension-preferences-route",
|
||||
|
||||
instantiate: (): Route<ExtensionPreferenceRouteParams> => ({
|
||||
path: "/preferences/extension/:extensionId/:tabId?",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default extensionPreferencesRouteInjectable;
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import extensionPreferencesRouteInjectable from "./extension-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToExtensionPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-extension-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(extensionPreferencesRouteInjectable);
|
||||
|
||||
return (extensionId: string, tabId?: string) => navigateToRoute(route, {
|
||||
parameters: {
|
||||
extensionId,
|
||||
tabId,
|
||||
},
|
||||
withoutAffectingBackButton: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToExtensionPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const kubernetesPreferencesRouteInjectable = getInjectable({
|
||||
id: "kubernetes-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/kubernetes",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default kubernetesPreferencesRouteInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import kubernetesPreferencesRouteInjectable from "./kubernetes-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToKubernetesPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-kubernetes-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(kubernetesPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToKubernetesPreferencesInjectable;
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import navigateToAppPreferencesInjectable from "./app/navigate-to-app-preferences.injectable";
|
||||
|
||||
const navigateToPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-preferences",
|
||||
|
||||
instantiate: (di) => di.inject(navigateToAppPreferencesInjectable),
|
||||
});
|
||||
|
||||
export default navigateToPreferencesInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import proxyPreferencesRouteInjectable from "./proxy-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToProxyPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-proxy-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(proxyPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToProxyPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const proxyPreferencesRouteInjectable = getInjectable({
|
||||
id: "proxy-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/proxy",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default proxyPreferencesRouteInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import telemetryPreferencesRouteInjectable from "./telemetry-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToTelemetryPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-telemetry-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(telemetryPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToTelemetryPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const telemetryPreferencesRouteInjectable = getInjectable({
|
||||
id: "telemetry-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/telemetry",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default telemetryPreferencesRouteInjectable;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import terminalPreferencesRouteInjectable from "./terminal-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToTerminalPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-terminal-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(terminalPreferencesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToTerminalPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const terminalPreferencesRouteInjectable = getInjectable({
|
||||
id: "terminal-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/terminal",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default terminalPreferencesRouteInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverrideForFunction } from "./test-utils/get-global-override-for-function";
|
||||
import logErrorInjectable from "./log-error.injectable";
|
||||
|
||||
// Note: this should remain as it is, and throw if called. Logging error is something
|
||||
// that cannot happen without a unit test explicitly causing it. It cannot be allowed
|
||||
// to happen without author of unit test knowing it.
|
||||
export default getGlobalOverrideForFunction(logErrorInjectable);
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getGlobalOverride } from "./get-global-override";
|
||||
import { camelCase } from "lodash/fp";
|
||||
|
||||
export const getGlobalOverrideForFunction = (
|
||||
injectable: Injectable<Function, any, any>,
|
||||
) =>
|
||||
getGlobalOverride(injectable, () => (...args: any[]) => {
|
||||
console.warn(
|
||||
`Tried to invoke a function "${injectable.id}" without override. The args were:`,
|
||||
args,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Tried to invoke a function "${
|
||||
injectable.id
|
||||
}" without override. Add eg. "di.override(${camelCase(
|
||||
injectable.id,
|
||||
)}Mock)" to the unit test interested in this.`,
|
||||
);
|
||||
});
|
||||
@@ -16,7 +16,7 @@ export const advanceFakeTime = (milliseconds: number) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useFakeTime = (dateTime: string) => {
|
||||
export const useFakeTime = (dateTime = "2015-10-21T07:28:00Z") => {
|
||||
usingFakeTime = true;
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { UserStore } from "./user-store";
|
||||
import selectedUpdateChannelInjectable from "../application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||
|
||||
const userStoreInjectable = getInjectable({
|
||||
id: "user-store",
|
||||
|
||||
@@ -11,8 +11,10 @@ import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
||||
import { DESCRIPTORS } from "./preferences-helpers";
|
||||
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
||||
import logger from "../../main/logger";
|
||||
import type { SelectedUpdateChannel } from "../application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
import type { ReleaseChannel } from "../application-update/update-channels";
|
||||
|
||||
// TODO: Remove coupling with Feature
|
||||
import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
||||
|
||||
export interface UserStoreModel {
|
||||
lastSeenAppVersion: string;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { addSeparator } from "./add-separator";
|
||||
|
||||
describe("add-separator", () => {
|
||||
it("given multiple items, adds separators", () => {
|
||||
const items = ["first", "second", "third"];
|
||||
|
||||
const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items);
|
||||
|
||||
expect(actual).toEqual([
|
||||
"first",
|
||||
"separator-between-first-and-second",
|
||||
"second",
|
||||
"separator-between-second-and-third",
|
||||
"third",
|
||||
]);
|
||||
});
|
||||
|
||||
it("given multiple items including falsy ones, adds separators", () => {
|
||||
const items = [false, undefined, null, NaN];
|
||||
|
||||
const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items);
|
||||
|
||||
expect(actual).toEqual([
|
||||
false,
|
||||
"separator-between-false-and-undefined",
|
||||
undefined,
|
||||
"separator-between-undefined-and-null",
|
||||
null,
|
||||
"separator-between-null-and-NaN",
|
||||
NaN,
|
||||
]);
|
||||
});
|
||||
|
||||
it("given no items, does not add separator", () => {
|
||||
const items: any[] = [];
|
||||
|
||||
const actual = addSeparator(() => "separator", items);
|
||||
|
||||
expect(actual).toEqual([]);
|
||||
});
|
||||
|
||||
it("given one item, does not add separator", () => {
|
||||
const items = ["first"];
|
||||
|
||||
const actual = addSeparator(() => "separator", items);
|
||||
|
||||
expect(actual).toEqual(["first"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export type GetSeparator<Item, Separator> = (left: Item, right: Item) => Separator;
|
||||
|
||||
export const addSeparator = <Item, Separator>(
|
||||
getSeparator: GetSeparator<Item, Separator>,
|
||||
items: Item[],
|
||||
) => items.flatMap(toSeparatedTupleUsing(getSeparator));
|
||||
|
||||
const toSeparatedTupleUsing =
|
||||
<Item, Separator>(getSeparator: GetSeparator<Item, Separator>) =>
|
||||
(leftItem: Item, index: number, arr: Item[]) => {
|
||||
const itemIsLast = arr.length === index + 1;
|
||||
|
||||
if (itemIsLast) {
|
||||
return [leftItem];
|
||||
}
|
||||
|
||||
const rightItem = arr[index + 1];
|
||||
const separator = getSeparator(leftItem, rightItem);
|
||||
|
||||
return [leftItem, separator];
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
|
||||
export interface Discriminable<T extends string> { readonly kind: T }
|
||||
|
||||
// Note: this will fail at transpilation time, if all kinds are not instructed in switch/case.
|
||||
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
|
||||
export const checkThatAllDiscriminablesAreExhausted = <T extends never>(value: T) => {
|
||||
const _exhaustiveCheck: never = value;
|
||||
|
||||
return new Error(
|
||||
`Tried to exhaust discriminables, but no instructions were found for ${(_exhaustiveCheck as any).kind}`,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export interface Labelable {
|
||||
readonly label: string;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { sortBy } from "lodash/fp";
|
||||
|
||||
export interface Orderable {
|
||||
readonly orderNumber: number;
|
||||
}
|
||||
|
||||
export type MaybeOrderable = Orderable | object;
|
||||
|
||||
export const orderByOrderNumber = <T extends MaybeOrderable>(maybeOrderables: T[]) =>
|
||||
sortBy(
|
||||
(orderable) =>
|
||||
"orderNumber" in orderable
|
||||
? orderable.orderNumber
|
||||
: Number.MAX_SAFE_INTEGER,
|
||||
maybeOrderables,
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { isBoolean } from "../../type-narrowing";
|
||||
|
||||
export interface Showable {
|
||||
readonly isShown: IComputedValue<boolean> | boolean;
|
||||
}
|
||||
|
||||
export type MaybeShowable = Showable | object;
|
||||
|
||||
export const isShown = (showable: MaybeShowable) => {
|
||||
if (!("isShown" in showable)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (showable.isShown === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isBoolean(showable.isShown)) {
|
||||
return showable.isShown;
|
||||
}
|
||||
|
||||
return showable.isShown.get();
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { compositeHasDescendant } from "./composite-has-descendant";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("composite-has-descendant, given composite with children and grand children", () => {
|
||||
let composite: Composite<{ id: string; parentId?: string }>;
|
||||
|
||||
beforeEach(() => {
|
||||
const items = [
|
||||
{ id: "some-root-id", parentId: undefined },
|
||||
{ id: "some-child-item", parentId: "some-root-id" },
|
||||
|
||||
{
|
||||
id: "some-grand-child-item",
|
||||
parentId: "some-child-item",
|
||||
},
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
composite = getComposite(items);
|
||||
});
|
||||
|
||||
it("has a child as descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) => referenceComposite.value.id === "some-child-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("has a grand child as descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) =>
|
||||
referenceComposite.value.id === "some-grand-child-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("does not have an unrelated descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) =>
|
||||
referenceComposite.value.id === "some-unknown-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
const compositeHasDescendant = <T>(
|
||||
predicate: (referenceComposite: Composite<T>) => boolean,
|
||||
) => {
|
||||
const _compositeHasDescendant = (composite: Composite<T>): boolean =>
|
||||
predicate(composite) ||
|
||||
!!composite.children.find((childComposite) =>
|
||||
_compositeHasDescendant(childComposite),
|
||||
);
|
||||
|
||||
return _compositeHasDescendant;
|
||||
};
|
||||
|
||||
export { compositeHasDescendant };
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { findComposite } from "./find-composite";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("find-composite", () => {
|
||||
let composite: Composite<{ id: string; parentId?: string }>;
|
||||
|
||||
beforeEach(() => {
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
composite = getComposite([
|
||||
{ id: "some-root-id" },
|
||||
{ id: "some-child-id", parentId: "some-root-id" },
|
||||
{ id: "some-grandchild-id", parentId: "some-child-id" },
|
||||
{ id: "some-other-grandchild-id", parentId: "some-child-id" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("when finding root using path, does so", () => {
|
||||
const actual = findComposite("some-root-id")(composite);
|
||||
|
||||
expect(actual.id).toBe("some-root-id");
|
||||
});
|
||||
|
||||
it("when finding child using path, does so", () => {
|
||||
const actual = findComposite("some-root-id", "some-child-id")(composite);
|
||||
|
||||
expect(actual.id).toBe("some-child-id");
|
||||
});
|
||||
|
||||
it("when finding grandchild using path, does so", () => {
|
||||
const actual = findComposite(
|
||||
"some-root-id",
|
||||
"some-child-id",
|
||||
"some-grandchild-id",
|
||||
)(composite);
|
||||
|
||||
expect(actual.id).toBe("some-grandchild-id");
|
||||
});
|
||||
|
||||
it("when finding with non existing leaf-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-root-id",
|
||||
"some-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-root-id -> some-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id -> some-child-id' had only following children:
|
||||
some-grandchild-id
|
||||
some-other-grandchild-id`);
|
||||
});
|
||||
|
||||
it("when finding with non-existing mid-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-root-id",
|
||||
"some-non-existing-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id' had only following children:
|
||||
some-child-id`);
|
||||
});
|
||||
|
||||
it("when finding with non-existing root-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-non-existing-root-id",
|
||||
"some-non-existing-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-non-existing-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id' had only following children:
|
||||
some-child-id`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
const _findComposite = <T>(currentLeftIds: string[], currentId: string, currentRightIds: string[], composite: Composite<T>): Composite<T> => {
|
||||
const [nextId, ...nextRightIds] = currentRightIds;
|
||||
const nextLeftIds = [...currentLeftIds, currentId];
|
||||
|
||||
if (currentRightIds.length === 0 && composite.id === currentId) {
|
||||
return composite;
|
||||
}
|
||||
|
||||
const foundChildComposite = composite.children.find((child) => child.id === nextId);
|
||||
|
||||
if (foundChildComposite) {
|
||||
return _findComposite(nextLeftIds, nextId, nextRightIds, foundChildComposite);
|
||||
}
|
||||
|
||||
const fullPathString = [...currentLeftIds, currentId, ...currentRightIds].join(" -> ");
|
||||
|
||||
throw new Error(`Tried to find '${fullPathString}' from a composite, but found nothing.
|
||||
|
||||
Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following children:
|
||||
${composite.children.map((child) => child.id).join("\n")}`);
|
||||
};
|
||||
|
||||
export const findComposite =
|
||||
(...path: string[]) =>
|
||||
<T>(composite: Composite<T>): Composite<T> => {
|
||||
const [currentId, ...rightIds] = path;
|
||||
const leftIds: string[] = [];
|
||||
|
||||
return _findComposite(leftIds, currentId, rightIds, composite);
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getCompositeNormalization } from "./get-composite-normalization";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
|
||||
describe("get-composite-normalization", () => {
|
||||
it("given a composite, flattens it to paths and composites", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-child-id",
|
||||
parentId: "some-id",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const actual = getCompositeNormalization(composite);
|
||||
|
||||
expect(actual).toEqual([
|
||||
[["some-root-id"], expect.objectContaining({ value: someRootItem })],
|
||||
|
||||
[["some-root-id", "some-id"], expect.objectContaining({ value: someItem })],
|
||||
|
||||
[
|
||||
["some-root-id", "some-id", "some-child-id"],
|
||||
expect.objectContaining({ value: someNestedItem }),
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
export const getCompositeNormalization = <T>(composite: Composite<T>) => {
|
||||
const _normalizeComposite = <T>(
|
||||
composite: Composite<T>,
|
||||
previousPath: string[] = [],
|
||||
): (readonly [path: string[], composite: Composite<T>])[] => {
|
||||
const currentPath = [...previousPath, composite.id];
|
||||
|
||||
const pathAndCompositeTuple = [currentPath, composite] as const;
|
||||
|
||||
return [
|
||||
pathAndCompositeTuple,
|
||||
|
||||
...composite.children.flatMap((child) =>
|
||||
_normalizeComposite(child, currentPath),
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
return _normalizeComposite(composite);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getCompositePaths } from "./get-composite-paths";
|
||||
import { sortBy } from "lodash/fp";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("get-composite-paths", () => {
|
||||
it("given composite with transformed children, returns paths of transformed children in hierarchical order", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
};
|
||||
|
||||
const someChildItem1 = {
|
||||
id: "some-child-id-1",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someChildItem2 = {
|
||||
id: "some-child-id-2",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const someGrandchildItem1 = {
|
||||
id: "some-grandchild-id-1",
|
||||
parentId: "some-child-id-1",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someGrandchildItem2 = {
|
||||
id: "some-grandchild-id-2",
|
||||
parentId: "some-child-id-1",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const items = [
|
||||
someRootItem,
|
||||
// Note: not in order yet.
|
||||
someChildItem2,
|
||||
someChildItem1,
|
||||
someGrandchildItem2,
|
||||
someGrandchildItem1,
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
transformChildren: children => sortBy(child => child.orderNumber, children),
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const actual = getCompositePaths(composite);
|
||||
|
||||
expect(actual).toEqual([
|
||||
["some-root-id"],
|
||||
["some-root-id", "some-child-id-1"],
|
||||
["some-root-id", "some-child-id-1", "some-grandchild-id-1"],
|
||||
["some-root-id", "some-child-id-1", "some-grandchild-id-2"],
|
||||
["some-root-id", "some-child-id-2"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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 { map } from "lodash/fp";
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { getCompositeNormalization } from "../get-composite-normalization/get-composite-normalization";
|
||||
|
||||
export const getCompositePaths = (
|
||||
composite: Composite<unknown>,
|
||||
): string[][] => pipeline(composite, getCompositeNormalization, map(([path]) => path));
|
||||
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Composite } from "./get-composite";
|
||||
import { getCompositePaths } from "../get-composite-paths/get-composite-paths";
|
||||
import { sortBy } from "lodash/fp";
|
||||
import { getCompositeFor } from "./get-composite";
|
||||
|
||||
interface SomeItem {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}
|
||||
|
||||
describe("get-composite", () => {
|
||||
it("given items and an explicit root id, creates a composite", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
someProperty: "some-root-content",
|
||||
};
|
||||
|
||||
const someIrrelevantRootItem = {
|
||||
id: "some-irrelevant-root-id",
|
||||
someProperty: "some-other-root-content",
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
someProperty: "some-content",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
someProperty: "some-nested-content",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someIrrelevantRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
expect(composite).toEqual({
|
||||
id: "some-root-id",
|
||||
value: someRootItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
value: someItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
value: someNestedItem,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("given items and implicit root, creates a composite", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
someProperty: "some-root-content",
|
||||
// Notice: no "parentId" makes this the implicit root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
someProperty: "some-content",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
someProperty: "some-nested-content",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
// Notice: no root id
|
||||
// rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
expect(composite).toEqual({
|
||||
id: "some-root-id",
|
||||
value: someRootItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
value: someItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
value: someNestedItem,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("given items and an unspecified root id and multiple items without parent as root, throws", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
// Notice: no "parentId" makes this a root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someOtherRootItem = {
|
||||
id: "some-other-root-id",
|
||||
// Notice: no "parentId" makes also this a root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const items = [someRootItem, someOtherRootItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given non-unique ids, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "irrelevant",
|
||||
};
|
||||
|
||||
const someOtherItem = {
|
||||
id: "some-id",
|
||||
parentId: "irrelevant",
|
||||
};
|
||||
|
||||
const items = [someItem, someOtherItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite but encountered non-unique ids: "some-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItemWithMissingParentId = {
|
||||
id: "some-other-id",
|
||||
parentId: "some-missing-id",
|
||||
};
|
||||
|
||||
const items = [someItem, someItemWithMissingParentId];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
`Tried to get a composite but encountered missing parent ids: "some-missing-id".
|
||||
|
||||
Available parent ids are:
|
||||
"some-id",
|
||||
"some-other-id"`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("given items with missing parents, when creating composite with handling for missing parents", () => {
|
||||
let composite: Composite<any>;
|
||||
let handleMissingParentIdMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const someItem = {
|
||||
id: "some-root-id",
|
||||
};
|
||||
|
||||
const someItemWithMissingParentId = {
|
||||
id: "some-orphan-id",
|
||||
// Note: the item corresponding to this id does not exist,
|
||||
// making this item have a "missing parent".
|
||||
parentId: "some-missing-id",
|
||||
};
|
||||
|
||||
const items = [someItem, someItemWithMissingParentId];
|
||||
|
||||
handleMissingParentIdMock = jest.fn();
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
handleMissingParentIds: handleMissingParentIdMock,
|
||||
});
|
||||
|
||||
composite = getComposite(items);
|
||||
});
|
||||
|
||||
it("creates composite without the orphan item, and without throwing", () => {
|
||||
const paths = getCompositePaths(composite);
|
||||
|
||||
expect(paths).toEqual([["some-root-id"]]);
|
||||
});
|
||||
|
||||
it("handles the missing parent ids", () => {
|
||||
expect(handleMissingParentIdMock).toHaveBeenCalledWith({
|
||||
missingParentIds: ["some-missing-id"],
|
||||
availableParentIds: ["some-root-id", "some-orphan-id"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("given items with same id and parent id, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-id",
|
||||
};
|
||||
|
||||
const someRoot = {
|
||||
id: "root",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const items = [someItem, someRoot];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite, but found items with self as parent: "some-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given undefined ids, throws", () => {
|
||||
const root = {
|
||||
parentId: undefined,
|
||||
id: "some-root",
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
parentId: "some-root",
|
||||
id: undefined,
|
||||
};
|
||||
|
||||
const someOtherItem = {
|
||||
parentId: "some-root",
|
||||
id: undefined,
|
||||
};
|
||||
|
||||
const items = [root, someItem, someOtherItem];
|
||||
|
||||
const getComposite = getCompositeFor<any>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow("Tried to get a composite but encountered 2 undefined ids");
|
||||
});
|
||||
|
||||
it("given transformed children, creates a composite with transformed children", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someItem1 = {
|
||||
id: "some-id-1",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someItem2 = {
|
||||
id: "some-id-2",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const someChildItem1 = {
|
||||
id: "some-child-id-1",
|
||||
parentId: "some-id-1",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someChildItem2 = {
|
||||
id: "some-child-id-2",
|
||||
parentId: "some-id-1",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const items = [
|
||||
someRootItem,
|
||||
// Note: not in order yet.
|
||||
someItem2,
|
||||
someItem1,
|
||||
someChildItem2,
|
||||
someChildItem1,
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
transformChildren: (things) =>
|
||||
sortBy((thing) => thing.orderNumber, things),
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const orderedPaths = getCompositePaths(composite);
|
||||
|
||||
expect(orderedPaths).toEqual([
|
||||
["some-root-id"],
|
||||
["some-root-id", "some-id-1"],
|
||||
["some-root-id", "some-id-1", "some-child-id-1"],
|
||||
["some-root-id", "some-id-1", "some-child-id-2"],
|
||||
["some-root-id", "some-id-2"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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 {
|
||||
countBy,
|
||||
filter,
|
||||
toPairs,
|
||||
nth,
|
||||
map,
|
||||
uniq,
|
||||
without,
|
||||
compact,
|
||||
identity,
|
||||
} from "lodash/fp";
|
||||
|
||||
export interface Composite<T> {
|
||||
id: string;
|
||||
parentId: string | undefined;
|
||||
value: T;
|
||||
children: Composite<T>[];
|
||||
}
|
||||
|
||||
interface Configuration<T> {
|
||||
rootId?: string;
|
||||
getId: (thing: T) => string;
|
||||
getParentId: (thing: T) => string | undefined;
|
||||
transformChildren?: (things: T[]) => T[];
|
||||
handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void;
|
||||
}
|
||||
|
||||
export const getCompositeFor = <T>({
|
||||
rootId = undefined,
|
||||
getId,
|
||||
getParentId,
|
||||
transformChildren = identity,
|
||||
handleMissingParentIds = throwMissingParentIds,
|
||||
}: Configuration<T>) => (source: T[]) => {
|
||||
const undefinedIds = pipeline(
|
||||
source,
|
||||
filter((x) => getId(x) === undefined),
|
||||
);
|
||||
|
||||
if (undefinedIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered ${undefinedIds.length} undefined ids`,
|
||||
);
|
||||
}
|
||||
|
||||
const selfReferencingIds = pipeline(
|
||||
source,
|
||||
filter((x) => getId(x) === getParentId(x)),
|
||||
map(getId),
|
||||
);
|
||||
|
||||
if (selfReferencingIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite, but found items with self as parent: "${selfReferencingIds.join(
|
||||
'", ',
|
||||
)}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const duplicateIds = pipeline(
|
||||
source,
|
||||
countBy(getId),
|
||||
toPairs,
|
||||
filter(([, count]) => count > 1),
|
||||
map(nth(0)),
|
||||
);
|
||||
|
||||
if (duplicateIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered non-unique ids: "${duplicateIds
|
||||
.map((x) => String(x))
|
||||
.join('", "')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const allIds = pipeline(source, map(getId));
|
||||
|
||||
const allParentIds = pipeline(source, map(getParentId), uniq, compact);
|
||||
|
||||
const missingParentIds = without(allIds, allParentIds);
|
||||
|
||||
if (missingParentIds.length) {
|
||||
handleMissingParentIds({ missingParentIds, availableParentIds: allIds });
|
||||
}
|
||||
|
||||
const toComposite = (thing: T): Composite<T> => {
|
||||
const thingId = getId(thing);
|
||||
|
||||
return {
|
||||
id: thingId,
|
||||
parentId: getParentId(thing),
|
||||
value: thing,
|
||||
|
||||
children: pipeline(
|
||||
source,
|
||||
|
||||
filter((childThing) => {
|
||||
const parentId = getParentId(childThing);
|
||||
|
||||
return parentId === thingId;
|
||||
}),
|
||||
|
||||
transformChildren,
|
||||
|
||||
map(toComposite),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const isRootId = rootId
|
||||
? (thing: T) => getId(thing) === rootId
|
||||
: (thing: T) => getParentId(thing) === undefined;
|
||||
|
||||
const roots = source.filter(isRootId);
|
||||
|
||||
if (roots.length > 1) {
|
||||
throw new Error(
|
||||
`Tried to get a composite, but multiple roots where encountered: "${roots
|
||||
.map(getId)
|
||||
.join('", "')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return toComposite(roots[0]);
|
||||
};
|
||||
|
||||
interface ParentIdsForHandling {
|
||||
missingParentIds: string[];
|
||||
availableParentIds: string[];
|
||||
}
|
||||
|
||||
const throwMissingParentIds = ({
|
||||
missingParentIds,
|
||||
availableParentIds,
|
||||
}: ParentIdsForHandling) => {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered missing parent ids: "${missingParentIds.join(
|
||||
'", "',
|
||||
)}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface ParentOfChildComposite<Id extends string = string> {
|
||||
id: Id;
|
||||
}
|
||||
|
||||
export interface ChildOfParentComposite<ParentId extends string = string> {
|
||||
parentId: ParentId;
|
||||
}
|
||||
|
||||
export type RootComposite<Id extends string = string> =
|
||||
& { parentId: undefined }
|
||||
& ParentOfChildComposite<Id>;
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { findExactlyOne } from "./find-exactly-one";
|
||||
|
||||
describe("find-exactly-one", () => {
|
||||
it("when predicate matches to single item, returns the item", () => {
|
||||
const actual = findExactlyOne((item) => item === "some-item")([
|
||||
"some-item",
|
||||
"some-other-item",
|
||||
]);
|
||||
|
||||
expect(actual).toBe("some-item");
|
||||
});
|
||||
|
||||
it("when predicate matches to many items, throws", () => {
|
||||
expect(() => {
|
||||
findExactlyOne((item) => item === "some-item")([
|
||||
"some-item",
|
||||
"some-item",
|
||||
]);
|
||||
}).toThrow("Tried to find exactly one, but found many");
|
||||
});
|
||||
|
||||
it("when predicate does not match, throws", () => {
|
||||
expect(() => {
|
||||
findExactlyOne((item) => item === "some-item")([
|
||||
"some-other-item",
|
||||
]);
|
||||
}).toThrow("Tried to find exactly one, but didn't find any");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export const findExactlyOne = <T>(predicate: (item: T) => boolean) => (collection: T[]): T => {
|
||||
const itemsFound = collection.filter(predicate);
|
||||
|
||||
if (!itemsFound.length) {
|
||||
throw new Error(
|
||||
"Tried to find exactly one, but didn't find any",
|
||||
);
|
||||
}
|
||||
|
||||
if (itemsFound.length > 1) {
|
||||
throw new Error(
|
||||
"Tried to find exactly one, but found many",
|
||||
);
|
||||
}
|
||||
|
||||
return itemsFound[0];
|
||||
};
|
||||
@@ -35,6 +35,15 @@ export function hasTypedProperty<S extends object, K extends PropertyKey, V>(val
|
||||
return hasOwnProperty(val, key) && isValid(val[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type string
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasStringProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: string }) {
|
||||
return hasOwnProperty(val, key) && isString(val[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
|
||||
* @param val the value that we are trying to type narrow
|
||||
|
||||
@@ -4,28 +4,25 @@
|
||||
*/
|
||||
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import loggerInjectable from "../../logger.injectable";
|
||||
import type { Logger } from "../../logger";
|
||||
import withErrorLoggingInjectable from "./with-error-logging.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||
import logErrorInjectable from "../../log-error.injectable";
|
||||
|
||||
describe("with-error-logging", () => {
|
||||
describe("given decorated sync function", () => {
|
||||
let loggerStub: Logger;
|
||||
let toBeDecorated: jest.Mock<number | undefined, [string, string]>;
|
||||
let decorated: (a: string, b: string) => number | undefined;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
loggerStub = {
|
||||
error: jest.fn(),
|
||||
} as unknown as Logger;
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
di.override(loggerInjectable, () => loggerStub);
|
||||
di.override(logErrorInjectable, () => logErrorMock);
|
||||
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
@@ -52,7 +49,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("does not log error", () => {
|
||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the value", () => {
|
||||
@@ -75,7 +72,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("does not log error", () => {
|
||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns nothing", () => {
|
||||
@@ -104,7 +101,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("logs the error", () => {
|
||||
expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||
expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||
});
|
||||
|
||||
it("throws", () => {
|
||||
@@ -114,18 +111,16 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
describe("given decorated async function", () => {
|
||||
let loggerStub: Logger;
|
||||
let decorated: (a: string, b: string) => Promise<number | undefined>;
|
||||
let toBeDecorated: AsyncFnMock<typeof decorated>;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
loggerStub = {
|
||||
error: jest.fn(),
|
||||
} as unknown as Logger;
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
di.override(loggerInjectable, () => loggerStub);
|
||||
di.override(logErrorInjectable, () => logErrorMock);
|
||||
|
||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||
|
||||
@@ -153,7 +148,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("does not log error yet", () => {
|
||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
@@ -176,7 +171,7 @@ describe("with-error-logging", () => {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||
expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||
});
|
||||
|
||||
it("rejects", () => {
|
||||
@@ -198,7 +193,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("logs the rejection", () => {
|
||||
expect(loggerStub.error).toHaveBeenCalledWith(
|
||||
expect(logErrorMock).toHaveBeenCalledWith(
|
||||
"some-error-message-for-some-rejection",
|
||||
error,
|
||||
);
|
||||
@@ -215,7 +210,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("does not log error", () => {
|
||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves with the value", async () => {
|
||||
@@ -231,7 +226,7 @@ describe("with-error-logging", () => {
|
||||
});
|
||||
|
||||
it("does not log error", () => {
|
||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves without value", async () => {
|
||||
|
||||
@@ -5,21 +5,20 @@
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import loggerInjectable from "../../logger.injectable";
|
||||
import type { Logger } from "../../logger";
|
||||
import withOrphanPromiseInjectable from "./with-orphan-promise.injectable";
|
||||
import logErrorInjectable from "../../log-error.injectable";
|
||||
|
||||
describe("with orphan promise, when called", () => {
|
||||
let toBeDecorated: AsyncFnMock<(arg1: string, arg2: string) => Promise<string>>;
|
||||
let actual: void;
|
||||
let loggerStub: Logger;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
loggerStub = { error: jest.fn() } as unknown as Logger;
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
di.override(loggerInjectable, () => loggerStub);
|
||||
di.override(logErrorInjectable, () => logErrorMock);
|
||||
|
||||
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
|
||||
|
||||
@@ -49,7 +48,7 @@ describe("with orphan promise, when called", () => {
|
||||
});
|
||||
|
||||
it("logs the rejection", () => {
|
||||
expect(loggerStub.error).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error");
|
||||
expect(logErrorMock).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error");
|
||||
});
|
||||
|
||||
it("nothing else happens", () => {
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
// Todo: OCP by creating distinct injectables for platforms.
|
||||
export const allPlatforms = ["win32", "darwin", "linux"] as const;
|
||||
|
||||
const platformInjectable = getInjectable({
|
||||
id: "platform",
|
||||
instantiate: () => process.platform,
|
||||
instantiate: () => process.platform as typeof allPlatforms[number],
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ReleaseChannel } from "../application-update/update-channels";
|
||||
import { createInitializableState } from "../initializable-state/create";
|
||||
import buildSemanticVersionInjectable from "./build-semantic-version.injectable";
|
||||
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
||||
|
||||
const releaseChannelInjectable = createInitializableState({
|
||||
id: "release-channel",
|
||||
|
||||
Reference in new issue
Block a user