1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Demonstrate how InjectNaive could be not naive by using injectables

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2021-11-10 14:04:23 +02:00
parent 78e1c6357b
commit 7938e9c4ef
17 changed files with 392 additions and 142 deletions

View File

@ -27,11 +27,11 @@ import type { KubeApi } from "./kube-api";
import type { KubeObject } from "./kube-object"; import type { KubeObject } from "./kube-object";
import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse"; import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse";
export interface IGettableStore { export interface IApiManager {
getStore<TKubeObjectStore extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): TKubeObjectStore | undefined; getStore<TKubeObjectStore extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): TKubeObjectStore | undefined;
} }
export class ApiManager implements IGettableStore { export class ApiManager implements IApiManager {
private apis = observable.map<string, KubeApi<KubeObject>>(); private apis = observable.map<string, KubeApi<KubeObject>>();
private stores = observable.map<string, KubeObjectStore<KubeObject>>(); private stores = observable.map<string, KubeObjectStore<KubeObject>>();

View File

@ -32,11 +32,11 @@ export interface KubeObjectMenuRegistration {
components: KubeObjectMenuComponents; components: KubeObjectMenuComponents;
} }
export interface IHasGettableItemsForKind { export interface IKubeObjectMenuRegistry {
getItemsForKind(kind: string, apiVersion: string): any; getItemsForKind(kind: string, apiVersion: string): any;
} }
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> implements IHasGettableItemsForKind { export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> implements IKubeObjectMenuRegistry {
getItemsForKind = (kind: string, apiVersion: string) => getItemsForKind = (kind: string, apiVersion: string) =>
this.getItems().filter((item) => this.getItems().filter((item) =>
item.kind === kind && item.apiVersions.includes(apiVersion), item.kind === kind && item.apiVersions.includes(apiVersion),

View File

@ -37,12 +37,16 @@ import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../com
import { disposer, storedKubeConfigFolder, toJS } from "../common/utils"; import { disposer, storedKubeConfigFolder, toJS } from "../common/utils";
import type { Response } from "request"; import type { Response } from "request";
export interface ICluster {
name: string
}
/** /**
* Cluster * Cluster
* *
* @beta * @beta
*/ */
export class Cluster implements ClusterModel, ClusterState { export class Cluster implements ClusterModel, ClusterState, ICluster {
/** Unique id for a cluster */ /** Unique id for a cluster */
public readonly id: ClusterId; public readonly id: ClusterId;
private kubeCtl: Kubectl; private kubeCtl: Kubectl;

View File

@ -24,7 +24,7 @@ import "./deployment-replicasets.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { ReplicaSet } from "../../../common/k8s-api/endpoints"; import type { ReplicaSet } from "../../../common/k8s-api/endpoints";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object-menu"; import type { KubeObjectMenuProps } from "../kube-object-menu";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { prevDefault, stopPropagation } from "../../utils"; import { prevDefault, stopPropagation } from "../../utils";
import { DrawerTitle } from "../drawer"; import { DrawerTitle } from "../drawer";
@ -32,8 +32,8 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { showDetails } from "../kube-detail-params"; import { showDetails } from "../kube-detail-params";
import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; import { Inject } from "@ogre-tools/injectable-react";
import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable";
enum sortBy { enum sortBy {
name = "name", name = "name",
@ -115,7 +115,6 @@ export class DeploymentReplicaSets extends React.Component<Props> {
} }
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) { export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
return ( return <Inject injectableKey={KubeObjectMenuInjectable} {...props} />;
<InjectNaive Component={KubeObjectMenu} {...props}/>
);
} }

View File

@ -29,14 +29,13 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { apiManager } from "../../../common/k8s-api/api-manager"; import { apiManager } from "../../../common/k8s-api/api-manager";
import { crdStore } from "../+custom-resources/crd.store"; import { crdStore } from "../+custom-resources/crd.store";
import { KubeObjectMenu } from "../kube-object-menu";
import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import logger from "../../../main/logger"; import logger from "../../../main/logger";
import { CrdResourceDetails } from "../+custom-resources"; import { CrdResourceDetails } from "../+custom-resources";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { hideDetails, kubeDetailsUrlParam } from "../kube-detail-params"; import { hideDetails, kubeDetailsUrlParam } from "../kube-detail-params";
import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; import { Inject } from "@ogre-tools/injectable-react";
import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable";
export interface KubeObjectDetailsProps<T extends KubeObject = KubeObject> { export interface KubeObjectDetailsProps<T extends KubeObject = KubeObject> {
className?: string; className?: string;
@ -105,7 +104,7 @@ export class KubeObjectDetails extends React.Component {
className="KubeObjectDetails flex column" className="KubeObjectDetails flex column"
open={isOpen} open={isOpen}
title="" title=""
toolbar={<InjectNaive Component={KubeObjectMenu} object={object} toolbar={true} />} toolbar={<Inject injectableKey={KubeObjectMenuInjectable} object={object} toolbar={true} />}
onClose={hideDetails} onClose={hideDetails}
> >
{isLoading && <Spinner center />} {isLoading && <Spinner center />}
@ -145,7 +144,7 @@ export class KubeObjectDetails extends React.Component {
className="KubeObjectDetails flex column" className="KubeObjectDetails flex column"
open={isOpen} open={isOpen}
title={title} title={title}
toolbar={<InjectNaive Component={KubeObjectMenu} object={object} toolbar={true}/>} toolbar={<Inject injectableKey={KubeObjectMenuInjectable} object={object} toolbar={true}/>}
onClose={hideDetails} onClose={hideDetails}
> >
{isLoading && <Spinner center/>} {isLoading && <Spinner center/>}

View File

@ -28,7 +28,6 @@ import { cssNames, Disposer } from "../../utils";
import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout"; import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectMenu } from "../kube-object-menu";
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
@ -36,7 +35,8 @@ import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip"; import { TooltipPosition } from "../tooltip";
import type { ClusterContext } from "../../../common/k8s-api/cluster-context"; import type { ClusterContext } from "../../../common/k8s-api/cluster-context";
import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; import { Inject } from "@ogre-tools/injectable-react";
import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable";
export interface KubeObjectListLayoutProps<K extends KubeObject> extends ItemListLayoutProps<K> { export interface KubeObjectListLayoutProps<K extends KubeObject> extends ItemListLayoutProps<K> {
store: KubeObjectStore<K>; store: KubeObjectStore<K>;
@ -141,7 +141,7 @@ export class KubeObjectListLayout<K extends KubeObject> extends React.Component<
}), }),
...[customizeHeader].flat(), ...[customizeHeader].flat(),
]} ]}
renderItemMenu={item => <InjectNaive Component={KubeObjectMenu} object={item} />} renderItemMenu={item => <Inject injectableKey={KubeObjectMenuInjectable} object={item} />}
/> />
); );
} }

