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

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

Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Janne Savolainen 2022-01-31 16:16:29 +01:00 committed by GitHub
parent 0ce4e3d793
commit ac42a6565f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 333 additions and 299 deletions

View File

@ -0,0 +1,5 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export default {};

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
export type { StatusBarRegistration } from "../../renderer/components/cluster-manager/status-bar-registration"; export type { StatusBarRegistration } from "../../renderer/components/cluster-manager/status-bar-registration";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-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 "../registries/kube-object-detail-registry";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"; export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry"; export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry"; export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";

View File

@ -276,7 +276,6 @@ export class ExtensionLoader {
const removeItems = [ const removeItems = [
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension), registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension), registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
registries.KubeObjectMenuRegistry.getInstance().add(extension.kubeObjectMenuItems),
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems), registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts), registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems), registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),

View File

@ -19,6 +19,7 @@ import type { AppPreferenceRegistration } from "../renderer/components/+preferen
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns"; import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views"; import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
import type { StatusBarRegistration } from "../renderer/components/cluster-manager/status-bar-registration"; import type { StatusBarRegistration } from "../renderer/components/cluster-manager/status-bar-registration";
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
export class LensRendererExtension extends LensExtension { export class LensRendererExtension extends LensExtension {
globalPages: registries.PageRegistration[] = []; globalPages: registries.PageRegistration[] = [];
@ -29,7 +30,7 @@ export class LensRendererExtension extends LensExtension {
entitySettings: registries.EntitySettingRegistration[] = []; entitySettings: registries.EntitySettingRegistration[] = [];
statusBarItems: StatusBarRegistration[] = []; statusBarItems: StatusBarRegistration[] = [];
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = []; kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = []; kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = []; kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
commands: CommandRegistration[] = []; commands: CommandRegistration[] = [];
welcomeMenus: WelcomeMenuRegistration[] = []; welcomeMenus: WelcomeMenuRegistration[] = [];

View File

@ -8,7 +8,6 @@
export * from "./page-registry"; export * from "./page-registry";
export * from "./page-menu-registry"; export * from "./page-menu-registry";
export * from "./kube-object-detail-registry"; export * from "./kube-object-detail-registry";
export * from "./kube-object-menu-registry";
export * from "./kube-object-status-registry"; export * from "./kube-object-status-registry";
export * from "./entity-setting-registry"; export * from "./entity-setting-registry";
export * from "./catalog-entity-detail-registry"; export * from "./catalog-entity-detail-registry";

View File

@ -73,9 +73,6 @@ export async function bootstrap(di: DependencyInjectionContainer) {
logger.info(`${logPrefix} initializing EntitySettingsRegistry`); logger.info(`${logPrefix} initializing EntitySettingsRegistry`);
initializers.initEntitySettingsRegistry(); initializers.initEntitySettingsRegistry();
logger.info(`${logPrefix} initializing KubeObjectMenuRegistry`);
initializers.initKubeObjectMenuRegistry();
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`); logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
initializers.initKubeObjectDetailRegistry(); initializers.initKubeObjectDetailRegistry();

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { KubeObjectMenuProps } from "../../kube-object-menu";
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints";
import { MenuItem } from "../../menu";
import { openServiceAccountKubeConfig } from "../../kubeconfig-dialog";
import { Icon } from "../../icon";
export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
const { object, toolbar } = props;
return (
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
<Icon material="insert_drive_file" tooltip="Kubeconfig File" interactive={toolbar} />
<span className="title">Kubeconfig</span>
</MenuItem>
);
}

View File

@ -8,13 +8,8 @@ import "./view.scss";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import React from "react"; import React from "react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints/service-accounts.api";
import { Icon } from "../../icon";
import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectListLayout } from "../../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
import type { KubeObjectMenuProps } from "../../kube-object-menu";
import { openServiceAccountKubeConfig } from "../../kubeconfig-dialog";
import { MenuItem } from "../../menu";
import { CreateServiceAccountDialog } from "./create-dialog"; import { CreateServiceAccountDialog } from "./create-dialog";
import { serviceAccountsStore } from "./store"; import { serviceAccountsStore } from "./store";
import type { ServiceAccountsRouteParams } from "../../../../common/routes"; import type { ServiceAccountsRouteParams } from "../../../../common/routes";
@ -69,13 +64,3 @@ export class ServiceAccounts extends React.Component<Props> {
} }
} }
export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
const { object, toolbar } = props;
return (
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
<Icon material="insert_drive_file" tooltip="Kubeconfig File" interactive={toolbar} />
<span className="title">Kubeconfig</span>
</MenuItem>
);
}

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import { CronJob, cronJobApi } from "../../../common/k8s-api/endpoints";
import { MenuItem } from "../menu";
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
import { Icon } from "../icon";
import { ConfirmDialog } from "../confirm-dialog";
import { Notifications } from "../notifications";
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
<Icon material="play_circle_filled" tooltip="Trigger" interactive={toolbar}/>
<span className="title">Trigger</span>
</MenuItem>
{object.isSuspend() ?
<MenuItem onClick={() => ConfirmDialog.open({
ok: async () => {
try {
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Resume`,
message: (
<p>
Resume CronJob <b>{object.getName()}</b>?
</p>),
})}>
<Icon material="play_circle_outline" tooltip="Resume" interactive={toolbar}/>
<span className="title">Resume</span>
</MenuItem>
: <MenuItem onClick={() => ConfirmDialog.open({
ok: async () => {
try {
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Suspend`,
message: (
<p>
Suspend CronJob <b>{object.getName()}</b>?
</p>),
})}>
<Icon material="pause_circle_filled" tooltip="Suspend" interactive={toolbar}/>
<span className="title">Suspend</span>
</MenuItem>
}
</>
);
}

View File

@ -8,18 +8,11 @@ import "./cronjobs.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import { CronJob, cronJobApi } from "../../../common/k8s-api/endpoints/cron-job.api";
import { MenuItem } from "../menu";
import { Icon } from "../icon";
import { cronJobStore } from "./cronjob.store"; import { cronJobStore } from "./cronjob.store";
import { jobStore } from "../+workloads-jobs/job.store"; import { jobStore } from "../+workloads-jobs/job.store";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout";
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { ConfirmDialog } from "../confirm-dialog/confirm-dialog";
import { Notifications } from "../notifications/notifications";
import type { CronJobsRouteParams } from "../../../common/routes"; import type { CronJobsRouteParams } from "../../../common/routes";
import moment from "moment"; import moment from "moment";
@ -87,53 +80,3 @@ export class CronJobs extends React.Component<Props> {
} }
} }
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
<Icon material="play_circle_filled" tooltip="Trigger" interactive={toolbar}/>
<span className="title">Trigger</span>
</MenuItem>
{object.isSuspend() ?
<MenuItem onClick={() => ConfirmDialog.open({
ok: async () => {
try {
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Resume`,
message: (
<p>
Resume CronJob <b>{object.getName()}</b>?
</p>),
})}>
<Icon material="play_circle_outline" tooltip="Resume" interactive={toolbar}/>
<span className="title">Resume</span>
</MenuItem>
: <MenuItem onClick={() => ConfirmDialog.open({
ok: async () => {
try {
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Suspend`,
message: (
<p>
Suspend CronJob <b>{object.getName()}</b>?
</p>),
})}>
<Icon material="pause_circle_filled" tooltip="Suspend" interactive={toolbar}/>
<span className="title">Suspend</span>
</MenuItem>
}
</>
);
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import { Deployment, deploymentApi } from "../../../common/k8s-api/endpoints";
import { MenuItem } from "../menu";
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
import { Icon } from "../icon";
import { ConfirmDialog } from "../confirm-dialog";
import { Notifications } from "../notifications";
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
<MenuItem onClick={() => ConfirmDialog.open({
ok: async () =>
{
try {
await deploymentApi.restart({
namespace: object.getNs(),
name: object.getName(),
});
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Restart`,
message: (
<p>
Are you sure you want to restart deployment <b>{object.getName()}</b>?
</p>
),
})}>
<Icon material="autorenew" tooltip="Restart" interactive={toolbar}/>
<span className="title">Restart</span>
</MenuItem>
</>
);
}

View File

@ -8,12 +8,7 @@ import "./deployments.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import { Deployment, deploymentApi } from "../../../common/k8s-api/endpoints"; import type { Deployment } from "../../../common/k8s-api/endpoints";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import { MenuItem } from "../menu";
import { Icon } from "../icon";
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
import { ConfirmDialog } from "../confirm-dialog";
import { deploymentStore } from "./deployments.store"; import { deploymentStore } from "./deployments.store";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout";
@ -21,7 +16,6 @@ import { cssNames } from "../../utils";
import kebabCase from "lodash/kebabCase"; import kebabCase from "lodash/kebabCase";
import orderBy from "lodash/orderBy"; import orderBy from "lodash/orderBy";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { Notifications } from "../notifications";
import type { DeploymentsRouteParams } from "../../../common/routes"; import type { DeploymentsRouteParams } from "../../../common/routes";
enum columnId { enum columnId {
@ -95,38 +89,3 @@ export class Deployments extends React.Component<Props> {
); );
} }
} }
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
<MenuItem onClick={() => ConfirmDialog.open({
ok: async () =>
{
try {
await deploymentApi.restart({
namespace: object.getNs(),
name: object.getName(),
});
} catch (err) {
Notifications.error(err);
}
},
labelOk: `Restart`,
message: (
<p>
Are you sure you want to restart deployment <b>{object.getName()}</b>?
</p>
),
})}>
<Icon material="autorenew" tooltip="Restart" interactive={toolbar}/>
<span className="title">Restart</span>
</MenuItem>
</>
);
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import type { ReplicaSet } from "../../../common/k8s-api/endpoints";
import { MenuItem } from "../menu";
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
import { Icon } from "../icon";
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
</>
);
}

