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

simplify page/menu/registry implementation

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2020-11-13 09:27:48 +02:00
parent 97068f4c07
commit 6d0c07b3d1
14 changed files with 83 additions and 98 deletions

View File

@ -57,29 +57,29 @@ export class ExtensionLoader {
loadOnMain() { loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main') logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((ext: LensMainExtension) => [ this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { key: ext }) registries.menuRegistry.add(ext.appMenus)
]); ]);
} }
loadOnClusterManagerRenderer() { loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((ext: LensRendererExtension) => [ this.autoInitExtensions((ext: LensRendererExtension) => [
registries.globalPageRegistry.add(ext.globalPages, { key: ext }), registries.globalPageRegistry.add(ext.globalPages, ext),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }), registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }), registries.appPreferenceRegistry.add(ext.appPreferences),
registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }), registries.clusterFeatureRegistry.add(ext.clusterFeatures),
registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }), registries.statusBarRegistry.add(ext.statusBarItems),
]); ]);
} }
loadOnClusterRenderer() { loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((ext: LensRendererExtension) => [ this.autoInitExtensions((ext: LensRendererExtension) => [
registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }), registries.clusterPageRegistry.add(ext.clusterPages, ext),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }), registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }), registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }), registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext }) registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
]) ])
} }

View File

@ -1,12 +1,12 @@
import type React from "react" import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface AppPreferenceComponents { export interface AppPreferenceComponents {
Hint: React.ComponentType<any>; Hint: React.ComponentType<any>;
Input: React.ComponentType<any>; Input: React.ComponentType<any>;
} }
export interface AppPreferenceRegistration extends BaseRegistryItem { export interface AppPreferenceRegistration {
title: string; title: string;
components: AppPreferenceComponents; components: AppPreferenceComponents;
} }

View File

@ -1,65 +1,24 @@
// Base class for extensions-api registries // Base class for extensions-api registries
import { action, observable } from "mobx"; import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
import { getRandId } from "../../common/utils";
export type BaseRegistryKey = LensExtension | null; export class BaseRegistry<T = any> {
export type BaseRegistryItemId = string | symbol; protected items = observable<T>([], { deep: false });
export interface BaseRegistryItem { getItems(): T[] {
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided return this.items.toJS();
}
export interface BaseRegistryAddMeta {
key?: BaseRegistryKey;
merge?: boolean
}
export class BaseRegistry<T extends BaseRegistryItem = any> {
private items = observable.map<BaseRegistryKey, T[]>([], { deep: false });
getItems(): (T & { extension?: LensExtension | null })[] {
return Array.from(this.items).map(([ext, items]) => {
return items.map(item => ({
...item,
extension: ext,
}))
}).flat()
}
getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T {
const byId = (item: BaseRegistryItem) => item.id === itemId;
if (key) {
return this.items.get(key)?.find(byId)
}
return this.getItems().find(byId);
} }
@action @action
add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) { add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => { const normalizedItems = (Array.isArray(items) ? items : [items])
item.id = item.id || getRandId(); this.items.push(...normalizedItems);
return item; return () => this.remove(...normalizedItems);
});
if (merge && this.items.has(key)) {
const newItems = new Set(this.items.get(key));
normalizedItems.forEach(item => newItems.add(item))
this.items.set(key, [...newItems]);
} else {
this.items.set(key, normalizedItems);
}
return () => this.remove(normalizedItems, key)
} }
@action @action
remove(items: T[], key: BaseRegistryKey = null) { remove(...items: T[]) {
const storedItems = this.items.get(key); items.forEach(item => {
if (!storedItems) return; this.items.remove(item); // works because of {deep: false};
const newItems = storedItems.filter(item => !items.includes(item)); // works because of {deep: false}; })
if (newItems.length > 0) {
this.items.set(key, newItems)
} else {
this.items.delete(key);
}
} }
} }

View File