View File

@ -17,7 +17,7 @@ exports[`kube-object-menu given kube object renders 1`] = `
> >
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
id="tooltip_target_3" id="tooltip_target_4"
tabindex="0" tabindex="0"
> >
<span <span
@ -65,7 +65,7 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
> >
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
id="tooltip_target_7" id="tooltip_target_8"
tabindex="0" tabindex="0"
> >
<span <span
@ -116,7 +116,7 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
</b> </b>
from from
<b> <b>
Some cluster name Some name
</b> </b>
? ?
</p> </p>
@ -166,7 +166,7 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
> >
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
id="tooltip_target_32" id="tooltip_target_33"
tabindex="0" tabindex="0"
> >
<span <span
@ -217,7 +217,7 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
</b> </b>
from from
<b> <b>
Some cluster name Some name
</b> </b>
? ?
</p> </p>
@ -267,7 +267,7 @@ exports[`kube-object-menu given kube object without namespace when removing kube
> >
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
id="tooltip_target_40" id="tooltip_target_41"
tabindex="0" tabindex="0"
> >
<span <span
@ -318,7 +318,7 @@ exports[`kube-object-menu given kube object without namespace when removing kube
</b> </b>
from from
<b> <b>
Some cluster name Some name
</b> </b>
? ?
</p> </p>

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { IApiManager, apiManager } from "../../../../common/k8s-api/api-manager";
import type { IInjectable } from "@ogre-tools/injectable";
const apiManagerInjectable: IInjectable<IApiManager> = {
getDependencies: () => ({}),
instantiate: () => apiManager,
};
export default apiManagerInjectable;

View File

@ -18,37 +18,16 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import React from "react";
import { editResourceTab } from "../../dock/edit-resource.store";
import { hideDetails } from "../../kube-detail-params";
import { apiManager } from "../../../../common/k8s-api/api-manager";
import { KubeObjectMenuRegistry } from "../../../../extensions/registries";
import { getActiveClusterEntity } from "../../../api/catalog-entity-registry"; import { getActiveClusterEntity } from "../../../api/catalog-entity-registry";
import type { KubeObjectMenuDependencies } from "../kube-object-menu";
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
interface Props { import type { IInjectable } from "@ogre-tools/injectable";
Component: React.ElementType<any> import { lifecycleEnum } from "@ogre-tools/injectable";
[key: string]: any, import type { ICluster } from "../../../../main/cluster";
}
export const InjectNaive = ({ Component, ...props }: Props) => { const clusterInjectable: IInjectable<ICluster | null> = {
const kubeObjectMenuRegistry = KubeObjectMenuRegistry.getInstance(); getDependencies: () => ({}),
const cluster = getActiveClusterEntity(); instantiate: () => getActiveClusterEntity(),
lifecycle: lifecycleEnum.transient,
const dependencies: KubeObjectMenuDependencies<KubeObject> = {
clusterName: cluster.name,
editResourceTab,
hideDetails,
apiManager,
kubeObjectMenuRegistry,
};
return (
<Component
dependencies={dependencies}
{...props}
/>
);
}; };
export default clusterInjectable;

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { lifecycleEnum } from "@ogre-tools/injectable";
import type { ICluster } from "../../../../main/cluster";
import type { IInjectable } from "@ogre-tools/injectable";
import clusterInjectable from "./cluster.injectable";
interface IDependencies {
cluster: ICluster;
}
const clusterNameInjectable: IInjectable<
string | undefined,
IDependencies
> = {
getDependencies: di => ({
cluster: di.inject(clusterInjectable),
}),
instantiate: ({ cluster }) => cluster?.name,
lifecycle: lifecycleEnum.transient,
};
export default clusterNameInjectable;

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { editResourceTab } from "../../dock/edit-resource.store";
import type { IInjectable } from "@ogre-tools/injectable";
const editResourceTabInjectable: IInjectable<typeof editResourceTab> = {
getDependencies: () => ({}),
instantiate: () => editResourceTab,
};
export default editResourceTabInjectable;

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { hideDetails } from "../../kube-detail-params";
import type { IInjectable } from "@ogre-tools/injectable";
export const hideDetailsInjectable: IInjectable<typeof hideDetails> = {
getDependencies: () => ({}),
instantiate: () => hideDetails,
};
export default hideDetailsInjectable;

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { IKubeObjectMenuRegistry, KubeObjectMenuRegistry } from "../../../../extensions/registries";
import type { IInjectable } from "@ogre-tools/injectable";
const kubeObjectMenuRegistryInjectable: IInjectable<IKubeObjectMenuRegistry> = {
getDependencies: () => ({}),
instantiate: () => KubeObjectMenuRegistry.getInstance(),
};
export default kubeObjectMenuRegistryInjectable;

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectMenu, KubeObjectMenuDependencies } from "./kube-object-menu";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type { IComponentInjectable } from "@ogre-tools/injectable";
import apiManagerInjectable from "./dependencies/apiManager.injectable";
import clusterNameInjectable from "./dependencies/clusterName.injectable";
import kubeObjectMenuRegistryInjectable from "./dependencies/kubeObjectMenuRegistry.injectable";
import editResourceTabInjectable from "./dependencies/editResourceTab.injectable";
import hideDetailsInjectable from "./dependencies/hideDetails.injectable";
const KubeObjectMenuInjectable: IComponentInjectable<
typeof KubeObjectMenu,
KubeObjectMenuDependencies<KubeObject>
> = {
getDependencies: di => ({
clusterName: di.inject(clusterNameInjectable),
apiManager: di.inject(apiManagerInjectable),
kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable),
editResourceTab: di.inject(editResourceTabInjectable),
hideDetails: di.inject(hideDetailsInjectable),
}),
instantiate: KubeObjectMenu,
};
export default KubeObjectMenuInjectable;

View File

@ -20,28 +20,41 @@
*/ */
import React from "react"; import React from "react";
import { render, screen } from "@testing-library/react"; import { screen } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect"; import "@testing-library/jest-dom/extend-expect";
import { KubeObjectMenu, KubeObjectMenuDependencies } from "./kube-object-menu";
import { KubeObject } from "../../../common/k8s-api/kube-object"; import { KubeObject } from "../../../common/k8s-api/kube-object";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import type { KubeApi } from "../../../common/k8s-api/kube-api"; import type { IConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import type { KubeObjectMenuRegistration } from "../../../extensions/registries"; import type { KubeObjectMenuRegistration } from "../../../extensions/registries";
import type { IGettableStore } from "../../../common/k8s-api/api-manager"; import { KubeObjectMenuRegistry } from "../../../extensions/registries";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import { KubeObjectMenuRegistry } from "../../../extensions/registries"; import { getDiForUnitTesting } from "../getDiForUnitTesting";
import { Inject } from "@ogre-tools/injectable-react";
import clusterInjectable from "./dependencies/cluster.injectable";
import apiManagerInjectable from "./dependencies/apiManager.injectable";
import hideDetailsInjectable from "./dependencies/hideDetails.injectable";
import editResourceTabInjectable from "./dependencies/editResourceTab.injectable";
import { TabKind } from "../dock/dock.store";
import KubeObjectMenuInjectable from "./kube-object-menu.injectable";
import kubeObjectMenuRegistryInjectable from "./dependencies/kubeObjectMenuRegistry.injectable";
import { renderFor, IDiRender } from "../test-utils/renderFor";
describe("kube-object-menu", () => { describe("kube-object-menu", () => {
let hideDetailsStub: () => void;
let editResourceTabStub: () => void;
let apiManagerStub: IGettableStore;
let kubeObjectMenuRegistry: KubeObjectMenuRegistry;
let objectStub: KubeObject | null; let objectStub: KubeObject | null;
let dependencies: KubeObjectMenuDependencies<KubeObject>; let di: IConfigurableDependencyInjectionContainer;
let render: IDiRender;
beforeEach(() => { beforeEach(() => {
di = getDiForUnitTesting();
// TODO: Remove global shared state
KubeObjectMenuRegistry.resetInstance();
KubeObjectMenuRegistry.createInstance();
render = renderFor(di);
// TODO: Remove illegal global overwrites for what should be a dependency somewhere. // TODO: Remove illegal global overwrites for what should be a dependency somewhere.
// TODO: Remove usage of experimental browser API. // TODO: Remove usage of experimental browser API.
window.requestIdleCallback = (callback: IdleRequestCallback): number => { window.requestIdleCallback = (callback: IdleRequestCallback): number => {
@ -52,62 +65,66 @@ describe("kube-object-menu", () => {
window.cancelIdleCallback = () => {}; window.cancelIdleCallback = () => {};
apiManagerStub = { di.override(clusterInjectable, {
getStore: <TKubeObjectStore extends KubeObjectStore<KubeObject>>( name: "Some name",
// eslint-disable-next-line unused-imports/no-unused-vars-ts });
api: string | KubeApi<KubeObject>,
): TKubeObjectStore | undefined => undefined,
};
// TODO: Remove global shared state di.override(apiManagerInjectable, {
KubeObjectMenuRegistry.resetInstance(); getStore: () => undefined,
KubeObjectMenuRegistry.createInstance(); });
kubeObjectMenuRegistry = KubeObjectMenuRegistry.getInstance();
const MenuItemComponent: React.FC = () => <li>Some menu item</li>; di.override(hideDetailsInjectable, () => {});
di.override(editResourceTabInjectable, () => ({
id: "irrelevant",
kind: TabKind.TERMINAL,
pinned: false,
title: "irrelevant",
}));
addDynamicMenuItem({ addDynamicMenuItem({
kubeObjectMenuRegistry, di,
MenuItemComponent,
apiVersions: ["some-api-version"], apiVersions: ["some-api-version"],
kind: "some-kind", kind: "some-kind",
}); });
addDynamicMenuItem({ addDynamicMenuItem({
kubeObjectMenuRegistry, di,
MenuItemComponent,
apiVersions: ["some-unrelated-api-version"], apiVersions: ["some-unrelated-api-version"],
kind: "some-kind", kind: "some-kind",
}); });
addDynamicMenuItem({ addDynamicMenuItem({
kubeObjectMenuRegistry, di,
MenuItemComponent,
apiVersions: ["some-api-version"], apiVersions: ["some-api-version"],
kind: "some-unrelated-kind", kind: "some-unrelated-kind",
}); });
});
hideDetailsStub = () => {}; it("given no cluster, does not crash", () => {
di.override(clusterInjectable, null);
editResourceTabStub = () => {}; objectStub = null;
dependencies = { expect(() => {
clusterName: "Some cluster name", render(
apiManager: apiManagerStub, <Inject
kubeObjectMenuRegistry, injectableKey={KubeObjectMenuInjectable}
hideDetails: hideDetailsStub, object={objectStub}
editResourceTab: editResourceTabStub, toolbar={true}
}; />,
);
}).not.toThrow();
}); });
it("given no kube object, renders", () => { it("given no kube object, renders", () => {
objectStub = null; objectStub = null;
const { baseElement } = render( const { baseElement } = render(
<KubeObjectMenu <Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub} object={objectStub}
toolbar={true} toolbar={true}
dependencies={dependencies}
/>, />,
); );
@ -136,9 +153,9 @@ describe("kube-object-menu", () => {
<div> <div>
<ConfirmDialog /> <ConfirmDialog />
<KubeObjectMenu <Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub} object={objectStub}
dependencies={dependencies}
toolbar={true} toolbar={true}
removeAction={removeActionMock} removeAction={removeActionMock}
/> />
@ -195,7 +212,6 @@ describe("kube-object-menu", () => {
describe("given kube object with namespace", () => { describe("given kube object with namespace", () => {
let baseElement: Element; let baseElement: Element;
let getByTestId: (arg0: string) => any;
beforeEach(async () => { beforeEach(async () => {
objectStub = KubeObject.create({ objectStub = KubeObject.create({
@ -209,13 +225,13 @@ describe("kube-object-menu", () => {
}, },
}); });
({ baseElement, getByTestId } = render( ({ baseElement } = render(
<div> <div>
<ConfirmDialog /> <ConfirmDialog />
<KubeObjectMenu <Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub} object={objectStub}
dependencies={dependencies}
toolbar={true} toolbar={true}
removeAction={() => {}} removeAction={() => {}}
/> />
@ -224,7 +240,7 @@ describe("kube-object-menu", () => {
}); });
it("when removing kube object, renders confirmation dialog with namespace", () => { it("when removing kube object, renders confirmation dialog with namespace", () => {
const menuItem = getByTestId("menu-action-remove"); const menuItem = screen.getByTestId("menu-action-remove");
userEvent.click(menuItem); userEvent.click(menuItem);
@ -234,7 +250,6 @@ describe("kube-object-menu", () => {
describe("given kube object without namespace", () => { describe("given kube object without namespace", () => {
let baseElement: Element; let baseElement: Element;
let getByTestId: (arg0: string) => any;
beforeEach(async () => { beforeEach(async () => {
objectStub = KubeObject.create({ objectStub = KubeObject.create({
@ -248,13 +263,13 @@ describe("kube-object-menu", () => {
}, },
}); });
({ baseElement, getByTestId } = render( ({ baseElement } = render(
<div> <div>
<ConfirmDialog /> <ConfirmDialog />
<KubeObjectMenu <Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub} object={objectStub}
dependencies={dependencies}
toolbar={true} toolbar={true}
removeAction={() => {}} removeAction={() => {}}
/> />
@ -263,7 +278,7 @@ describe("kube-object-menu", () => {
}); });
it("when removing kube object, renders confirmation dialog without namespace", () => { it("when removing kube object, renders confirmation dialog without namespace", () => {
const menuItem = getByTestId("menu-action-remove"); const menuItem = screen.getByTestId("menu-action-remove");
userEvent.click(menuItem); userEvent.click(menuItem);
@ -273,21 +288,25 @@ describe("kube-object-menu", () => {
}); });
const addDynamicMenuItem = ({ const addDynamicMenuItem = ({
kubeObjectMenuRegistry, di,
MenuItemComponent,
apiVersions, apiVersions,
kind, kind,
}: { }: {
kubeObjectMenuRegistry: KubeObjectMenuRegistry; di: any;
MenuItemComponent: React.ComponentType;
apiVersions: string[]; apiVersions: string[];
kind: string; kind: string;
}) => { }) => {
const MenuItemComponent: React.FC = () => <li>Some menu item</li>;
const dynamicMenuItemStub: KubeObjectMenuRegistration = { const dynamicMenuItemStub: KubeObjectMenuRegistration = {
apiVersions, apiVersions,
kind, kind,
components: { MenuItem: MenuItemComponent }, components: { MenuItem: MenuItemComponent },
}; };
const kubeObjectMenuRegistry: KubeObjectMenuRegistry = di.inject(
kubeObjectMenuRegistryInjectable,
);
kubeObjectMenuRegistry.add(dynamicMenuItemStub); kubeObjectMenuRegistry.add(dynamicMenuItemStub);
}; };

View File

@ -22,16 +22,16 @@
import React from "react"; import React from "react";
import { boundMethod, cssNames } from "../../utils"; import { boundMethod, cssNames } from "../../utils";
import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { MenuActions, MenuActionsProps } from "../menu/menu-actions"; import { MenuActions, MenuActionsProps } from "../menu";
import identity from "lodash/identity"; import identity from "lodash/identity";
import type { IHasGettableItemsForKind } from "../../../extensions/registries"; import type { IKubeObjectMenuRegistry } from "../../../extensions/registries";
import type { IGettableStore } from "../../../common/k8s-api/api-manager"; import type { IApiManager } from "../../../common/k8s-api/api-manager";
export interface KubeObjectMenuDependencies<TKubeObject>{ export interface KubeObjectMenuDependencies<TKubeObject> {
apiManager: IGettableStore; apiManager: IApiManager;
kubeObjectMenuRegistry: IHasGettableItemsForKind; kubeObjectMenuRegistry: IKubeObjectMenuRegistry;
clusterName: string, clusterName: string;
hideDetails: () => void; hideDetails: () => void;
editResourceTab: (kubeObject: TKubeObject) => void; editResourceTab: (kubeObject: TKubeObject) => void;
} }
@ -40,22 +40,21 @@ export interface KubeObjectMenuProps<TKubeObject> extends MenuActionsProps {
object: TKubeObject | null | undefined; object: TKubeObject | null | undefined;
editable?: boolean; editable?: boolean;
removable?: boolean; removable?: boolean;
dependencies?: KubeObjectMenuDependencies<TKubeObject>
} }
export interface KubeObjectMenuPropsAndDependencies<TKubeObject>
extends KubeObjectMenuProps<TKubeObject>,
KubeObjectMenuDependencies<TKubeObject> {}
export class KubeObjectMenu< export class KubeObjectMenu<
TKubeObject extends KubeObject, TKubeObject extends KubeObject,
> extends React.Component<KubeObjectMenuProps<TKubeObject>> { > extends React.Component<KubeObjectMenuPropsAndDependencies<TKubeObject>> {
get dependencies() {
return this.props.dependencies;
}
get store() { get store() {
const { object } = this.props; const { object } = this.props;
if (!object) return null; if (!object) return null;
return this.dependencies.apiManager.getStore(object.selfLink); return this.props.apiManager.getStore(object.selfLink);
} }
get isEditable() { get isEditable() {
@ -68,13 +67,13 @@ export class KubeObjectMenu<
@boundMethod @boundMethod
async update() { async update() {
this.dependencies.hideDetails(); this.props.hideDetails();
this.dependencies.editResourceTab(this.props.object); this.props.editResourceTab(this.props.object);
} }
@boundMethod @boundMethod
async remove() { async remove() {
this.dependencies.hideDetails(); this.props.hideDetails();
const { object, removeAction } = this.props; const { object, removeAction } = this.props;
// TODO: currently only branch for removeAction() is unit tested, and store.remove() is not. // TODO: currently only branch for removeAction() is unit tested, and store.remove() is not.
@ -90,16 +89,13 @@ export class KubeObjectMenu<
return null; return null;
} }
const breadcrumbParts = [ const breadcrumbParts = [object.getNs(), object.getName()];
object.getNs(),
object.getName(),
];
const breadcrumb = breadcrumbParts.filter(identity).join("/"); const breadcrumb = breadcrumbParts.filter(identity).join("/");
return ( return (
<p> <p>
Remove {object.kind} <b>{breadcrumb}</b> from <b>{this.dependencies.clusterName}</b>? Remove {object.kind} <b>{breadcrumb}</b> from <b>{this.props.clusterName}</b>?
</p> </p>
); );
} }
@ -111,16 +107,22 @@ export class KubeObjectMenu<
return []; return [];
} }
return this.dependencies.kubeObjectMenuRegistry return this.props.kubeObjectMenuRegistry
.getItemsForKind(object.kind, object.apiVersion) .getItemsForKind(object.kind, object.apiVersion)
.map(({ components: { MenuItem }}: { components: { MenuItem: React.ReactType<any> }}, index: number) => ( .map(
<MenuItem (
object={object} {
toolbar={toolbar} components: { MenuItem },
// TODO: Fix misuse of index in key }: { components: { MenuItem: React.ReactType<any> }},
key={`menu-item-${index}`} index: number,
/> ) => (
), <MenuItem
object={object}
toolbar={toolbar}
// TODO: Fix misuse of index in key
key={`menu-item-${index}`}
/>
),
); );
} }

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import {
render as testingLibraryRender,
RenderResult,
} from "@testing-library/react";
import type { IConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import { DiContextProvider } from "@ogre-tools/injectable-react";
export type IDiRender = (ui: React.ReactElement) => RenderResult;
type IDiRenderFor = (
di: IConfigurableDependencyInjectionContainer,
) => IDiRender;
export const renderFor: IDiRenderFor = di => ui =>
testingLibraryRender(
<DiContextProvider value={{ di }}>{ui}</DiContextProvider>,
);