Replace kube object menu registry with reactive solution (#4731)

Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Janne SavolainenandSebastian Malton authored and GitHub committed 2022-01-31 10:16:29 -05:00
1 parent 0ce4e3d793
commit ac42a6565f
25 files changed
+333 -299

No files matched your search

@@ -2,21 +2,36 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMenuRegistry } from "../../../../../extensions/registries";
import { conforms, includes, eq } from "lodash/fp";
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
import type { LensRendererExtension } from "../../../../../extensions/lens-renderer-extension";
import { staticKubeObjectMenuItems as staticMenuItems } from "./static-kube-object-menu-items";
interface Dependencies {
extensions: LensRendererExtension[];
kubeObject: KubeObject;
}
export const getKubeObjectMenuItems = ({
kubeObjectMenuRegistry,
extensions,
kubeObject,
}: {
kubeObjectMenuRegistry: KubeObjectMenuRegistry;
kubeObject: KubeObject;
}) => {
}: Dependencies) => {
if (!kubeObject) {
return [];
}
return kubeObjectMenuRegistry
.getItemsForKind(kubeObject.kind, kubeObject.apiVersion)
const extensionMenuItems = extensions.flatMap(
(extension) => extension.kubeObjectMenuItems,
);
return [...staticMenuItems, ...extensionMenuItems]
.filter(
conforms({
kind: eq(kubeObject.kind),
apiVersions: includes(kubeObject.apiVersion),
}),
)
.map((item) => item.components.MenuItem);
};
@@ -3,15 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import kubeObjectMenuRegistryInjectable from "./kube-object-menu-registry.injectable";
import { getKubeObjectMenuItems } from "./get-kube-object-menu-items";
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
import rendererExtensionsInjectable from "../../../../../extensions/renderer-extensions.injectable";
const kubeObjectMenuItemsInjectable = getInjectable({
instantiate: (di, { kubeObject }: { kubeObject: KubeObject }) =>
getKubeObjectMenuItems({
kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable),
extensions: di.inject(rendererExtensionsInjectable).get(),
kubeObject,
}),
@@ -0,0 +1,22 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type React from "react";
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
export interface KubeObjectMenuItemProps {
object: KubeObject;
toolbar?: boolean;
}
export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<KubeObjectMenuItemProps>;
}
export interface KubeObjectMenuRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectMenuComponents;
}
@@ -1,13 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObjectMenuRegistry } from "../../../../../extensions/registries";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
const kubeObjectMenuRegistryInjectable = getInjectable({
instantiate: () => KubeObjectMenuRegistry.getInstance(),
lifecycle: lifecycleEnum.singleton,
});
export default kubeObjectMenuRegistryInjectable;
@@ -0,0 +1,47 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ServiceAccountMenu } from "../../../+user-management/+service-accounts/service-account-menu";
import { CronJobMenu } from "../../../+workloads-cronjobs/cron-job-menu";
import { DeploymentMenu } from "../../../+workloads-deployments/deployment-menu";
import { ReplicaSetMenu } from "../../../+workloads-replicasets/replica-set-menu";
import { StatefulSetMenu } from "../../../+workloads-statefulsets/stateful-set-menu";
export const staticKubeObjectMenuItems = [
{
kind: "ServiceAccount",
apiVersions: ["v1"],
components: {
MenuItem: ServiceAccountMenu,
},
},
{
kind: "CronJob",
apiVersions: ["batch/v1beta1"],
components: {
MenuItem: CronJobMenu,
},
},
{
kind: "Deployment",
apiVersions: ["apps/v1"],
components: {
MenuItem: DeploymentMenu,
},
},
{
kind: "ReplicaSet",
apiVersions: ["apps/v1"],
components: {
MenuItem: ReplicaSetMenu,
},
},
{
kind: "StatefulSet",
apiVersions: ["apps/v1"],
components: {
MenuItem: StatefulSetMenu,
},
},
];
@@ -2,76 +2,108 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { screen } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import userEvent from "@testing-library/user-event";
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import type { KubeObjectMenuRegistration } from "../../../extensions/registries";
import { KubeObjectMenuRegistry } from "../../../extensions/registries";
import { ConfirmDialog } from "../confirm-dialog";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import clusterInjectable from "./dependencies/cluster.injectable";
import hideDetailsInjectable from "./dependencies/hide-details.injectable";
import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource-tab.injectable";
import kubeObjectMenuRegistryInjectable from "./dependencies/kube-object-menu-items/kube-object-menu-registry.injectable";
import { DiRender, renderFor } from "../test-utils/renderFor";
import type { Cluster } from "../../../common/cluster/cluster";
import type { ApiManager } from "../../../common/k8s-api/api-manager";
import apiManagerInjectable from "./dependencies/api-manager.injectable";
import { KubeObjectMenu } from "./index";
import type { KubeObjectMenuRegistration } from "./dependencies/kube-object-menu-items/kube-object-menu-registration";
import { computed } from "mobx";
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource-tab.injectable";
// TODO: Make tooltips free of side effects by making it deterministic
jest.mock("../tooltip");
class SomeTestExtension extends LensRendererExtension {
constructor(
kubeObjectMenuItems: KubeObjectMenuRegistration[],
) {
super({
id: "some-id",
absolutePath: "irrelevant",
isBundled: false,
isCompatible: false,
isEnabled: false,
manifest: { name: "some-id", version: "some-version" },
manifestPath: "irrelevant",
});
this.kubeObjectMenuItems = kubeObjectMenuItems;
}
}
describe("kube-object-menu", () => {
let di: ConfigurableDependencyInjectionContainer;
let render: DiRender;
beforeEach(async () => {
const MenuItemComponent: React.FC = () => <li>Some menu item</li>;
const kubeObjectMenuItems = [
{
apiVersions: ["some-api-version"],
kind: "some-kind",
components: { MenuItem: MenuItemComponent },
},
{
apiVersions: ["some-unrelated-api-version"],
kind: "some-kind",
components: { MenuItem: MenuItemComponent },
},
{
apiVersions: ["some-api-version"],
kind: "some-unrelated-kind",
components: { MenuItem: MenuItemComponent },
},
];
const someTestExtension = new SomeTestExtension(kubeObjectMenuItems);
di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
// TODO: Remove global shared state
KubeObjectMenuRegistry.resetInstance();
KubeObjectMenuRegistry.createInstance();
render = renderFor(di);
di.override(clusterInjectable, () => ({
name: "Some name",
}) as Cluster);
di.override(rendererExtensionsInjectable, () =>
computed(() => [someTestExtension]),
);
di.override(apiManagerInjectable, () => ({
getStore: api => void api,
}) as ApiManager);
await di.runSetups();
di.override(
clusterInjectable,
() =>
({
name: "Some name",
} as Cluster),
);
di.override(
apiManagerInjectable,
() =>
({
getStore: (api) => void api,
} as ApiManager),
);
di.override(hideDetailsInjectable, () => () => {});
di.override(createEditResourceTabInjectable, () => () => "irrelevant");
addDynamicMenuItem({
di,
apiVersions: ["some-api-version"],
kind: "some-kind",
});
addDynamicMenuItem({
di,
apiVersions: ["some-unrelated-api-version"],
kind: "some-kind",
});
addDynamicMenuItem({
di,
apiVersions: ["some-api-version"],
kind: "some-unrelated-kind",
});
});
it("given no cluster, does not crash", () => {
@@ -242,25 +274,3 @@ describe("kube-object-menu", () => {
});
});
});
const addDynamicMenuItem = ({
di,
apiVersions,
kind,
}: {
di: ConfigurableDependencyInjectionContainer;
apiVersions: string[];
kind: string;
}) => {
const MenuItemComponent: React.FC = () => <li>Some menu item</li>;
const dynamicMenuItemStub: KubeObjectMenuRegistration = {
apiVersions,
kind,
components: { MenuItem: MenuItemComponent },
};
const kubeObjectMenuRegistry = di.inject(kubeObjectMenuRegistryInjectable);
kubeObjectMenuRegistry.add([dynamicMenuItemStub]);
};