From 09b33633f755c187fb036d70ae7f6f3c0e379f71 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Fri, 28 Jan 2022 14:40:58 +0100 Subject: [PATCH] Replace StatusBarRegistry with reactive solution (#4728) --- src/extensions/common-api/registrations.ts | 3 +- .../extension-loader/extension-loader.ts | 1 - src/extensions/lens-renderer-extension.ts | 3 +- src/extensions/registries/index.ts | 1 - .../bottom-bar-items.injectable.ts | 28 +++++ .../cluster-manager/bottom-bar.test.tsx | 119 ++++++++++++------ .../components/cluster-manager/bottom-bar.tsx | 34 +++-- .../status-bar-registration.d.ts} | 9 -- src/renderer/initializers/registries.ts | 1 - 9 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 src/renderer/components/cluster-manager/bottom-bar-items.injectable.ts rename src/{extensions/registries/status-bar-registry.ts => renderer/components/cluster-manager/status-bar-registration.d.ts} (74%) diff --git a/src/extensions/common-api/registrations.ts b/src/extensions/common-api/registrations.ts index 20512afb22..5178dd9ee4 100644 --- a/src/extensions/common-api/registrations.ts +++ b/src/extensions/common-api/registrations.ts @@ -2,13 +2,12 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - +export type { StatusBarRegistration } from "../../renderer/components/cluster-manager/status-bar-registration"; export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration"; export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry"; export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"; export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"; export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry"; export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry"; -export type { StatusBarRegistration } from "../registries/status-bar-registry"; export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler"; export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views"; diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index 04b552884c..11ace2cbf1 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -249,7 +249,6 @@ export class ExtensionLoader { const removeItems = [ registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension), registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), - registries.StatusBarRegistry.getInstance().add(extension.statusBarItems), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), ]; diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 7d511c5504..9104f8871b 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -18,6 +18,7 @@ import type { CommandRegistration } from "../renderer/components/command-palette import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration"; import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns"; import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views"; +import type { StatusBarRegistration } from "../renderer/components/cluster-manager/status-bar-registration"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -26,7 +27,7 @@ export class LensRendererExtension extends LensExtension { kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = []; appPreferences: AppPreferenceRegistration[] = []; entitySettings: registries.EntitySettingRegistration[] = []; - statusBarItems: registries.StatusBarRegistration[] = []; + statusBarItems: StatusBarRegistration[] = []; kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = []; kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = []; kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = []; diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 477f406b2c..464d19cd4e 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -7,7 +7,6 @@ export * from "./page-registry"; export * from "./page-menu-registry"; -export * from "./status-bar-registry"; export * from "./kube-object-detail-registry"; export * from "./kube-object-menu-registry"; export * from "./kube-object-status-registry"; diff --git a/src/renderer/components/cluster-manager/bottom-bar-items.injectable.ts b/src/renderer/components/cluster-manager/bottom-bar-items.injectable.ts new file mode 100644 index 0000000000..9f32831ca6 --- /dev/null +++ b/src/renderer/components/cluster-manager/bottom-bar-items.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; +import type { StatusBarRegistration } from "./status-bar-registration"; + +const bottomBarItemsInjectable = getInjectable({ + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => + extensions + .get() + .flatMap((extension) => extension.statusBarItems) + .sort(leftItemsBeforeRight), + ); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default bottomBarItemsInjectable; + +const leftItemsBeforeRight = (firstItem: StatusBarRegistration, secondItem: StatusBarRegistration) => + firstItem.components?.position?.localeCompare(secondItem.components?.position); diff --git a/src/renderer/components/cluster-manager/bottom-bar.test.tsx b/src/renderer/components/cluster-manager/bottom-bar.test.tsx index 97539f0ea3..523500629c 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.test.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.test.tsx @@ -4,10 +4,15 @@ */ import React from "react"; -import { render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import { BottomBar } from "./bottom-bar"; -import { StatusBarRegistry } from "../../../extensions/registries"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import type { DiRender } from "../test-utils/renderFor"; +import { renderFor } from "../test-utils/renderFor"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import { computed, IObservableArray, observable, runInAction } from "mobx"; +import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; jest.mock("electron", () => ({ app: { @@ -15,13 +20,43 @@ jest.mock("electron", () => ({ }, })); -describe("", () => { - beforeEach(() => { - StatusBarRegistry.createInstance(); - }); +class SomeTestExtension extends LensRendererExtension { + constructor(statusBarItems: IObservableArray) { + super({ + id: "some-id", + absolutePath: "irrelevant", + isBundled: false, + isCompatible: false, + isEnabled: false, + manifest: { name: "some-id", version: "some-version" }, + manifestPath: "irrelevant", + }); - afterEach(() => { - StatusBarRegistry.resetInstance(); + this.statusBarItems = statusBarItems; + } +} + +describe("", () => { + let render: DiRender; + let statusBarItems: IObservableArray; + + beforeEach(async () => { + + statusBarItems = observable.array([]); + + const someTestExtension = new SomeTestExtension(statusBarItems); + + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + + di.override(rendererExtensionsInjectable, () => { + return computed(() => [someTestExtension]); + }); + + render = renderFor(di); + + await di.runSetups(); }); it("renders w/o errors", () => { @@ -30,6 +65,7 @@ describe("", () => { expect(container).toBeInstanceOf(HTMLElement); }); + it.each([ undefined, "hello", @@ -39,7 +75,10 @@ describe("", () => { [{}], {}, ])("renders w/o errors when .getItems() returns not type compliant (%p)", val => { - StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => val); + runInAction(() => { + statusBarItems.replace([val]); + }); + expect(() => render()).not.toThrow(); }); @@ -47,9 +86,12 @@ describe("", () => { const testId = "testId"; const text = "heee"; - StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ - { item: {text} }, - ]); + runInAction(() => { + statusBarItems.replace([ + { item: {text} }, + ]); + }); + const { getByTestId } = render(); expect(getByTestId(testId)).toHaveTextContent(text); @@ -59,9 +101,12 @@ describe("", () => { const testId = "testId"; const text = "heee"; - StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ - { item: () => {text} }, - ]); + runInAction(() => { + statusBarItems.replace([ + { item: () => {text} }, + ]); + }); + const { getByTestId } = render(); expect(getByTestId(testId)).toHaveTextContent(text); @@ -69,31 +114,33 @@ describe("", () => { it("sort positioned items properly", () => { - StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ - { - components: { - Item: () =>
right
, + runInAction(() => { + statusBarItems.replace([ + { + components: { + Item: () =>
right
, + }, }, - }, - { - components: { - Item: () =>
right
, - position: "right", + { + components: { + Item: () =>
right
, + position: "right", + }, }, - }, - { - components: { - Item: () =>
left
, - position: "left", + { + components: { + Item: () =>
left
, + position: "left", + }, }, - }, - { - components: { - Item: () =>
left
, - position: "left", + { + components: { + Item: () =>
left
, + position: "left", + }, }, - }, - ]); + ]); + }); const { getAllByTestId } = render(); const elems = getAllByTestId("sortedElem"); diff --git a/src/renderer/components/cluster-manager/bottom-bar.tsx b/src/renderer/components/cluster-manager/bottom-bar.tsx index 84e0f7bbc8..ecbb1634cc 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.tsx @@ -7,11 +7,18 @@ import styles from "./bottom-bar.module.scss"; import React from "react"; import { observer } from "mobx-react"; -import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries"; import { cssNames } from "../../utils"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import bottomBarItemsInjectable from "./bottom-bar-items.injectable"; +import type { IComputedValue } from "mobx"; +import type { StatusBarRegistration } from "./status-bar-registration"; + +interface Dependencies { + items: IComputedValue +} @observer -export class BottomBar extends React.Component { +class NonInjectedBottomBar extends React.Component { renderRegisteredItem(registration: StatusBarRegistration) { const { item } = registration; @@ -23,19 +30,9 @@ export class BottomBar extends React.Component { } renderRegisteredItems() { - const items = StatusBarRegistry.getInstance().getItems(); - - if (!Array.isArray(items)) { - return null; - } - - items.sort(function sortLeftPositionFirst(a, b) { - return a.components?.position?.localeCompare(b.components?.position); - }); - return ( <> - {items.map((registration, index) => { + {this.props.items.get().map((registration, index) => { if (!registration?.item && !registration?.components?.Item) { return null; } @@ -64,3 +61,14 @@ export class BottomBar extends React.Component { ); } } + +export const BottomBar = withInjectables( + NonInjectedBottomBar, + + { + getProps: (di, props) => ({ + items: di.inject(bottomBarItemsInjectable), + ...props, + }), + }, +); diff --git a/src/extensions/registries/status-bar-registry.ts b/src/renderer/components/cluster-manager/status-bar-registration.d.ts similarity index 74% rename from src/extensions/registries/status-bar-registry.ts rename to src/renderer/components/cluster-manager/status-bar-registration.d.ts index e8d4661abc..2faea55cc5 100644 --- a/src/extensions/registries/status-bar-registry.ts +++ b/src/renderer/components/cluster-manager/status-bar-registration.d.ts @@ -2,12 +2,6 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -// Extensions API -> Status bar customizations - -import type React from "react"; -import { BaseRegistry } from "./base-registry"; - interface StatusBarComponents { Item?: React.ComponentType; /** @@ -28,6 +22,3 @@ export interface StatusBarRegistration extends StatusBarRegistrationV2 { */ item?: React.ReactNode; } - -export class StatusBarRegistry extends BaseRegistry { -} diff --git a/src/renderer/initializers/registries.ts b/src/renderer/initializers/registries.ts index 93caeee51e..1c9e5662d4 100644 --- a/src/renderer/initializers/registries.ts +++ b/src/renderer/initializers/registries.ts @@ -14,6 +14,5 @@ export function initRegistries() { registries.KubeObjectDetailRegistry.createInstance(); registries.KubeObjectMenuRegistry.createInstance(); registries.KubeObjectStatusRegistry.createInstance(); - registries.StatusBarRegistry.createInstance(); registries.WorkloadsOverviewDetailRegistry.createInstance(); }