Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify pages/menus/registry extension api internal implementation #1364

Merged
merged 13 commits into from
Nov 13, 2020
4 changes: 3 additions & 1 deletion extensions/support-page/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { SupportPage } from "./src/support";
export default class SupportPageRendererExtension extends LensRendererExtension {
globalPages: Interface.PageRegistration[] = [
{
id: "support",
routePath: "/support",
components: {
Page: SupportPage,
}
Expand All @@ -14,7 +16,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
statusBarItems: Interface.StatusBarRegistration[] = [
{
item: (
<div className="SupportPageIcon flex align-center" onClick={() => this.navigate()}>
<div className="SupportPageIcon flex align-center" onClick={() => this.navigate("/support")}>
<Component.Icon interactive material="help" smallest/>
</div>
)
Expand Down
23 changes: 23 additions & 0 deletions src/extensions/__tests__/lens-extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LensExtension } from "../lens-extension"

let ext: LensExtension = null

describe("lens extension", () => {
beforeEach(async () => {
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1"
},
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})

describe("name", () => {
it("returns name", () => {
expect(ext.name).toBe("foo-bar")
})
})
})
22 changes: 11 additions & 11 deletions src/extensions/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,29 @@ export class ExtensionLoader {
loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { key: ext })
registries.menuRegistry.add(ext.appMenus)
]);
}

loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((ext: LensRendererExtension) => [
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 }),
registries.globalPageRegistry.add(ext.globalPages, ext),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
registries.appPreferenceRegistry.add(ext.appPreferences),
registries.clusterFeatureRegistry.add(ext.clusterFeatures),
registries.statusBarRegistry.add(ext.statusBarItems),
]);
}

loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((ext: LensRendererExtension) => [
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 })
registries.clusterPageRegistry.add(ext.clusterPages, ext),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
])
}

Expand Down
10 changes: 0 additions & 10 deletions src/extensions/lens-extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { InstalledExtension } from "./extension-manager";
import { action, observable, reaction } from "mobx";
import { compile } from "path-to-regexp"
import logger from "../main/logger";

export type LensExtensionId = string; // path to manifest (package.json)
Expand All @@ -15,7 +14,6 @@ export interface LensExtensionManifest {
}

export class LensExtension {
readonly routePrefix = "/extension/:name"
readonly manifest: LensExtensionManifest;
readonly manifestPath: string;
readonly isBundled: boolean;
Expand Down Expand Up @@ -44,14 +42,6 @@ export class LensExtension {
return this.manifest.description
}

getPageUrl(baseUrl = "") {
return compile(this.routePrefix)({ name: this.name }) + baseUrl;
}

getPageRoute(baseRoute = "") {
return this.routePrefix + baseRoute;
}

@action
async enable() {
if (this.isEnabled) return;
Expand Down
3 changes: 2 additions & 1 deletion src/extensions/lens-main-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import type { MenuRegistration } from "./registries/menu-registry";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { WindowManager } from "../main/window-manager";
import { getPageUrl } from "./registries/page-registry"

export class LensMainExtension extends LensExtension {
@observable.shallow appMenus: MenuRegistration[] = []

async navigate(location?: string, frameId?: number) {
const windowManager = WindowManager.getInstance<WindowManager>();
const url = this.getPageUrl(location); // get full path to extension's page
const url = getPageUrl(this, location); // get full path to extension's page
await windowManager.navigate(url, frameId);
}
}
3 changes: 2 additions & 1 deletion src/extensions/lens-renderer-extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { getPageUrl } from "./registries/page-registry"

export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
Expand All @@ -16,6 +17,6 @@ export class LensRendererExtension extends LensExtension {

async navigate(location?: string) {
const { navigate } = await import("../renderer/navigation");
navigate(this.getPageUrl(location));
navigate(getPageUrl(this, location));
}
}
31 changes: 31 additions & 0 deletions src/extensions/registries/__tests__/page-registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getPageUrl } from "../page-registry"
import { LensExtension } from "../../lens-extension"

let ext: LensExtension = null

describe("getPageUrl", () => {
beforeEach(async () => {
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1"
},
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})

it("returns a page url for extension", () => {
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
})

it("allows to pass base url as parameter", () => {
expect(getPageUrl(ext, "/test")).toBe("/extension/foo-bar/test")
})

it("removes @", () => {
ext.manifest.name = "@foo/bar"
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
})
})
4 changes: 2 additions & 2 deletions src/extensions/registries/app-preference-registry.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { BaseRegistry } from "./base-registry";

export interface AppPreferenceComponents {
Hint: React.ComponentType<any>;
Input: React.ComponentType<any>;
}

export interface AppPreferenceRegistration extends BaseRegistryItem {
export interface AppPreferenceRegistration {
title: string;
components: AppPreferenceComponents;
}
Expand Down
65 changes: 12 additions & 53 deletions src/extensions/registries/base-registry.ts
Original file line number Diff line number Diff line change
@@ -1,65 +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 class BaseRegistry<T = any> {
private items = observable<T>([], { deep: false });

export interface BaseRegistryItem {
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided
}

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);
getItems(): T[] {
return this.items.toJS();
}

@action
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)
add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items])
this.items.push(...normalizedItems);
return () => this.remove(...normalizedItems);
}

@action
remove(items: T[], key: BaseRegistryKey = null) {
const storedItems = this.items.get(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);
}
remove(...items: T[]) {
items.forEach(item => {
this.items.remove(item); // works because of {deep: false};
})
}
}
4 changes: 2 additions & 2 deletions src/extensions/registries/cluster-feature-registry.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { BaseRegistry } from "./base-registry";
import { ClusterFeature } from "../cluster-feature";

export interface ClusterFeatureComponents {
Description: React.ComponentType<any>;
}

export interface ClusterFeatureRegistration extends BaseRegistryItem {
export interface ClusterFeatureRegistration {
title: string;
components: ClusterFeatureComponents
feature: ClusterFeature
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/registries/kube-object-detail-registry.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { BaseRegistry } from "./base-registry";

export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>;
}

export interface KubeObjectDetailRegistration extends BaseRegistryItem {
export interface KubeObjectDetailRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectDetailComponents;
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/registries/kube-object-menu-registry.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react"
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
import { BaseRegistry } from "./base-registry";

export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<any>;
}

export interface KubeObjectMenuRegistration extends BaseRegistryItem {
export interface KubeObjectMenuRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectMenuComponents;
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/registries/kube-object-status-registry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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;
apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus;
Expand Down
30 changes: 20 additions & 10 deletions src/extensions/registries/page-menu-registry.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
// Extensions-api -> Register page menu items

import type React from "react";
import { action } from "mobx";
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 { PageRegistration } from "../interfaces";

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"
export interface PageMenuTarget {
pageId: string;
extensionId: string;
params: object;
}

export interface PageMenuRegistration {
target?: PageMenuTarget;
title: React.ReactNode;
components: PageMenuComponents;
subMenus?: PageSubMenuRegistration[];
}

export interface PageSubMenuRegistration {
Expand All @@ -22,11 +29,14 @@ export interface PageMenuComponents {
}

export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
getItems() {
return super.getItems().map(item => {
item.url = item.extension.getPageUrl(item.url)
return item
});

@action
add(items: T[], ext?: LensExtension) {
const normalizedItems = items.map((i) => {
i.target.extensionId = ext.name
return i
})
return super.add(normalizedItems);
}
}

Expand Down
Loading