mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Replace CatalogEntityDetailRegistry with an injectable solution (#6605)
* Replace EntityDetailRegistry with an injectable solution - Add some behavioural tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix import error Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify loading extensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove the last reminents of BaseRegistry Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix import errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix TypeError when loading extensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup LensExtensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove bad comment Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
1acacbb7ef
commit
9ba92cb072
78 files changed
+15151
-573
No files matched your search
@@ -7,9 +7,9 @@ export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../..
|
||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration";
|
||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";
|
||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../../renderer/routes/page-registration";
|
||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../../renderer/components/layout/cluster-page-menu";
|
||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../../common/protocol-handler/registration";
|
||||
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
||||
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||
export type { KubeObjectContextMenuItem, KubeObjectOnContextMenuOpenContext, KubeObjectOnContextMenuOpen, KubeObjectHandlers, KubeObjectHandlerRegistration } from "../../renderer/kube-object/handler";
|
||||
|
||||
@@ -8,12 +8,9 @@ import { isEqual } from "lodash";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
import { isDefined, toJS } from "../../common/utils";
|
||||
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
||||
import type { LensRendererExtension } from "../lens-renderer-extension";
|
||||
import * as registries from "../registries";
|
||||
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
||||
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
|
||||
import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
|
||||
@@ -203,7 +200,7 @@ export class ExtensionLoader {
|
||||
|
||||
protected async initMain() {
|
||||
this.isLoaded = true;
|
||||
this.loadOnMain();
|
||||
await this.autoInitExtensions();
|
||||
|
||||
ipcMainHandle(extensionLoaderFromMainChannel, () => {
|
||||
return Array.from(this.toJSON());
|
||||
@@ -251,38 +248,7 @@ export class ExtensionLoader {
|
||||
});
|
||||
}
|
||||
|
||||
loadOnMain() {
|
||||
this.autoInitExtensions(() => Promise.resolve([]));
|
||||
}
|
||||
|
||||
loadOnClusterManagerRenderer = () => {
|
||||
this.dependencies.logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||
|
||||
return this.autoInitExtensions(async (ext) => {
|
||||
const extension = ext as LensRendererExtension;
|
||||
const removeItems = [
|
||||
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
||||
];
|
||||
|
||||
this.onRemoveExtensionId.addListener((removedExtensionId) => {
|
||||
if (removedExtensionId === extension.id) {
|
||||
removeItems.forEach(remove => {
|
||||
remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return removeItems;
|
||||
});
|
||||
};
|
||||
|
||||
loadOnClusterRenderer = () => {
|
||||
this.dependencies.logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
||||
|
||||
this.autoInitExtensions(async () => []);
|
||||
};
|
||||
|
||||
protected async loadExtensions(installedExtensions: Map<string, InstalledExtension>, register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||
protected async loadExtensions(installedExtensions: Map<string, InstalledExtension>) {
|
||||
// Steps of the function:
|
||||
// 1. require and call .activate for each Extension
|
||||
// 2. Wait until every extension's onActivate has been resolved
|
||||
@@ -346,7 +312,7 @@ export class ExtensionLoader {
|
||||
|
||||
// Return ExtensionLoading[]
|
||||
return extensions.map(extension => {
|
||||
const loaded = extension.instance.enable(register).catch((err) => {
|
||||
const loaded = extension.instance.enable().catch((err) => {
|
||||
this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||
});
|
||||
|
||||
@@ -357,12 +323,14 @@ export class ExtensionLoader {
|
||||
});
|
||||
}
|
||||
|
||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||
autoInitExtensions() {
|
||||
this.dependencies.logger.info(`${logModule}: auto initializing extensions`);
|
||||
|
||||
// Setup reaction to load extensions on JSON changes
|
||||
reaction(() => this.toJSON(), installedExtensions => this.loadExtensions(installedExtensions, register));
|
||||
reaction(() => this.toJSON(), installedExtensions => this.loadExtensions(installedExtensions));
|
||||
|
||||
// Load initial extensions
|
||||
return this.loadExtensions(this.toJSON(), register);
|
||||
return this.loadExtensions(this.toJSON());
|
||||
}
|
||||
|
||||
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
||||
|
||||
@@ -12,9 +12,11 @@ import type { CatalogEntityRegistry as RendererCatalogEntityRegistry } from "../
|
||||
import type { GetExtensionPageParameters } from "../renderer/routes/get-extension-page-parameters.injectable";
|
||||
import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store";
|
||||
import type { NavigateForExtension } from "../main/start-main-application/lens-window/navigate-for-extension.injectable";
|
||||
import type { Logger } from "../common/logger";
|
||||
|
||||
export interface LensExtensionDependencies {
|
||||
readonly fileSystemProvisionerStore: FileSystemProvisionerStore;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export interface LensMainExtensionDependencies extends LensExtensionDependencies {
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
|
||||
import type { InstalledExtension } from "./extension-discovery/extension-discovery";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import logger from "../main/logger";
|
||||
import type { ProtocolHandlerRegistration } from "./registries";
|
||||
import type { PackageJson } from "type-fest";
|
||||
import type { Disposer } from "../common/utils";
|
||||
import { disposer } from "../common/utils";
|
||||
import type { LensExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||
import type { ProtocolHandlerRegistration } from "./common-api/registrations";
|
||||
|
||||
export type LensExtensionId = string; // path to manifest (package.json)
|
||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||
@@ -88,20 +86,13 @@ export class LensExtension<Dependencies extends LensExtensionDependencies = Lens
|
||||
}
|
||||
|
||||
@action
|
||||
async enable(register: (ext: this) => Promise<Disposer[]>) {
|
||||
async enable() {
|
||||
if (this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._isEnabled = true;
|
||||
|
||||
this[Disposers].push(...await register(this));
|
||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[EXTENSION]: failed to activate ${this.name}@${this.version}: ${error}`);
|
||||
}
|
||||
this._isEnabled = true;
|
||||
this[lensExtensionDependencies].logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -115,9 +106,9 @@ export class LensExtension<Dependencies extends LensExtensionDependencies = Lens
|
||||
try {
|
||||
await this.onDeactivate();
|
||||
this[Disposers]();
|
||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||
this[lensExtensionDependencies].logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||
} catch (error) {
|
||||
logger.error(`[EXTENSION]: disabling ${this.name}@${this.version} threw an error: ${error}`);
|
||||
this[lensExtensionDependencies].logger.error(`[EXTENSION]: disabling ${this.name}@${this.version} threw an error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type * as registries from "./registries";
|
||||
import { Disposers, LensExtension, lensExtensionDependencies } from "./lens-extension";
|
||||
import type { CatalogEntity } from "../common/catalog";
|
||||
import type { Disposer } from "../common/utils";
|
||||
@@ -30,11 +29,13 @@ import type { AppPreferenceTabRegistration } from "../features/preferences/rende
|
||||
import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||
import type { ClusterFrameChildComponent } from "../renderer/frames/cluster-frame/cluster-frame-child-component-injection-token";
|
||||
import type { EntitySettingRegistration } from "../renderer/components/+entity-settings/extension-registrator.injectable";
|
||||
import type { CatalogEntityDetailRegistration } from "../renderer/components/+catalog/entity-details/token";
|
||||
import type { ClusterPageMenuRegistration, PageRegistration } from "./common-api/registrations";
|
||||
|
||||
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
||||
globalPages: registries.PageRegistration[] = [];
|
||||
clusterPages: registries.PageRegistration[] = [];
|
||||
clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
|
||||
globalPages: PageRegistration[] = [];
|
||||
clusterPages: PageRegistration[] = [];
|
||||
clusterPageMenus: ClusterPageMenuRegistration[] = [];
|
||||
clusterFrameComponents: ClusterFrameChildComponent[] = [];
|
||||
kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
|
||||
appPreferences: AppPreferenceRegistration[] = [];
|
||||
@@ -47,7 +48,7 @@ export class LensRendererExtension extends LensExtension<LensRendererExtensionDe
|
||||
commands: CommandRegistration[] = [];
|
||||
welcomeMenus: WelcomeMenuRegistration[] = [];
|
||||
welcomeBanners: WelcomeBannerRegistration[] = [];
|
||||
catalogEntityDetailItems: registries.CatalogEntityDetailRegistration<CatalogEntity>[] = [];
|
||||
catalogEntityDetailItems: CatalogEntityDetailRegistration<CatalogEntity>[] = [];
|
||||
topBarItems: TopBarRegistration[] = [];
|
||||
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
|
||||
customCategoryViews: CustomCategoryViewRegistration[] = [];
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Base class for extensions-api registries
|
||||
import { action, observable, makeObservable } from "mobx";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
import { Singleton } from "../../common/utils";
|
||||
import type { LensExtension } from "../lens-extension";
|
||||
|
||||
export class BaseRegistry<T, I = T> extends Singleton {
|
||||
private items = observable.map<T, I>([], { deep: false });
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
getItems(): I[] {
|
||||
return Array.from(this.items.values());
|
||||
}
|
||||
|
||||
@action
|
||||
add(items: T | T[], extension?: LensExtension): Disposer {
|
||||
const itemArray = [items].flat() as T[];
|
||||
|
||||
itemArray.forEach(item => {
|
||||
this.items.set(item, this.getRegisteredItem(item, extension));
|
||||
});
|
||||
|
||||
return () => this.remove(...itemArray);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
protected getRegisteredItem(item: T, extension?: LensExtension): I {
|
||||
return item as never;
|
||||
}
|
||||
|
||||
@action
|
||||
remove(...items: T[]) {
|
||||
items.forEach(item => {
|
||||
this.items.delete(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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 { Disposer } from "../../common/utils";
|
||||
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>> {
|
||||
add<T extends CatalogEntity>(items: CatalogEntityDetailRegistration<T> | CatalogEntityDetailRegistration<T>[]): Disposer {
|
||||
return super.add(items as never);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// All registries managed by extensions api
|
||||
|
||||
export * from "./page-registry";
|
||||
export * from "./page-menu-registry";
|
||||
export * from "./catalog-entity-detail-registry";
|
||||
export * from "./protocol-handler";
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Extensions-api -> Register page menu items
|
||||
import type { IconProps } from "../../renderer/components/icon";
|
||||
import type React from "react";
|
||||
import type { PageTarget } from "./page-registry";
|
||||
import type { IComputedValue } from "mobx";
|
||||
|
||||
export interface ClusterPageMenuRegistration {
|
||||
id?: string;
|
||||
parentId?: string;
|
||||
target?: PageTarget;
|
||||
title: React.ReactNode;
|
||||
components: ClusterPageMenuComponents;
|
||||
visible?: IComputedValue<boolean>;
|
||||
}
|
||||
|
||||
export interface ClusterPageMenuComponents {
|
||||
Icon: React.ComponentType<IconProps>;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* 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 type { PageParamInit, PageParam } from "../../renderer/navigation";
|
||||
|
||||
// Extensions-api -> Custom page registration
|
||||
|
||||
export interface PageRegistration {
|
||||
/**
|
||||
* Page ID, part of extension's page url, must be unique within same extension
|
||||
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
|
||||
*/
|
||||
id?: string;
|
||||
params?: PageParams<string | Omit<PageParamInit<any>, "name" | "prefix">>;
|
||||
components: PageComponents;
|
||||
enabled?: IComputedValue<boolean>;
|
||||
}
|
||||
|
||||
export interface PageComponents {
|
||||
Page: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface PageTarget {
|
||||
extensionId?: string;
|
||||
pageId?: string;
|
||||
params?: PageParams;
|
||||
}
|
||||
|
||||
export interface PageParams<V = any> {
|
||||
[paramName: string]: V;
|
||||
}
|
||||
|
||||
export interface PageComponentProps<P extends PageParams = {}> {
|
||||
params?: {
|
||||
[N in keyof P]: PageParam<P[N]>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegisteredPage {
|
||||
id: string;
|
||||
extensionId: string;
|
||||
url: string; // registered extension's page URL (without page params)
|
||||
params: PageParams<PageParam<any>>; // normalized params
|
||||
components: PageComponents; // normalized components
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ProtocolHandlerRegistration is the data required for an extension to register
|
||||
* a handler to a specific path or dynamic path.
|
||||
*/
|
||||
export interface ProtocolHandlerRegistration {
|
||||
pathSchema: string;
|
||||
handler: RouteHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of the dynamic parts of a URI which initiated a `lens://`
|
||||
* protocol request
|
||||
*/
|
||||
export interface RouteParams {
|
||||
/**
|
||||
* the parts of the URI query string
|
||||
*/
|
||||
search: Record<string, string | undefined>;
|
||||
|
||||
/**
|
||||
* the matching parts of the path. The dynamic parts of the URI path.
|
||||
*/
|
||||
pathname: Record<string, string | undefined>;
|
||||
|
||||
/**
|
||||
* if the most specific path schema that is matched does not cover the whole
|
||||
* of the URI's path. Then this field will be set to the remaining path
|
||||
* segments.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* If the path schema `/landing/:type` is the matched schema for the URI
|
||||
* `/landing/soft/easy` then this field will be set to `"/easy"`.
|
||||
*/
|
||||
tail?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RouteHandler represents the function signature of the handler function for
|
||||
* `lens://` protocol routing.
|
||||
*/
|
||||
export interface RouteHandler {
|
||||
(params: RouteParams): void;
|
||||
}
|
||||
Reference in new issue
Block a user