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

Replace catalog entity detail registry with reactive solution

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-04-05 09:48:05 +03:00
parent 1e2a2161f5
commit ff1ab54e0f
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
16 changed files with 296 additions and 150 deletions

View File

@ -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) => {

View File

@ -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<CatalogEntity>[] = [];
catalogEntityDetailItems: CatalogEntityDetailRegistration<CatalogEntity>[] = [];
topBarItems: TopBarRegistration[] = [];
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
customCategoryViews: CustomCategoryViewRegistration[] = [];

View File

@ -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<T extends CatalogEntity> {
entity: T;
}
export interface CatalogEntityDetailComponents<T extends CatalogEntity> {
Details: React.ComponentType<CatalogEntityDetailsProps<T>>;
}
export interface CatalogEntityDetailRegistration<T extends CatalogEntity> {
kind: string;
apiVersions: string[];
components: CatalogEntityDetailComponents<T>;
priority?: number;
}
export class CatalogEntityDetailRegistry extends BaseRegistry<CatalogEntityDetailRegistration<CatalogEntity>> {
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));
}
}

View File

@ -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";

View File

@ -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);

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { 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<T extends CatalogEntity> {
apiVersions: string[];
kind: string;
components: {
Details: React.ComponentType<CatalogEntityDetailItemComponentProps<T>>;
};
orderNumber: number;
extension?: LensRendererExtension;
}
export const catalogEntityDetailItemInjectionToken = getInjectionToken<CatalogEntityDetailItem<CatalogEntity>>({
id: "catalog-entity-detail-item",
});

View File

@ -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<CatalogEntity>) =>
!item.extension;
const isEnabledExtensionItemFor =
(enabledExtensions: LensRendererExtension[]) =>
(item: CatalogEntityDetailItem<CatalogEntity>) =>
!!enabledExtensions.find((extension) => extension === item.extension);
export default catalogEntityDetailItemsInjectable;

View File

@ -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;

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { CatalogEntity } from "../../../../common/catalog";
export interface CatalogEntityDetailItemComponentProps<T extends CatalogEntity> {
entity: T;
}
export interface CatalogEntityDetailComponents<T extends CatalogEntity> {
Details: React.ComponentType<CatalogEntityDetailItemComponentProps<T>>;
}
export interface CatalogEntityDetailRegistration<T extends CatalogEntity> {
kind: string;
apiVersions: string[];
components: CatalogEntityDetailComponents<T>;
priority?: number;
}

View File

@ -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<KubernetesCluster>) => (
<>
<DrawerTitle>Kubernetes Information</DrawerTitle>
<div className="box grow EntityMetadata">
<DrawerItem name="Distribution">
{entity.metadata.distro || "unknown"}
</DrawerItem>
<DrawerItem name="Kubelet Version">
{entity.metadata.kubeVersion || "unknown"}
</DrawerItem>
</div>
</>
);
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;

View File

@ -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<WebLink>) => (
<>
<DrawerTitle>More Information</DrawerTitle>
<DrawerItem name="URL">{entity.spec.url}</DrawerItem>
</>
);
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;

View File

@ -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<T extends CatalogEntity> {
entity: T;
@ -22,25 +25,17 @@ export interface CatalogEntityDetailsProps<T extends CatalogEntity> {
onRun: () => void;
}
@observer
export class CatalogEntityDetails<T extends CatalogEntity> extends Component<CatalogEntityDetailsProps<T>> {
categoryIcon(category: CatalogCategory) {
if (Icon.isSvg(category.metadata.icon)) {
return <Icon svg={category.metadata.icon} smallest />;
} else {
return <Icon material={category.metadata.icon} smallest />;
}
}
interface Dependencies {
detailItems: IComputedValue<CatalogEntityDetailItem<any>[]>;
}
@observer
class NonInjectedCatalogEntityDetails<T extends CatalogEntity> extends Component<CatalogEntityDetailsProps<T> & Dependencies> {
renderContent(entity: T) {
const { onRun, hideDetails } = this.props;
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion);
const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />);
const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined;
return (
<>
{showDefaultDetails && (
<div className="flex">
<div className={styles.entityIcon}>
<Avatar
@ -84,9 +79,8 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
)}
</div>
</div>
)}
<div className="box grow">
{details}
{this.props.detailItems.get().map(({ components: { Details }}, index) => <Details entity={entity} key={index} />)}
</div>
</>
);
@ -109,3 +103,18 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
);
}
}
const InjectedCatalogEntityDetails = withInjectables<Dependencies, CatalogEntityDetailsProps<CatalogEntity>>(
NonInjectedCatalogEntityDetails,
{
getProps: (di, props) => ({
detailItems: di.inject(catalogEntityDetailItemsInjectable, props.entity),
...props,
}),
},
);
export const CatalogEntityDetails = <T extends CatalogEntity>(
props: CatalogEntityDetailsProps<T>,
) => <InjectedCatalogEntityDetails {...props} />;

View File

@ -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("<Catalog />", () => {
UserStore.createInstance();
ThemeStore.createInstance();
CatalogEntityDetailRegistry.createInstance();
render = renderFor(di);
@ -123,7 +121,6 @@ describe("<Catalog />", () => {
afterEach(() => {
UserStore.resetInstance();
ThemeStore.resetInstance();
CatalogEntityDetailRegistry.resetInstance();
jest.clearAllMocks();
jest.restoreAllMocks();

View File

@ -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<KubernetesCluster>) => (
<>
<DrawerTitle>Kubernetes Information</DrawerTitle>
<div className="box grow EntityMetadata">
<DrawerItem name="Distribution">
{entity.metadata.distro || "unknown"}
</DrawerItem>
<DrawerItem name="Kubelet Version">
{entity.metadata.kubeVersion || "unknown"}
</DrawerItem>
</div>
</>
),
},
},
{
apiVersions: [WebLink.apiVersion],
kind: WebLink.kind,
components: {
Details: ({ entity }: CatalogEntityDetailsProps<WebLink>) => (
<>
<DrawerTitle>More Information</DrawerTitle>
<DrawerItem name="URL">
{entity.spec.url}
</DrawerItem>
</>
),
},
},
]);
}

View File

@ -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";

View File

@ -6,7 +6,6 @@
import * as registries from "../../extensions/registries";
export function initRegistries() {
registries.CatalogEntityDetailRegistry.createInstance();
registries.KubeObjectDetailRegistry.createInstance();
registries.EntitySettingRegistry.createInstance();
}