From a489d9aabe91fec8b5a40c2b1aae6a42406d9a90 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 12 Nov 2020 13:58:05 +0200 Subject: [PATCH] more fixes & refactoring Signed-off-by: Roman --- .../capabilities/common-capabilities.md | 15 ++--- extensions/support-page/renderer.tsx | 2 +- package.json | 4 +- src/extensions/extension-loader.ts | 22 +++---- src/extensions/lens-extension.ts | 6 +- src/extensions/lens-renderer-extension.ts | 10 +-- .../registries/app-preference-registry.ts | 4 +- src/extensions/registries/base-registry.ts | 62 ++++++++++++------- .../registries/cluster-feature-registry.ts | 7 ++- .../registries/kube-object-detail-registry.ts | 4 +- .../registries/kube-object-menu-registry.ts | 4 +- .../registries/kube-object-status-registry.ts | 4 +- .../registries/page-menu-registry.ts | 24 ++++--- src/extensions/registries/page-registry.ts | 21 +++---- .../registries/status-bar-registry.ts | 4 +- src/renderer/components/app.tsx | 10 +-- .../cluster-manager/clusters-menu.tsx | 4 +- src/renderer/components/layout/sidebar.tsx | 4 +- 18 files changed, 109 insertions(+), 102 deletions(-) diff --git a/docs/extensions/capabilities/common-capabilities.md b/docs/extensions/capabilities/common-capabilities.md index 940f5462b4..84f1cbdffa 100644 --- a/docs/extensions/capabilities/common-capabilities.md +++ b/docs/extensions/capabilities/common-capabilities.md @@ -100,7 +100,6 @@ import { ExamplePage } from "./src/example-page" export default class ExampleRendererExtension extends LensRendererExtension { globalPages = [ { - routePath: "/items/:id?", components: { Page: ExamplePage, } @@ -109,7 +108,6 @@ export default class ExampleRendererExtension extends LensRendererExtension { globalPageMenus = [ { - url: "/items/1", title: "Example page", // used in icon's tooltip components: { Icon: () => , @@ -155,8 +153,8 @@ import { ExampleIcon, ExamplePage } from "./src/page" export default class ExampleExtension extends LensRendererExtension { clusterPages = [ { - routePath: "/extension-example", - exact: true, + routePath: "/extension-example", // optional + exact: true, // optional components: { Page: () => , } @@ -165,7 +163,7 @@ export default class ExampleExtension extends LensRendererExtension { clusterPageMenus = [ { - url: "/extension-example", + url: "/extension-example", // optional title: "Example Extension", components: { Icon: ExampleIcon, @@ -217,11 +215,8 @@ export default class ExampleExtension extends LensRendererExtension { statusBarItems = [ { item: ( -
Navigation.navigate("/example-page")} - > - +
this.navigate("/example-page")} > +
) } diff --git a/extensions/support-page/renderer.tsx b/extensions/support-page/renderer.tsx index b46eaa67aa..64b46b76b7 100644 --- a/extensions/support-page/renderer.tsx +++ b/extensions/support-page/renderer.tsx @@ -13,7 +13,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension } ] - statusBarItems = [ + statusBarItems: Interface.StatusBarRegistration[] = [ { item: (
this.navigate(pageUrl)}> diff --git a/package.json b/package.json index 4e7bdaee23..ddcfb9452c 100644 --- a/package.json +++ b/package.json @@ -241,8 +241,6 @@ "openid-client": "^3.15.2", "path-to-regexp": "^6.1.0", "proper-lockfile": "^4.1.1", - "react": "^16.14.0", - "react-router": "^5.2.0", "request": "^2.88.2", "request-promise-native": "^1.0.8", "semver": "^7.3.2", @@ -363,9 +361,11 @@ "postinstall-postinstall": "^2.1.0", "progress-bar-webpack-plugin": "^2.1.0", "raw-loader": "^4.0.1", + "react": "^16.14.0", "react-beautiful-dnd": "^13.0.0", "react-dom": "^16.13.1", "react-refresh": "^0.9.0", + "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-select": "^3.1.0", "react-window": "^1.8.5", diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index f38c7c3ae9..b75fd5147d 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -57,29 +57,29 @@ export class ExtensionLoader { loadOnMain() { logger.info('[EXTENSIONS-LOADER]: load on main') this.autoInitExtensions((ext: LensMainExtension) => [ - registries.menuRegistry.add(ext.appMenus, { ext }) + registries.menuRegistry.add(ext.appMenus, { key: ext }) ]); } loadOnClusterManagerRenderer() { logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') this.autoInitExtensions((ext: LensRendererExtension) => [ - registries.globalPageRegistry.add(ext.globalPages, { ext }), - registries.globalPageMenuRegistry.add(ext.globalPageMenus, { ext }), - registries.appPreferenceRegistry.add(ext.appPreferences, { ext }), - registries.clusterFeatureRegistry.add(ext.clusterFeatures, { ext }), - registries.statusBarRegistry.add(ext.statusBarItems, { ext }), + registries.globalPageRegistry.add(ext.globalPages, { key: ext }), + registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }), + registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }), + registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }), + registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }), ]); } loadOnClusterRenderer() { logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') this.autoInitExtensions((ext: LensRendererExtension) => [ - registries.clusterPageRegistry.add(ext.clusterPages, { ext }), - registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { ext }), - registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { ext }), - registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { ext }), - registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { ext }) + registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }), + registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }), + registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }), + registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }), + registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext }) ]) } diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index 3931831fbf..ca135a0b39 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -44,11 +44,11 @@ export class LensExtension { return this.manifest.description } - getPageUrl(baseUrl: string) { - return compile(this.routePrefix)({ name: this.name }) + baseUrl + getPageUrl(baseUrl = "") { + return compile(this.routePrefix)({ name: this.name }) + baseUrl; } - getPageRoute(baseRoute: string) { + getPageRoute(baseRoute = "") { return this.routePrefix + baseRoute; } diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index e330293105..86be06e17e 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -1,11 +1,7 @@ -import type { - AppPreferenceRegistration, ClusterFeatureRegistration, - KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, KubeObjectStatusRegistration, - PageRegistration, PageMenuRegistration, -} from "./registries" +import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries" +import { ipcRenderer } from "electron" import { observable } from "mobx"; import { LensExtension } from "./lens-extension" -import { ipcRenderer } from "electron" export class LensRendererExtension extends LensExtension { @observable.shallow globalPages: PageRegistration[] = [] @@ -20,6 +16,6 @@ export class LensRendererExtension extends LensExtension { @observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = [] navigate(location: string) { - ipcRenderer.emit("renderer:navigate", location) + ipcRenderer.emit("renderer:navigate", this.getPageUrl(location)) } } diff --git a/src/extensions/registries/app-preference-registry.ts b/src/extensions/registries/app-preference-registry.ts index 6c54911f82..dc15ec3e20 100644 --- a/src/extensions/registries/app-preference-registry.ts +++ b/src/extensions/registries/app-preference-registry.ts @@ -1,12 +1,12 @@ import type React from "react" -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; export interface AppPreferenceComponents { Hint: React.ComponentType; Input: React.ComponentType; } -export interface AppPreferenceRegistration { +export interface AppPreferenceRegistration extends BaseRegistryItem { title: string; components: AppPreferenceComponents; } diff --git a/src/extensions/registries/base-registry.ts b/src/extensions/registries/base-registry.ts index 1f362862d1..76678791f9 100644 --- a/src/extensions/registries/base-registry.ts +++ b/src/extensions/registries/base-registry.ts @@ -1,16 +1,24 @@ // Base class for extensions-api registries import { action, observable } from "mobx"; import { LensExtension } from "../lens-extension"; +import { getRandId } from "../../common/utils"; + +export type BaseRegistryKey = LensExtension | null; +export type BaseRegistryItemId = string | symbol; + +export interface BaseRegistryItem { + id?: BaseRegistryItemId; // uniq id, generated automatically when not provided +} export interface BaseRegistryAddMeta { - ext?: LensExtension | null; + key?: BaseRegistryKey; merge?: boolean } -export class BaseRegistry { - private items = observable.map([], { deep: false }); +export class BaseRegistry { + private items = observable.map([], { deep: false }); - getItems(): (T & { extension?: LensExtension })[] { + getItems(): (T & { extension?: LensExtension | null })[] { return Array.from(this.items).map(([ext, items]) => { return items.map(item => ({ ...item, @@ -19,29 +27,39 @@ export class BaseRegistry { }).flat() } - @action - add(items: T | T[], { ext = null, merge = true }: BaseRegistryAddMeta = {}) { - const itemsList: T[] = Array.isArray(items) ? items : [items]; - if (merge && this.items.has(ext)) { - const newItems = new Set(this.items.get(ext)); - itemsList.forEach(item => newItems.add(item)) - this.items.set(ext, [...newItems]); - } else { - this.items.set(ext, itemsList); + getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T { + const byId = (item: BaseRegistryItem) => item.id === itemId; + if (key) { + return this.items.get(key)?.find(byId) } - return () => this.remove(itemsList, ext) + return this.getItems().find(byId); } @action - remove(items: T[], key: LensExtension = null) { + add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) { + const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => { + item.id = item.id || getRandId(); + return item; + }); + 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 + remove(items: T[], key: BaseRegistryKey = null) { const storedItems = this.items.get(key); - if (storedItems) { - 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); - } + if (!storedItems) return; + 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); } } } diff --git a/src/extensions/registries/cluster-feature-registry.ts b/src/extensions/registries/cluster-feature-registry.ts index 0b579fd639..17a4ef1eef 100644 --- a/src/extensions/registries/cluster-feature-registry.ts +++ b/src/extensions/registries/cluster-feature-registry.ts @@ -1,17 +1,18 @@ import type React from "react" -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; import { ClusterFeature } from "../cluster-feature"; export interface ClusterFeatureComponents { Description: React.ComponentType; } -export interface ClusterFeatureRegistration { +export interface ClusterFeatureRegistration extends BaseRegistryItem { title: string; components: ClusterFeatureComponents feature: ClusterFeature } -export class ClusterFeatureRegistry extends BaseRegistry {} +export class ClusterFeatureRegistry extends BaseRegistry { +} export const clusterFeatureRegistry = new ClusterFeatureRegistry() diff --git a/src/extensions/registries/kube-object-detail-registry.ts b/src/extensions/registries/kube-object-detail-registry.ts index 32fea66b81..63d9cfa30d 100644 --- a/src/extensions/registries/kube-object-detail-registry.ts +++ b/src/extensions/registries/kube-object-detail-registry.ts @@ -1,11 +1,11 @@ import React from "react" -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; export interface KubeObjectDetailComponents { Details: React.ComponentType; } -export interface KubeObjectDetailRegistration { +export interface KubeObjectDetailRegistration extends BaseRegistryItem { kind: string; apiVersions: string[]; components: KubeObjectDetailComponents; diff --git a/src/extensions/registries/kube-object-menu-registry.ts b/src/extensions/registries/kube-object-menu-registry.ts index 8f527d6a3d..3e0cfb0251 100644 --- a/src/extensions/registries/kube-object-menu-registry.ts +++ b/src/extensions/registries/kube-object-menu-registry.ts @@ -1,11 +1,11 @@ import React from "react" -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; export interface KubeObjectMenuComponents { MenuItem: React.ComponentType; } -export interface KubeObjectMenuRegistration { +export interface KubeObjectMenuRegistration extends BaseRegistryItem { kind: string; apiVersions: string[]; components: KubeObjectMenuComponents; diff --git a/src/extensions/registries/kube-object-status-registry.ts b/src/extensions/registries/kube-object-status-registry.ts index 74fd8145d2..3a516a82e1 100644 --- a/src/extensions/registries/kube-object-status-registry.ts +++ b/src/extensions/registries/kube-object-status-registry.ts @@ -1,7 +1,7 @@ import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api"; -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; -export interface KubeObjectStatusRegistration { +export interface KubeObjectStatusRegistration extends BaseRegistryItem { kind: string; apiVersions: string[]; resolve: (object: KubeObject) => KubeObjectStatus; diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 41680dabf9..ea2f04d288 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -2,28 +2,26 @@ import type React from "react"; import type { IconProps } from "../../renderer/components/icon"; -import { BaseRegistry } from "./base-registry"; -import { matchPath } from "react-router"; +import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry"; -export interface PageMenuRegistration { - url: string; +export interface PageMenuRegistration extends BaseRegistryItem { + 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" title: React.ReactNode; components: PageMenuComponents; - subMenus?: Omit[]; + subMenus?: PageSubMenuRegistration[]; +} + +export interface PageSubMenuRegistration { + url: string; + title: React.ReactNode; } export interface PageMenuComponents { Icon: React.ComponentType; } -export class PageMenuRegistry extends BaseRegistry { - getByMatchingRoute(routePath: string | string[], exact?: boolean) { - return this.getItems().find(item => !!matchPath(item.url, { - path: routePath, - exact, - })) - } - +export class PageMenuRegistry extends BaseRegistry { getItems() { return super.getItems().map(item => { item.url = item.extension.getPageUrl(item.url) diff --git a/src/extensions/registries/page-registry.ts b/src/extensions/registries/page-registry.ts index 886a00ea6a..0169f99de5 100644 --- a/src/extensions/registries/page-registry.ts +++ b/src/extensions/registries/page-registry.ts @@ -1,14 +1,19 @@ // Extensions-api -> Custom page registration import React from "react"; -import { matchPath } from "react-router"; -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; -export interface PageRegistration { - routePath: string; // react-router's path, e.g. "/page/:id" +export interface PageRegistration extends BaseRegistryItem { + 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 components: PageComponents; - subPages?: Omit[]; + subPages?: SubPageRegistration[]; +} + +export interface SubPageRegistration { + routePath: string; // required for sub-pages + exact?: boolean; + components: PageComponents; } export interface PageComponents { @@ -16,12 +21,6 @@ export interface PageComponents { } export class PageRegistry extends BaseRegistry { - getByMatchingUrl(baseUrl: string) { - return this.getItems().find(({ routePath: path, exact }) => { - return !!matchPath(baseUrl, { path, exact }); - }) - } - getItems() { return super.getItems().map(item => { item.routePath = item.extension.getPageRoute(item.routePath) diff --git a/src/extensions/registries/status-bar-registry.ts b/src/extensions/registries/status-bar-registry.ts index 88c4132d30..c1029e6d68 100644 --- a/src/extensions/registries/status-bar-registry.ts +++ b/src/extensions/registries/status-bar-registry.ts @@ -1,9 +1,9 @@ // Extensions API -> Status bar customizations import React from "react"; -import { BaseRegistry } from "./base-registry"; +import { BaseRegistry, BaseRegistryItem } from "./base-registry"; -export interface StatusBarRegistration { +export interface StatusBarRegistration extends BaseRegistryItem { item?: React.ReactNode; } diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index e1cdb467a9..45b7d565f2 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -74,17 +74,17 @@ export class App extends React.Component { } renderExtensionRoutes() { - return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath, subPages }) => { + return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => { const Component = () => { if (subPages) { const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => { - const matchingUrl = clusterPageMenuRegistry.getByMatchingRoute(routePath, exact) - if (!matchingUrl) return; + const menuItem = clusterPageMenuRegistry.getById(pageId); + if (!menuItem) return; return { routePath, exact, component: Page, - url: matchingUrl.url, - title: matchingUrl.title, + url: menuItem.url, + title: menuItem.title, } }).filter(Boolean); if (tabs.length > 0) { diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 25c514254c..7844399227 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -149,8 +149,8 @@ export class ClustersMenu extends React.Component { )}
- {globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { - const registeredPage = globalPageRegistry.getByMatchingUrl(url); + {globalPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { + const registeredPage = globalPageRegistry.getById(menuItemId); if (!registeredPage) return; const { routePath, exact } = registeredPage; const isActive = !!matchPath(navigation.location.pathname, { path: routePath, exact }); diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 45b5189ce1..064a3744a1 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -180,8 +180,8 @@ export class Sidebar extends React.Component { > {this.renderCustomResources()} - {clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { - const registeredPage = clusterPageRegistry.getByMatchingUrl(url); + {clusterPageMenuRegistry.getItems().map(({ id: menuItemId, title, url, components: { Icon } }) => { + const registeredPage = clusterPageRegistry.getById(menuItemId); if (!registeredPage) return; const { routePath, exact } = registeredPage; return (