View File

@ -7,15 +7,10 @@ import "./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 { KubeObjectMenuProps } from "../kube-object-menu";
import { replicaSetStore } from "./replicasets.store"; import { replicaSetStore } from "./replicasets.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout";
import { MenuItem } from "../menu/menu";
import { Icon } from "../icon/icon";
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
import type { ReplicaSetsRouteParams } from "../../../common/routes"; import type { ReplicaSetsRouteParams } from "../../../common/routes";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
@ -75,15 +70,3 @@ export class ReplicaSets extends React.Component<Props> {
} }
} }
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
</>
);
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import type { StatefulSet } from "../../../common/k8s-api/endpoints";
import { MenuItem } from "../menu";
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
import { Icon } from "../icon";
export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
</>
);
}

View File

@ -12,12 +12,8 @@ import type { StatefulSet } from "../../../common/k8s-api/endpoints";
import { podsStore } from "../+workloads-pods/pods.store"; import { podsStore } from "../+workloads-pods/pods.store";
import { statefulSetStore } from "./statefulset.store"; import { statefulSetStore } from "./statefulset.store";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
import type { KubeObjectMenuProps } from "../kube-object-menu";
import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
import { MenuItem } from "../menu/menu";
import { Icon } from "../icon/icon";
import type { StatefulSetsRouteParams } from "../../../common/routes"; import type { StatefulSetsRouteParams } from "../../../common/routes";
enum columnId { enum columnId {
@ -76,16 +72,3 @@ export class StatefulSets extends React.Component<Props> {
); );
} }
} }
export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
const { object, toolbar } = props;
return (
<>
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
<span className="title">Scale</span>
</MenuItem>
</>
);
}

