Skip to content

Commit

Permalink
Kludge "isEnabledForCluster" work again for cluster pages
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 12, 2022
1 parent 27cb7d1 commit fb6ad38
Show file tree
Hide file tree
Showing 10 changed files with 1,683 additions and 7 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* 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 { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import type { TestExtensionRenderer } from "../../../renderer/components/test-utils/get-extension-fake";
import { getExtensionFakeFor } from "../../../renderer/components/test-utils/get-extension-fake";
import type { KubernetesCluster } from "../../../common/catalog-entities";
import React from "react";
import extensionShouldBeEnabledForClusterFrameInjectable from "../../../renderer/extension-loader/extension-should-be-enabled-for-cluster-frame.injectable";

describe("disable-cluster-pages-when-cluster-is-not-relevant", () => {
let builder: ApplicationBuilder;
let rendered: RenderResult;
let rendererTestExtension: TestExtensionRenderer;
let isEnabledForClusterMock: AsyncFnMock<(cluster: KubernetesCluster) => Promise<boolean>>;

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

builder.setEnvironmentToClusterFrame();

builder.dis.rendererDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);

const getExtensionFake = getExtensionFakeFor(builder);

isEnabledForClusterMock = asyncFn();

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

rendererOptions: {
isEnabledForCluster: isEnabledForClusterMock,

clusterPages: [{
components: {
Page: () => <div data-testid="some-test-page">Some page</div>,
},
}],
},
});

rendererTestExtension = testExtension.renderer;

rendered = await builder.render();

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

describe("given not yet known if extension should be enabled for the cluster, when navigating", () => {
beforeEach(() => {
rendererTestExtension.navigate();
});

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

it("does not show the page", () => {
const actual = rendered.queryByTestId("some-test-page");

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

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

rendererTestExtension.navigate();
});

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

it("does not show the page", () => {
const actual = rendered.queryByTestId("some-test-page");

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

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

rendererTestExtension.navigate();
});

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

it("shows the page", () => {
const actual = rendered.getByTestId("some-test-page");

expect(actual).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { KubernetesCluster } from "../../common/catalog-entities";
import type { LensRendererExtension } from "../lens-renderer-extension";

interface ExtensionIsEnabledForCluster {
extension: LensRendererExtension;
cluster: KubernetesCluster;
}

const extensionIsEnabledForClusterInjectable = getInjectable({
id: "extension-is-enabled-for-cluster",

instantiate: async (
di,
{ extension, cluster }: ExtensionIsEnabledForCluster,
) => (await extension.isEnabledForCluster(cluster)) as boolean,

lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (
di,
{ extension, cluster }: ExtensionIsEnabledForCluster,
) => `${extension.sanitizedExtensionId}-${cluster.getId()}`,
}),
});

export default extensionIsEnabledForClusterInjectable;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { createExtensionInstanceInjectionToken } from "./create-extension-instan
import extensionInstancesInjectable from "./extension-instances.injectable";
import type { LensExtension } from "../lens-extension";
import extensionInjectable from "./extension/extension.injectable";
import type { LensRendererExtension } from "../lens-renderer-extension";
import extensionIsEnabledForClusterInjectable from "./extension-is-enabled-for-cluster.injectable";
import type { KubernetesCluster } from "../../common/catalog-entities";

const extensionLoaderInjectable = getInjectable({
id: "extension-loader",
Expand All @@ -18,6 +21,11 @@ const extensionLoaderInjectable = getInjectable({
createExtensionInstance: di.inject(createExtensionInstanceInjectionToken),
extensionInstances: di.inject(extensionInstancesInjectable),
getExtension: (instance: LensExtension) => di.inject(extensionInjectable, instance),

getExtensionIsEnabledForCluster: (extension: LensRendererExtension, cluster: KubernetesCluster) => di.inject(extensionIsEnabledForClusterInjectable, {
extension,
cluster,
}),
}),
});

Expand Down
5 changes: 4 additions & 1 deletion src/extensions/extension-loader/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface Dependencies {
createExtensionInstance: CreateExtensionInstance;
readonly extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
getExtension: (instance: LensExtension) => Extension;
getExtensionIsEnabledForCluster: (extension: LensRendererExtension, cluster: KubernetesCluster) => Promise<boolean>;
}

export interface ExtensionLoading {
Expand Down Expand Up @@ -282,7 +283,9 @@ export class ExtensionLoader {
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 extension.isEnabledForCluster(entity)) === false) {
const extensionIsEnabledForCluster = await this.dependencies.getExtensionIsEnabledForCluster(extension, entity);

if (!extensionIsEnabledForCluster) {
return [];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* 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 catalogEntityRegistryInjectable from "../api/catalog/entity/registry.injectable";
import { computed } from "mobx";
import { isKubernetesCluster } from "../../common/catalog-entities";

const activeKubernetesClusterInjectable = getInjectable({
id: "active-kubernetes-cluster",

instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);

return computed(() => {
const activeEntity = catalogEntityRegistry.activeEntity;

if (!isKubernetesCluster(activeEntity)) {
return null;
}

return activeEntity;
});
},
});

export default activeKubernetesClusterInjectable;
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import { renderFor } from "./renderFor";
import { RootFrame } from "../../frames/root-frame/root-frame";
import { ClusterFrame } from "../../frames/cluster-frame/cluster-frame";
import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluster-id.injectable";
import activeKubernetesClusterInjectable from "../../cluster-frame-context/active-kubernetes-cluster.injectable";
import { catalogEntityFromCluster } from "../../../main/cluster-manager";

type Callback = (dis: DiContainers) => void | Promise<void>;

Expand Down Expand Up @@ -382,6 +384,10 @@ export const getApplicationBuilder = () => {
accessibleNamespaces: [],
} as unknown as Cluster;

rendererDi.override(activeKubernetesClusterInjectable, () =>
computed(() => catalogEntityFromCluster(clusterStub)),
);

const namespaceStoreStub = {
contextNamespaces: [],
items: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
import type { KubernetesCluster } from "../../common/catalog-entities";
import extensionIsEnabledForClusterInjectable from "../../extensions/extension-loader/extension-is-enabled-for-cluster.injectable";
import activeKubernetesClusterInjectable from "../cluster-frame-context/active-kubernetes-cluster.injectable";
import { untracked } from "mobx";

const extensionShouldBeEnabledForClusterFrameInjectable = getInjectable({
id: "extension-should-be-enabled-for-cluster-frame",

instantiate: (di, extension: LensRendererExtension) => {
const activeKubernetesCluster = di.inject(activeKubernetesClusterInjectable);

const getExtensionIsEnabledForCluster = (
extension: LensRendererExtension,
cluster: KubernetesCluster,
) =>
untracked(() => di.inject(extensionIsEnabledForClusterInjectable, { extension, cluster }));

return asyncComputed(
async () => {
const cluster = activeKubernetesCluster.get();

if (!cluster) {
return false;
}

return getExtensionIsEnabledForCluster(
extension,
cluster,
);
},

false,
);
},

lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, extension: LensRendererExtension) =>
extension.sanitizedExtensionId,
}),
});

