Skip to content

Commit

Permalink
Kludge "isEnabledForCluster" work again for kube object details
Browse files Browse the repository at this point in the history
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
  • Loading branch information
jansav committed Jul 11, 2022
1 parent b747cce commit 591f8ed
Show file tree
Hide file tree
Showing 49 changed files with 2,866 additions and 512 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
import { getExtensionFakeFor } from "../../../../renderer/components/test-utils/get-extension-fake";
import { getInjectable } from "@ogre-tools/injectable";
import { frontEndRouteInjectionToken } from "../../../../common/front-end-routing/front-end-route-injection-token";
import { computed } from "mobx";
import React from "react";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import extensionShouldBeEnabledForClusterFrameInjectable from "../../../../renderer/extension-loader/extension-should-be-enabled-for-cluster-frame.injectable";
import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable";
import { KubeObjectDetails } from "../../../../renderer/components/kube-object-details";
import type { ApiManager } from "../../../../common/k8s-api/api-manager";

describe("disable kube object detail items when cluster is not relevant", () => {
let builder: ApplicationBuilder;
let rendered: RenderResult;
let isEnabledForClusterMock: AsyncFnMock<
(cluster: KubernetesCluster) => Promise<boolean>
>;

beforeEach(async () => {
builder = getApplicationBuilder();

builder.beforeApplicationStart(({ rendererDi }) => {
rendererDi.override(
apiManagerInjectable,
() =>
({
getStore: () => ({
getByPath: () =>
getKubeObjectStub("some-kind", "some-api-version"),
}),
} as unknown as ApiManager),
);
});

const rendererDi = builder.dis.rendererDi;

rendererDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);

rendererDi.register(testRouteInjectable, testRouteComponentInjectable);

builder.setEnvironmentToClusterFrame();

const getExtensionFake = getExtensionFakeFor(builder);

isEnabledForClusterMock = asyncFn();

const testExtension = getExtensionFake({
id: "test-extension-id",
name: "test-extension",

rendererOptions: {
isEnabledForCluster: isEnabledForClusterMock,

kubeObjectDetailItems: [
{
kind: "some-kind",
apiVersions: ["some-api-version"],
components: {
Details: () => (
<div data-testid="some-kube-object-detail-item">
Some detail
</div>
),
},
},
],
},
});

rendered = await builder.render();

const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
const testRoute = rendererDi.inject(testRouteInjectable);

navigateToRoute(testRoute);

builder.extensions.enable(testExtension);
});

describe("given not yet known if extension should be enabled for the cluster, when navigating", () => {
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});

it("does not show the kube object detail item", () => {
const actual = rendered.queryByTestId("some-kube-object-detail-item");

expect(actual).not.toBeInTheDocument();
});
});

describe("given extension shouldn't be enabled for the cluster, when navigating", () => {
beforeEach(async () => {
await isEnabledForClusterMock.resolve(false);
});

it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});

it("does not show the kube object detail item", () => {
const actual = rendered.queryByTestId("some-kube-object-detail-item");

expect(actual).not.toBeInTheDocument();
});
});

describe("given extension should be enabled for the cluster, when navigating", () => {
beforeEach(async () => {
await isEnabledForClusterMock.resolve(true);
});

it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});

it("shows the kube object detail item", () => {
const actual = rendered.getByTestId("some-kube-object-detail-item");

expect(actual).toBeInTheDocument();
});
});
});

const testRouteInjectable = getInjectable({
id: "test-route",

instantiate: () => ({
path: "/test-route",
clusterFrame: true,
isEnabled: computed(() => true),
}),

injectionToken: frontEndRouteInjectionToken,
});

const testRouteComponentInjectable = getInjectable({
id: "test-route-component",

instantiate: (di) => ({
route: di.inject(testRouteInjectable),

Component: () => <KubeObjectDetails />,
}),

injectionToken: routeSpecificComponentInjectionToken,
});

const getKubeObjectStub = (kind: string, apiVersion: string) =>
KubeObject.create({
apiVersion,
kind,
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: "some-namespace",
selfLink: "",
},
});
2 changes: 1 addition & 1 deletion src/extensions/common-api/registrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
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";
Expand Down
28 changes: 3 additions & 25 deletions src/extensions/extension-loader/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../..
import type { Disposer } from "../../common/utils";
import { isDefined, toJS } from "../../common/utils";
import logger from "../../main/logger";
import type { CatalogEntity, KubernetesCluster } from "../common-api/catalog";
import type { KubernetesCluster } from "../common-api/catalog";
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
import type { LensRendererExtension } from "../lens-renderer-extension";
Expand Down Expand Up @@ -275,32 +275,10 @@ export class ExtensionLoader {
});
};

loadOnClusterRenderer = (getCluster: () => CatalogEntity) => {
loadOnClusterRenderer = () => {
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);

this.autoInitExtensions(async (ext) => {
const entity = getCluster() as KubernetesCluster;
const extension = ext as LensRendererExtension;

// getCluster must be a callback, as the entity might be available only after an extension has been loaded
if (await this.dependencies.getExtensionIsEnabledForCluster(extension, entity)) {
return [];
}

const removeItems = [
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
];

this.onRemoveExtensionId.addListener((removedExtensionId) => {
if (removedExtensionId === extension.id) {
removeItems.forEach(remove => {
remove();
});
}
});

return removeItems;
});
this.autoInitExtensions(async () => []);
};