View File

@ -2,21 +2,36 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * 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 { 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 = ({ export const getKubeObjectMenuItems = ({
kubeObjectMenuRegistry, extensions,
kubeObject, kubeObject,
}: { }: Dependencies) => {
kubeObjectMenuRegistry: KubeObjectMenuRegistry;
kubeObject: KubeObject;
}) => {
if (!kubeObject) { if (!kubeObject) {
return []; return [];
} }
return kubeObjectMenuRegistry const extensionMenuItems = extensions.flatMap(
.getItemsForKind(kubeObject.kind, kubeObject.apiVersion) (extension) => extension.kubeObjectMenuItems,
);
return [...staticMenuItems, ...extensionMenuItems]
.filter(
conforms({
kind: eq(kubeObject.kind),
apiVersions: includes(kubeObject.apiVersion),
}),
)
.map((item) => item.components.MenuItem); .map((item) => item.components.MenuItem);
}; };

View File

@ -3,15 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import kubeObjectMenuRegistryInjectable from "./kube-object-menu-registry.injectable";
import { getKubeObjectMenuItems } from "./get-kube-object-menu-items"; import { getKubeObjectMenuItems } from "./get-kube-object-menu-items";
import type { KubeObject } from "../../../../../common/k8s-api/kube-object"; import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
import rendererExtensionsInjectable from "../../../../../extensions/renderer-extensions.injectable";
const kubeObjectMenuItemsInjectable = getInjectable({ const kubeObjectMenuItemsInjectable = getInjectable({
instantiate: (di, { kubeObject }: { kubeObject: KubeObject }) => instantiate: (di, { kubeObject }: { kubeObject: KubeObject }) =>
getKubeObjectMenuItems({ getKubeObjectMenuItems({
kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable), extensions: di.inject(rendererExtensionsInjectable).get(),
kubeObject, kubeObject,
}), }),

View File

@ -4,10 +4,15 @@
*/ */
import type React from "react"; import type React from "react";
import { BaseRegistry } from "./base-registry"; import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
export interface KubeObjectMenuItemProps {
object: KubeObject;
toolbar?: boolean;
}
export interface KubeObjectMenuComponents { export interface KubeObjectMenuComponents {
MenuItem: React.ComponentType<any>; MenuItem: React.ComponentType<KubeObjectMenuItemProps>;
} }
export interface KubeObjectMenuRegistration { export interface KubeObjectMenuRegistration {
@ -15,11 +20,3 @@ export interface KubeObjectMenuRegistration {
apiVersions: string[]; apiVersions: string[];
components: KubeObjectMenuComponents; components: KubeObjectMenuComponents;
} }
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion);
});
}
}

