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

PageRegistration & BaseRegistry refactoring #1334

Merged
merged 15 commits into from Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 23 additions & 5 deletions docs/extensions/capabilities/common-capabilities.md
Expand Up @@ -100,13 +100,22 @@ import { ExamplePage } from "./src/example-page"
export default class ExampleRendererExtension extends LensRendererExtension {
globalPages = [
{
path: "/example-route",
hideInMenu: true,
routePath: "/items/:id?",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems cumbersome if the extension developer has to specify this. Can it default to this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just an example. Now could be optional since we have route-prefix for all extensions.

components: {
Page: ExamplePage,
}
}
]

globalPageMenus = [
{
url: "/items/1",
title: "Example page", // used in icon's tooltip
components: {
Icon: () => <Component.Icon material="arrow"/>,
}
}
]
}
```

Expand Down Expand Up @@ -146,11 +155,20 @@ import { ExampleIcon, ExamplePage } from "./src/page"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
path: "/extension-example",
title: "Example Extension",
routePath: "/extension-example",
exact: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can default be true? So developer doesn't have to set it unless they want it false (which assumes they know what they are doing)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional. Could be default whatever we want :)

components: {
Page: () => <ExamplePage extension={this}/>,
MenuIcon: ExampleIcon,
}
}
]

clusterPageMenus = [
{
url: "/extension-example",
title: "Example Extension",
components: {
Icon: ExampleIcon,
}
}
]
Expand Down
4 changes: 1 addition & 3 deletions docs/extensions/get-started/anatomy.md
Expand Up @@ -84,11 +84,9 @@ import React from "react"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
path: "/extension-example",
title: "Hello World",
routePath: "/extension-example",
components: {
Page: () => <ExamplePage extension={this}/>,
MenuIcon: ExampleIcon,
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions extensions/support-page/main.ts
@@ -1,13 +1,13 @@
import { LensMainExtension } from "@k8slens/extensions";
import { supportPageURL } from "./src/support.route";
import { pageUrl } from "./src/common-vars";

export default class SupportPageMainExtension extends LensMainExtension {
appMenus = [
{
parentId: "help",
label: "Support",
click: () => {
this.navigate(supportPageURL());
this.navigate(pageUrl);
}
}
]
Expand Down
17 changes: 6 additions & 11 deletions extensions/support-page/renderer.tsx
@@ -1,14 +1,12 @@
import React from "react";
import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
import { supportPageRoute, supportPageURL } from "./src/support.route";
import { Component, Interface, LensRendererExtension } from "@k8slens/extensions";
import { Support } from "./src/support";
import { pageRoute, pageUrl } from "./src/common-vars";

export default class SupportPageRendererExtension extends LensRendererExtension {
globalPages = [
globalPages: Interface.PageRegistration[] = [
{
...supportPageRoute,
url: supportPageURL(),
hideInMenu: true,
routePath: pageRoute,
components: {
Page: Support,
}
Expand All @@ -18,11 +16,8 @@ export default class SupportPageRendererExtension extends LensRendererExtension
statusBarItems = [
{
item: (
<div
className="flex align-center gaps hover-highlight"
onClick={() => Navigation.navigate(supportPageURL())}
>
<Component.Icon material="help" smallest />
<div className="flex align-center gaps hover-highlight" onClick={() => this.navigate(pageUrl)}>
<Component.Icon material="help" smallest/>
</div>
)
}
Expand Down
4 changes: 4 additions & 0 deletions extensions/support-page/src/common-vars.ts
@@ -0,0 +1,4 @@
// Common variables for both processes (main & renderer)

export const pageRoute = "/support"
export const pageUrl = pageRoute; // same since no special :placeholder-s for react-router's route
7 changes: 0 additions & 7 deletions extensions/support-page/src/support.route.ts

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -241,6 +241,8 @@
"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",
Copy link
Member Author

@ixrock ixrock Nov 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is moved to normal dependencies since required in runtime (matchPath is used in page-registry.ts and page-menu-registry.ts)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react apparently also required internally in react-router when used in runtime

"request": "^2.88.2",
"request-promise-native": "^1.0.8",
"semver": "^7.3.2",
Expand Down Expand Up @@ -361,11 +363,9 @@
"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",
Expand Down
15 changes: 0 additions & 15 deletions src/extensions/dynamic-page.tsx

This file was deleted.

27 changes: 14 additions & 13 deletions src/extensions/extension-loader.ts
Expand Up @@ -56,30 +56,31 @@ export class ExtensionLoader {

loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((extension: LensMainExtension) => [
registries.menuRegistry.add(...extension.appMenus)
this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus, { ext })
]);
}

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

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

}

protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
Expand Down
1 change: 1 addition & 0 deletions src/extensions/interfaces/registrations.ts
Expand Up @@ -4,4 +4,5 @@ export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from ".
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
export type { PageRegistration, PageComponents } from "../registries/page-registry"
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"
export type { StatusBarRegistration } from "../registries/status-bar-registry"
10 changes: 10 additions & 0 deletions src/extensions/lens-extension.ts
@@ -1,5 +1,6 @@
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 @@ -14,6 +15,7 @@ export interface LensExtensionManifest {
}

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

getPageUrl(baseUrl: string) {
return compile(this.routePrefix)({ name: this.name }) + baseUrl
}

getPageRoute(baseRoute: string) {
return this.routePrefix + baseRoute;
}

@action
async enable() {
if (this.isEnabled) return;
Expand Down
4 changes: 3 additions & 1 deletion src/extensions/lens-main-extension.ts
Expand Up @@ -7,6 +7,8 @@ export class LensMainExtension extends LensExtension {
@observable.shallow appMenus: MenuRegistration[] = []

async navigate(location: string, frameId?: number) {
await WindowManager.getInstance<WindowManager>().navigate(location, frameId)
const windowManager = WindowManager.getInstance<WindowManager>();
const url = this.getPageUrl(location);
await windowManager.navigate(url, frameId)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot but @jakolehm couldn't get it to work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot but @jakolehm couldn't get it to work?

It works on main extension.

}
13 changes: 10 additions & 3 deletions src/extensions/lens-renderer-extension.ts
@@ -1,18 +1,25 @@
import type {
AppPreferenceRegistration, ClusterFeatureRegistration,
KubeObjectMenuRegistration, KubeObjectDetailRegistration,
PageRegistration, StatusBarRegistration, KubeObjectStatusRegistration
KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, KubeObjectStatusRegistration,
PageRegistration, PageMenuRegistration,
} from "./registries"
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"

export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
@observable.shallow clusterPages: PageRegistration[] = []
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
@observable.shallow clusterPageMenus: PageMenuRegistration[] = []
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
@observable.shallow statusBarItems: StatusBarRegistration[] = []
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []

async navigate(location: string){
const { navigate } = await import("../renderer/navigation");
navigate(this.getPageUrl(location));
}
}
46 changes: 35 additions & 11 deletions src/extensions/registries/base-registry.ts
@@ -1,23 +1,47 @@
// Base class for extensions-api registries
import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";

export class BaseRegistry<T = any> {
protected items = observable<T>([], { deep: false });
export interface BaseRegistryAddMeta {
ext?: LensExtension | null;
merge?: boolean
}

export class BaseRegistry<T extends object = any> {
private items = observable.map<LensExtension, T[]>([], { deep: false });

getItems(): T[] {
return this.items.toJS();
getItems(): (T & { extension?: LensExtension })[] {
return Array.from(this.items).map(([ext, items]) => {
return items.map(item => ({
...item,
extension: ext,
}))
}).flat()
}

@action
add(...items: T[]) {
this.items.push(...items);
return () => this.remove(...items);
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);
}
return () => this.remove(itemsList, ext)
}

@action
remove(...items: T[]) {
items.forEach(item => {
this.items.remove(item); // works because of {deep: false};
})
remove(items: T[], key: LensExtension = null) {
const storedItems = this.items.get(key);
if (storedItems) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be also be

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);
}
}
}
}
1 change: 1 addition & 0 deletions src/extensions/registries/cluster-feature-registry.ts
@@ -1,3 +1,4 @@
import type React from "react"
import { BaseRegistry } from "./base-registry";
import { ClusterFeature } from "../cluster-feature";

Expand Down
1 change: 1 addition & 0 deletions src/extensions/registries/index.ts
@@ -1,6 +1,7 @@
// All registries managed by extensions api

export * from "./page-registry"
export * from "./page-menu-registry"
export * from "./menu-registry"
export * from "./app-preference-registry"
export * from "./status-bar-registry"
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/registries/kube-object-detail-registry.ts
Expand Up @@ -13,7 +13,7 @@ export interface KubeObjectDetailRegistration {

export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.items.filter((item) => {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/registries/kube-object-menu-registry.ts
Expand Up @@ -13,7 +13,7 @@ export interface KubeObjectMenuRegistration {

export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.items.filter((item) => {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/registries/kube-object-status-registry.ts
Expand Up @@ -9,7 +9,7 @@ export interface KubeObjectStatusRegistration {

export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.items.filter((item) => {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
Expand Down