diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index 5771c31c34..fcbb5bfb28 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -250,7 +250,6 @@ export class ExtensionLoader { return this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), - registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 3baa9fd8bc..cc2143ec83 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -28,6 +28,9 @@ import extensionPageParametersInjectable from "../renderer/routes/extension-page import { pipeline } from "@ogre-tools/fp"; import { getExtensionRoutePath } from "../renderer/routes/get-extension-route-path"; import { navigateToRouteInjectionToken } from "../common/front-end-routing/navigate-to-route-injection-token"; +import type { + CatalogEntityDetailRegistration, +} from "../renderer/components/+catalog/catalog-entity-detail-items/extension-registration"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -43,7 +46,7 @@ export class LensRendererExtension extends LensExtension { commands: CommandRegistration[] = []; welcomeMenus: WelcomeMenuRegistration[] = []; welcomeBanners: WelcomeBannerRegistration[] = []; - catalogEntityDetailItems: registries.CatalogEntityDetailRegistration[] = []; + catalogEntityDetailItems: CatalogEntityDetailRegistration[] = []; topBarItems: TopBarRegistration[] = []; additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = []; customCategoryViews: CustomCategoryViewRegistration[] = []; diff --git a/src/extensions/registries/catalog-entity-detail-registry.ts b/src/extensions/registries/catalog-entity-detail-registry.ts deleted file mode 100644 index ac1f676ebf..0000000000 --- a/src/extensions/registries/catalog-entity-detail-registry.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type React from "react"; -import type { CatalogEntity } from "../common-api/catalog"; -import { BaseRegistry } from "./base-registry"; - -export interface CatalogEntityDetailsProps { - entity: T; -} - -export interface CatalogEntityDetailComponents { - Details: React.ComponentType>; -} - -export interface CatalogEntityDetailRegistration { - kind: string; - apiVersions: string[]; - components: CatalogEntityDetailComponents; - priority?: number; -} - -export class CatalogEntityDetailRegistry extends BaseRegistry> { - getItemsForKind(kind: string, apiVersion: string) { - const items = this.getItems().filter((item) => { - return item.kind === kind && item.apiVersions.includes(apiVersion); - }); - - return items.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50)); - } -} diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 23f064a26b..7fbdf24bb5 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -9,5 +9,4 @@ export * from "./page-registry"; export * from "./page-menu-registry"; export * from "./kube-object-detail-registry"; export * from "./entity-setting-registry"; -export * from "./catalog-entity-detail-registry"; export * from "./protocol-handler"; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 3d1f9abf3d..833f676d2e 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -86,9 +86,6 @@ export async function bootstrap(di: DiContainer) { logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`); initializers.initKubeObjectDetailRegistry(); - logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`); - initializers.initCatalogEntityDetailRegistry(); - const navigateToAddCluster = di.inject(navigateToAddClusterInjectable); const addSyncEntries = di.inject(addSyncEntriesInjectable); diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-item-injection-token.ts b/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-item-injection-token.ts new file mode 100644 index 0000000000..3b70ce6b36 --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-item-injection-token.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; +import type { CatalogEntity } from "../../../../common/catalog"; +import type { CatalogEntityDetailItemComponentProps } from "./extension-registration"; + +export interface CatalogEntityDetailItem { + apiVersions: string[]; + kind: string; + + components: { + Details: React.ComponentType>; + }; + + orderNumber: number; + + extension?: LensRendererExtension; +} + +export const catalogEntityDetailItemInjectionToken = getInjectionToken>({ + id: "catalog-entity-detail-item", +}); diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-items.injectable.ts b/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-items.injectable.ts new file mode 100644 index 0000000000..a5275cbea0 --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/catalog-entity-detail-items.injectable.ts @@ -0,0 +1,59 @@ +/** + * 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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; +import { CatalogEntityDetailItem, catalogEntityDetailItemInjectionToken } from "./catalog-entity-detail-item-injection-token"; +import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; +import { conforms, eq, filter, includes, overSome, sortBy } from "lodash/fp"; +import type { CatalogEntity } from "../../../../common/catalog"; + +const catalogEntityDetailItemsInjectable = getInjectable({ + id: "catalog-entity-detail-items", + + instantiate: (di, catalogEntity: CatalogEntity) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => { + const enabledExtensions = extensions.get(); + + return pipeline( + di.injectMany(catalogEntityDetailItemInjectionToken), + + filter((item) => + overSome([ + isNonExtensionItem, + isEnabledExtensionItemFor(enabledExtensions), + ])(item), + ), + + filter(item => + conforms({ + kind: eq(catalogEntity.kind), + apiVersions: includes(catalogEntity.apiVersion), + })(item), + ), + + items => sortBy("orderNumber", items), + ); + }); + }, + + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, catalogEntity: CatalogEntity) => + `${catalogEntity.kind}/${catalogEntity.apiVersion}`, + }), +}); + +const isNonExtensionItem = (item: CatalogEntityDetailItem) => + !item.extension; + +const isEnabledExtensionItemFor = + (enabledExtensions: LensRendererExtension[]) => + (item: CatalogEntityDetailItem) => + !!enabledExtensions.find((extension) => extension === item.extension); + +export default catalogEntityDetailItemsInjectable; diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/extension-catalog-entity-detail-items-registrator.injectable.ts b/src/renderer/components/+catalog/catalog-entity-detail-items/extension-catalog-entity-detail-items-registrator.injectable.ts new file mode 100644 index 0000000000..688242d721 --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/extension-catalog-entity-detail-items-registrator.injectable.ts @@ -0,0 +1,45 @@ +/** + * 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 { getInjectable } from "@ogre-tools/injectable"; +import { forEach } from "lodash/fp"; +import { extensionRegistratorInjectionToken } from "../../../../extensions/extension-loader/extension-registrator-injection-token"; +import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; +import { catalogEntityDetailItemInjectionToken } from "./catalog-entity-detail-item-injection-token"; + +const extensionCatalogEntityDetailItemsRegistratorInjectable = getInjectable({ + id: "extension-catalog-entity-detail-items-registrator", + + instantiate: + (di) => (extension: LensRendererExtension, installationCounter) => { + pipeline( + extension.catalogEntityDetailItems.map((registration, index) => + getInjectable({ + id: `catalog-entity-detail-item-${index}-from-${extension.sanitizedExtensionId}-instance-${installationCounter}`, + + instantiate: () => ({ + apiVersions: registration.apiVersions, + kind: registration.kind, + + components: { + Details: registration.components.Details, + }, + + orderNumber: -registration.priority || -50, + extension, + }), + + injectionToken: catalogEntityDetailItemInjectionToken, + }), + ), + + forEach(di.register), + ); + }, + + injectionToken: extensionRegistratorInjectionToken, +}); + +export default extensionCatalogEntityDetailItemsRegistratorInjectable; diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/extension-registration.ts b/src/renderer/components/+catalog/catalog-entity-detail-items/extension-registration.ts new file mode 100644 index 0000000000..4fa5686406 --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/extension-registration.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { CatalogEntity } from "../../../../common/catalog"; + +export interface CatalogEntityDetailItemComponentProps { + entity: T; +} + +export interface CatalogEntityDetailComponents { + Details: React.ComponentType>; +} + +export interface CatalogEntityDetailRegistration { + kind: string; + apiVersions: string[]; + components: CatalogEntityDetailComponents; + priority?: number; +} diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/kubernetes-information-catalog-entity-detail-item.injectable.tsx b/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/kubernetes-information-catalog-entity-detail-item.injectable.tsx new file mode 100644 index 0000000000..d30ede17fb --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/kubernetes-information-catalog-entity-detail-item.injectable.tsx @@ -0,0 +1,41 @@ +/** + * 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 { catalogEntityDetailItemInjectionToken } from "../catalog-entity-detail-item-injection-token"; +import { KubernetesCluster } from "../../../../../common/catalog-entities"; +import { DrawerItem, DrawerTitle } from "../../../drawer"; +import React from "react"; +import type { CatalogEntityDetailItemComponentProps } from "../extension-registration"; + +const Details = ({ entity }: CatalogEntityDetailItemComponentProps) => ( + <> + Kubernetes Information +
+ + {entity.metadata.distro || "unknown"} + + + {entity.metadata.kubeVersion || "unknown"} + +
+ +); + +const kubernetesInformationCatalogEntityDetailItemInjectable = getInjectable({ + id: "kubernetes-information-catalog-entity-detail-item", + instantiate: () => ({ + apiVersions: [KubernetesCluster.apiVersion], + kind: KubernetesCluster.kind, + orderNumber: 10, + + components: { + Details, + }, + }), + + injectionToken: catalogEntityDetailItemInjectionToken, +}); + +export default kubernetesInformationCatalogEntityDetailItemInjectable; diff --git a/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/web-link-catalog-entity-detail-item.injectable.tsx b/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/web-link-catalog-entity-detail-item.injectable.tsx new file mode 100644 index 0000000000..2ea2a94897 --- /dev/null +++ b/src/renderer/components/+catalog/catalog-entity-detail-items/implementations/web-link-catalog-entity-detail-item.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { catalogEntityDetailItemInjectionToken } from "../catalog-entity-detail-item-injection-token"; +import { WebLink } from "../../../../../common/catalog-entities"; +import { DrawerItem, DrawerTitle } from "../../../drawer"; +import React from "react"; +import type { CatalogEntityDetailItemComponentProps } from "../extension-registration"; + +const Details = ({ entity }: CatalogEntityDetailItemComponentProps) => ( + <> + More Information + {entity.spec.url} + +); + +const webLinkCatalogEntityDetailItemInjectable = getInjectable({ + id: "web-link-catalog-entity-detail-item", + + instantiate: () => ({ + apiVersions: [WebLink.apiVersion], + kind: WebLink.kind, + orderNumber: 20, + + components: { + Details, + }, + }), + + injectionToken: catalogEntityDetailItemInjectionToken, +}); + +export default webLinkCatalogEntityDetailItemInjectable; diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index 569e6b60c1..b544b1da99 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -7,14 +7,17 @@ import styles from "./catalog-entity-details.module.scss"; import React, { Component } from "react"; import { observer } from "mobx-react"; import { Drawer, DrawerItem } from "../drawer"; -import type { CatalogCategory, CatalogEntity } from "../../../common/catalog"; +import type { CatalogEntity } from "../../../common/catalog"; import { Icon } from "../icon"; import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu"; -import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { isDevelopment } from "../../../common/vars"; import { cssNames } from "../../utils"; import { Avatar } from "../avatar"; import { getLabelBadges } from "./helpers"; +import catalogEntityDetailItemsInjectable from "./catalog-entity-detail-items/catalog-entity-detail-items.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { CatalogEntityDetailItem } from "./catalog-entity-detail-items/catalog-entity-detail-item-injection-token"; +import type { IComputedValue } from "mobx"; export interface CatalogEntityDetailsProps { entity: T; @@ -22,71 +25,62 @@ export interface CatalogEntityDetailsProps { onRun: () => void; } -@observer -export class CatalogEntityDetails extends Component> { - categoryIcon(category: CatalogCategory) { - if (Icon.isSvg(category.metadata.icon)) { - return ; - } else { - return ; - } - } +interface Dependencies { + detailItems: IComputedValue[]>; +} +@observer +class NonInjectedCatalogEntityDetails extends Component & Dependencies> { renderContent(entity: T) { const { onRun, hideDetails } = this.props; - const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion); - const details = detailItems.map(({ components }, index) => ); - const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined; return ( <> - {showDefaultDetails && ( -
-
- - {entity.spec.icon?.material && } - - {entity.isEnabled() && ( -
+
+
+ + {entity.spec.icon?.material && } + + {entity.isEnabled() && ( +
Click to open -
- )} -
-
- - {entity.getName()} - - - {entity.kind} - - - {entity.getSource()} - - - {entity.status.phase} - - - {getLabelBadges(entity, hideDetails)} - - {isDevelopment && ( - - {entity.getId()} - - )} -
+
+ )}
- )} +
+ + {entity.getName()} + + + {entity.kind} + + + {entity.getSource()} + + + {entity.status.phase} + + + {getLabelBadges(entity, hideDetails)} + + {isDevelopment && ( + + {entity.getId()} + + )} +
+
- {details} + {this.props.detailItems.get().map(({ components: { Details }}, index) =>
)}
); @@ -109,3 +103,18 @@ export class CatalogEntityDetails extends Component>( + NonInjectedCatalogEntityDetails, + + { + getProps: (di, props) => ({ + detailItems: di.inject(catalogEntityDetailItemsInjectable, props.entity), + ...props, + }), + }, +); + +export const CatalogEntityDetails = ( + props: CatalogEntityDetailsProps, +) => ; diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 9422adf055..78ccd2761c 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -10,7 +10,6 @@ import { Catalog } from "./catalog"; import { mockWindow } from "../../../../__mocks__/windowMock"; import { CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../../api/catalog-entity-registry"; -import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiContainer } from "@ogre-tools/injectable"; @@ -101,7 +100,6 @@ describe("", () => { UserStore.createInstance(); ThemeStore.createInstance(); - CatalogEntityDetailRegistry.createInstance(); render = renderFor(di); @@ -123,7 +121,6 @@ describe("", () => { afterEach(() => { UserStore.resetInstance(); ThemeStore.resetInstance(); - CatalogEntityDetailRegistry.resetInstance(); jest.clearAllMocks(); jest.restoreAllMocks(); diff --git a/src/renderer/initializers/catalog-entity-detail-registry.tsx b/src/renderer/initializers/catalog-entity-detail-registry.tsx deleted file mode 100644 index 0ffccd8063..0000000000 --- a/src/renderer/initializers/catalog-entity-detail-registry.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { KubernetesCluster, WebLink } from "../../common/catalog-entities"; -import { CatalogEntityDetailRegistry, CatalogEntityDetailsProps } from "../../extensions/registries"; -import { DrawerItem, DrawerTitle } from "../components/drawer"; - -export function initCatalogEntityDetailRegistry() { - CatalogEntityDetailRegistry.getInstance() - .add([ - { - apiVersions: [KubernetesCluster.apiVersion], - kind: KubernetesCluster.kind, - components: { - Details: ({ entity }: CatalogEntityDetailsProps) => ( - <> - Kubernetes Information -
- - {entity.metadata.distro || "unknown"} - - - {entity.metadata.kubeVersion || "unknown"} - -
- - ), - }, - }, - { - apiVersions: [WebLink.apiVersion], - kind: WebLink.kind, - components: { - Details: ({ entity }: CatalogEntityDetailsProps) => ( - <> - More Information - - {entity.spec.url} - - - ), - }, - }, - ]); -} diff --git a/src/renderer/initializers/index.ts b/src/renderer/initializers/index.ts index b6b458a8eb..0f0141b1fc 100644 --- a/src/renderer/initializers/index.ts +++ b/src/renderer/initializers/index.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export * from "./catalog-entity-detail-registry"; export * from "./catalog"; export * from "./entity-settings-registry"; export * from "./ipc"; diff --git a/src/renderer/initializers/registries.ts b/src/renderer/initializers/registries.ts index 1caac13959..1cff0d47e7 100644 --- a/src/renderer/initializers/registries.ts +++ b/src/renderer/initializers/registries.ts @@ -6,7 +6,6 @@ import * as registries from "../../extensions/registries"; export function initRegistries() { - registries.CatalogEntityDetailRegistry.createInstance(); registries.KubeObjectDetailRegistry.createInstance(); registries.EntitySettingRegistry.createInstance(); }