View File

@ -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;

View File

@ -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,
},
},
];

View File

@ -2,76 +2,108 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import React from "react"; import React from "react";
import { 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 { 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 { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import type { KubeObjectMenuRegistration } from "../../../extensions/registries";
import { KubeObjectMenuRegistry } from "../../../extensions/registries";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import asyncFn, { AsyncFnMock } from "@async-fn/jest"; import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import clusterInjectable from "./dependencies/cluster.injectable"; import clusterInjectable from "./dependencies/cluster.injectable";
import hideDetailsInjectable from "./dependencies/hide-details.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 { DiRender, renderFor } from "../test-utils/renderFor";
import type { Cluster } from "../../../common/cluster/cluster"; import type { Cluster } from "../../../common/cluster/cluster";
import type { ApiManager } from "../../../common/k8s-api/api-manager"; import type { ApiManager } from "../../../common/k8s-api/api-manager";
import apiManagerInjectable from "./dependencies/api-manager.injectable"; import apiManagerInjectable from "./dependencies/api-manager.injectable";
import { KubeObjectMenu } from "./index"; 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 // TODO: Make tooltips free of side effects by making it deterministic
jest.mock("../tooltip"); 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", () => { describe("kube-object-menu", () => {
let di: ConfigurableDependencyInjectionContainer; let di: ConfigurableDependencyInjectionContainer;
let render: DiRender; let render: DiRender;
beforeEach(async () => { 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 }); di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
// TODO: Remove global shared state
KubeObjectMenuRegistry.resetInstance();
KubeObjectMenuRegistry.createInstance();
render = renderFor(di); render = renderFor(di);
di.override(clusterInjectable, () => ({ di.override(rendererExtensionsInjectable, () =>
name: "Some name", computed(() => [someTestExtension]),
}) as Cluster); );
di.override(apiManagerInjectable, () => ({ await di.runSetups();
getStore: api => void api,
}) as ApiManager); di.override(
clusterInjectable,
() =>
({
name: "Some name",
} as Cluster),
);
di.override(
apiManagerInjectable,
() =>
({
getStore: (api) => void api,
} as ApiManager),
);
di.override(hideDetailsInjectable, () => () => {}); di.override(hideDetailsInjectable, () => () => {});
di.override(createEditResourceTabInjectable, () => () => "irrelevant"); 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", () => { 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]);
};

View File

@ -8,7 +8,6 @@ export * from "./catalog";
export * from "./entity-settings-registry"; export * from "./entity-settings-registry";
export * from "./ipc"; export * from "./ipc";
export * from "./kube-object-detail-registry"; export * from "./kube-object-detail-registry";
export * from "./kube-object-menu-registry";
export * from "./registries"; export * from "./registries";
export * from "./workloads-overview-detail-registry"; export * from "./workloads-overview-detail-registry";
export * from "./catalog-category-registry"; export * from "./catalog-category-registry";

View File

@ -1,52 +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 { ServiceAccountMenu } from "../components/+user-management/+service-accounts";
import { CronJobMenu } from "../components/+workloads-cronjobs";
import { DeploymentMenu } from "../components/+workloads-deployments";
import { ReplicaSetMenu } from "../components/+workloads-replicasets";
import { StatefulSetMenu } from "../components/+workloads-statefulsets";
export function initKubeObjectMenuRegistry() {
KubeObjectMenuRegistry.getInstance()
.add([
{
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,
},
},
]);
}

View File

@ -12,7 +12,6 @@ export function initRegistries() {
registries.EntitySettingRegistry.createInstance(); registries.EntitySettingRegistry.createInstance();
registries.GlobalPageRegistry.createInstance(); registries.GlobalPageRegistry.createInstance();
registries.KubeObjectDetailRegistry.createInstance(); registries.KubeObjectDetailRegistry.createInstance();
registries.KubeObjectMenuRegistry.createInstance();
registries.KubeObjectStatusRegistry.createInstance(); registries.KubeObjectStatusRegistry.createInstance();
registries.WorkloadsOverviewDetailRegistry.createInstance(); registries.WorkloadsOverviewDetailRegistry.createInstance();
} }