export default extensionShouldBeEnabledForClusterFrameInjectable;
7 changes: 7 additions & 0 deletions src/renderer/getDiForUnitTesting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ import kubeObjectDetailsClusterFrameChildComponentInjectable from "./components/
import kubeconfigDialogClusterFrameChildComponentInjectable from "./components/kubeconfig-dialog/kubeconfig-dialog-cluster-frame-child-component.injectable";
import portForwardDialogClusterFrameChildComponentInjectable from "./port-forward/port-forward-dialog-cluster-frame-child-component.injectable";
import setupSystemCaInjectable from "./frames/root-frame/setup-system-ca.injectable";
import extensionShouldBeEnabledForClusterFrameInjectable from "./extension-loader/extension-should-be-enabled-for-cluster-frame.injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import forceUpdateModalRootFrameComponentInjectable from "./application-update/force-update-modal/force-update-modal-root-frame-component.injectable";

export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
Expand Down Expand Up @@ -126,6 +128,11 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
shouldRender: computed(() => false),
}));

// TODO: Remove after "LensRendererExtension.isEnabledForCluster" is removed
di.override(extensionShouldBeEnabledForClusterFrameInjectable, () =>
asyncComputed(async () => true, true),
);

// TODO: Remove side-effects and shared global state
const clusterFrameChildComponentInjectables: Injectable<any, any, any>[] = [
commandContainerClusterFrameChildComponentInjectable,
Expand Down
27 changes: 21 additions & 6 deletions src/renderer/routes/extension-route-registrator.injectable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,39 @@ import { extensionRegistratorInjectionToken } from "../../extensions/extension-l
import { SiblingsInTabLayout } from "../components/layout/siblings-in-tab-layout";
import extensionPageParametersInjectable from "./extension-page-parameters.injectable";
import { routeSpecificComponentInjectionToken } from "./route-specific-component-injection-token";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token";
import { getExtensionRoutePath } from "./for-extension";
import extensionShouldBeEnabledForClusterFrameInjectable from "../extension-loader/extension-should-be-enabled-for-cluster-frame.injectable";

const extensionRouteRegistratorInjectable = getInjectable({
id: "extension-route-registrator",

instantiate: (di) => {
return (ext) => {
const extension = ext as LensRendererExtension;
const toRouteInjectable = toRouteInjectableFor(
di,
const toRouteInjectable = toRouteInjectableFor(di, extension);

const extensionShouldBeEnabledForClusterFrame = di.inject(
extensionShouldBeEnabledForClusterFrameInjectable,
extension,
);

return [
...extension.globalPages.map(toRouteInjectable(false)),
...extension.clusterPages.map(toRouteInjectable(true)),
...extension.globalPages.map(
toRouteInjectable(
false,
computed(() => true),
),
),

...extension.clusterPages.map(
toRouteInjectable(
true,
computed(() => extensionShouldBeEnabledForClusterFrame.value.get()),
),
),
].flat();
};
},
Expand All @@ -46,15 +61,15 @@ const toRouteInjectableFor =
di: DiContainerForInjection,
extension: LensRendererExtension,
) =>
(clusterFrame: boolean) =>
(clusterFrame: boolean, isEnabled: IComputedValue<boolean>) =>
(registration: PageRegistration) => {
const routeInjectable = getInjectable({
id: `route-${registration.id}-for-extension-${extension.sanitizedExtensionId}`,

instantiate: () => ({
path: getExtensionRoutePath(extension, registration.id),
clusterFrame,
isEnabled: computed(() => true),
isEnabled,
extension,
}),

Expand Down

0 comments on commit fb6ad38

Please sign in to comment.