protected async loadExtensions(installedExtensions: Map<string, InstalledExtension>, register: (ext: LensExtension) => Promise<Disposer[]>) {
Expand Down
3 changes: 2 additions & 1 deletion src/extensions/lens-renderer-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getExtensionRoutePath } from "../renderer/routes/for-extension";
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler";
import type { AppPreferenceTabRegistration } from "../renderer/components/+preferences/app-preference-tab/app-preference-tab-registration";
import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration";

export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
globalPages: registries.PageRegistration[] = [];
Expand All @@ -37,7 +38,7 @@ export class LensRendererExtension extends LensExtension<LensRendererExtensionDe
appPreferenceTabs: AppPreferenceTabRegistration[] = [];
entitySettings: registries.EntitySettingRegistration[] = [];
statusBarItems: StatusBarRegistration[] = [];
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
kubeObjectDetailItems: KubeObjectDetailRegistration[] = [];
kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
kubeWorkloadsOverviewItems: WorkloadsOverviewDetailRegistration[] = [];
commands: CommandRegistration[] = [];
Expand Down
6 changes: 0 additions & 6 deletions src/renderer/api/kube-object-detail-registry.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/renderer/bootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,6 @@ export async function bootstrap(di: DiContainer) {
logger.info(`${logPrefix} initializing EntitySettingsRegistry`);
initializers.initEntitySettingsRegistry();

logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
initializers.initKubeObjectDetailRegistry();

logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`);
initializers.initCatalogEntityDetailRegistry();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const FlexVolume: VolumeVariantComponent<"flexVolume"> = (
{readOnly.toString()}
</DrawerItem>
{
...Object.entries(options)
Object.entries(options)
.map(([key, value]) => (
<DrawerItem key={key} name={`Option: ${key}`}>
{value}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import kubeDetailsUrlParamInjectable from "../kube-detail-params/kube-details-url.injectable";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
import loggerInjectable from "../../../common/logger.injectable";

const currentKubeObjectInDetailsInjectable = getInjectable({
id: "current-kube-object-in-details",

instantiate: (di) => {
const urlParam = di.inject(kubeDetailsUrlParamInjectable);
const apiManager = di.inject(apiManagerInjectable);
const logger = di.inject(loggerInjectable);

return computed(() => {
const path = urlParam.get();

try {
return apiManager.getStore(path)?.getByPath(path);
} catch (error) {
logger.error(
`[KUBE-OBJECT-DETAILS]: failed to get store or object ${path}: ${error}`,
);

return undefined;
}
});
},
});

export default currentKubeObjectInDetailsInjectable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { kubeObjectDetailItemInjectionToken } from "../kube-object-detail-item-injection-token";
import { computed } from "mobx";
import { ClusterRoleBindingDetails } from "../../../+user-management/+cluster-role-bindings";
import currentKubeObjectInDetailsInjectable from "../../current-kube-object-in-details.injectable";
import { kubeObjectMatchesToKindAndApiVersion } from "../kube-object-matches-to-kind-and-api-version";

const clusterRoleBindingDetailItemInjectable = getInjectable({
id: "cluster-role-binding-detail-item",

instantiate: (di) => {
const kubeObject = di.inject(currentKubeObjectInDetailsInjectable);

return {
Component: ClusterRoleBindingDetails,
enabled: computed(() => isClusterRoleBinding(kubeObject.get())),
orderNumber: 10,
};
},

injectionToken: kubeObjectDetailItemInjectionToken,
});

export default clusterRoleBindingDetailItemInjectable;

export const isClusterRoleBinding = kubeObjectMatchesToKindAndApiVersion(
"ClusterRoleBinding",
["rbac.authorization.k8s.io/v1"],
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { kubeObjectDetailItemInjectionToken } from "../kube-object-detail-item-injection-token";
import { computed } from "mobx";
import { ClusterRoleDetails } from "../../../+user-management/+cluster-roles";
import currentKubeObjectInDetailsInjectable from "../../current-kube-object-in-details.injectable";
import { kubeObjectMatchesToKindAndApiVersion } from "../kube-object-matches-to-kind-and-api-version";

const clusterRoleDetailItemInjectable = getInjectable({
id: "cluster-role-detail-item",

instantiate: (di) => {
const kubeObject = di.inject(currentKubeObjectInDetailsInjectable);

return {
Component: ClusterRoleDetails,
enabled: computed(() => isClusterRole(kubeObject.get())),
orderNumber: 10,
};
},

injectionToken: kubeObjectDetailItemInjectionToken,
});

export default clusterRoleDetailItemInjectable;

export const isClusterRole = kubeObjectMatchesToKindAndApiVersion(
"ClusterRole",
["rbac.authorization.k8s.io/v1"],
);
Loading

0 comments on commit 591f8ed

Please sign in to comment.