@ -1,12 +1,12 @@
import type React from "react" import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { ClusterFeature } from "../cluster-feature"; import { ClusterFeature } from "../cluster-feature";
export interface ClusterFeatureComponents { export interface ClusterFeatureComponents {
Description: React.ComponentType<any>; Description: React.ComponentType<any>;
} }
export interface ClusterFeatureRegistration extends BaseRegistryItem { export interface ClusterFeatureRegistration {
title: string; title: string;
components: ClusterFeatureComponents components: ClusterFeatureComponents
feature: ClusterFeature feature: ClusterFeature

View File

@ -1,11 +1,11 @@
import React from "react" import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectDetailComponents { export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>; Details: React.ComponentType<any>;
} }
export interface KubeObjectDetailRegistration extends BaseRegistryItem { export interface KubeObjectDetailRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
components: KubeObjectDetailComponents; components: KubeObjectDetailComponents;

View File

@ -1,11 +1,11 @@
import React from "react" import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectMenuComponents { export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<any>; MenuItem: React.ComponentType<any>;
} }
export interface KubeObjectMenuRegistration extends BaseRegistryItem { export interface KubeObjectMenuRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
components: KubeObjectMenuComponents; components: KubeObjectMenuComponents;

View File

@ -1,7 +1,7 @@
import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api"; import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface KubeObjectStatusRegistration extends BaseRegistryItem { export interface KubeObjectStatusRegistration {
kind: string; kind: string;
apiVersions: string[]; apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus; resolve: (object: KubeObject) => KubeObjectStatus;

View File

@ -1,11 +1,13 @@
// Extensions-api -> Register page menu items // Extensions-api -> Register page menu items
import type React from "react"; import type React from "react";
import { action } from "mobx";
import type { IconProps } from "../../renderer/components/icon"; import type { IconProps } from "../../renderer/components/icon";
import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
import { getPageUrl } from "./page-registry";
export interface PageMenuRegistration extends BaseRegistryItem { export interface PageMenuRegistration {
id: BaseRegistryItemId; // required id from page-registry item to match with
url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name" url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name"
title: React.ReactNode; title: React.ReactNode;
components: PageMenuComponents; components: PageMenuComponents;
@ -22,11 +24,17 @@ export interface PageMenuComponents {
} }
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> { export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
getItems() { @action
return super.getItems().map(item => { add(items: T[], ext?: LensExtension) {
item.url = item.extension.getPageUrl(item.url) const normalizedItems = items.map((i) => {
return item i.url = getPageUrl(ext, i.url)
}); return i
})
return super.add(normalizedItems);
}
getByRoutePath(routePath: string) {
return this.getItems().find((i) => i.url === routePath)
} }
} }

View File

@ -1,9 +1,12 @@
// Extensions-api -> Custom page registration // Extensions-api -> Custom page registration
import React from "react"; import React from "react";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { action } from "mobx";
import { compile } from "path-to-regexp";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension"
export interface PageRegistration extends BaseRegistryItem { export interface PageRegistration {
routePath?: string; // additional (suffix) route path to base extension's route: "/extension/:name" routePath?: string; // additional (suffix) route path to base extension's route: "/extension/:name"
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
components: PageComponents; components: PageComponents;
@ -20,12 +23,26 @@ export interface PageComponents {
Page: React.ComponentType<any>; Page: React.ComponentType<any>;
} }
const routePrefix = "/extension/:name"
export function getPageUrl(ext: LensExtension, baseUrl = "") {
const validUrlName = ext.name.replace("@", "").replace("/", "-");
return compile(routePrefix)({ name: validUrlName }) + baseUrl;
}
export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> { export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> {
getItems() {
return super.getItems().map(item => { @action
item.routePath = item.extension.getPageRoute(item.routePath) add(items: T[], ext?: LensExtension) {
return item const normalizedItems = items.map((i) => {
}); i.routePath = getPageUrl(ext, i.routePath)
return i
})
return super.add(normalizedItems);
}
getByUrl(url: string) {
return this.getItems().find((i) => i.routePath === url)
} }
} }

View File

@ -1,9 +1,9 @@
// Extensions API -> Status bar customizations // Extensions API -> Status bar customizations
import React from "react"; import React from "react";
import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface StatusBarRegistration extends BaseRegistryItem { export interface StatusBarRegistration {
item?: React.ReactNode; item?: React.ReactNode;
} }

View File

@ -74,11 +74,11 @@ export class App extends React.Component {
} }
renderExtensionRoutes() { renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => { return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath, subPages }) => {
const Component = () => { const Component = () => {
if (subPages) { if (subPages) {
const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => { const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => {
const menuItem = clusterPageMenuRegistry.getById(pageId); const menuItem = clusterPageMenuRegistry.getByRoutePath(routePath);
if (!menuItem) return; if (!menuItem) return;
return { return {
routePath, exact, routePath, exact,

View File

@ -70,7 +70,7 @@ export class ClusterManager extends React.Component {
<Route component={ClusterView} {...clusterViewRoute} /> <Route component={ClusterView} {...clusterViewRoute} />
<Route component={ClusterSettings} {...clusterSettingsRoute} /> <Route component={ClusterSettings} {...clusterSettingsRoute} />
{globalPageRegistry.getItems().map(({ routePath, exact, components: { Page } }) => { {globalPageRegistry.getItems().map(({ routePath, exact, components: { Page } }) => {
return <Route key={routePath} path={routePath} component={Page} exact={exact}/> return <Route key={routePath} path={routePath} component={Page} exact={true}/>
})} })}
<Redirect exact to={this.startUrl}/> <Redirect exact to={this.startUrl}/>
</Switch> </Switch>

View File

@ -148,8 +148,8 @@ export class ClustersMenu extends React.Component<Props> {
)} )}
</div> </div>
<div className="extensions"> <div className="extensions">
{globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { {globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getById(menuItemId); const registeredPage = globalPageRegistry.getByUrl(url);
if (!registeredPage) return; if (!registeredPage) return;
const { routePath, exact } = registeredPage; const { routePath, exact } = registeredPage;
return ( return (

View File

@ -191,8 +191,9 @@ export class Sidebar extends React.Component<Props> {
> >
{this.renderCustomResources()} {this.renderCustomResources()}
</SidebarNavItem> </SidebarNavItem>
{clusterPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { {clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
const registeredPage = clusterPageRegistry.getById(menuItemId); const registeredPage = clusterPageRegistry.getByUrl(url);
console.log(url, registeredPage)
if (!registeredPage) return; if (!registeredPage) return;
const { routePath, exact } = registeredPage; const { routePath, exact } = registeredPage;
return ( return (