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

Replace KubeObjectStatusRegistry with reactive solution (#4815)

This commit is contained in:
Janne Savolainen 2022-03-03 14:55:57 +02:00 committed by GitHub
parent d72ebac5f3
commit 54b87efd89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 687 additions and 37 deletions

View File

@ -6,7 +6,7 @@ export type { StatusBarRegistration } from "../../renderer/components/status-bar
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 { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";

View File

@ -277,7 +277,6 @@ export class ExtensionLoader {
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
];

View File

@ -20,12 +20,13 @@ import type { AdditionalCategoryColumnRegistration } from "../renderer/component
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
export class LensRendererExtension extends LensExtension {
globalPages: registries.PageRegistration[] = [];
clusterPages: registries.PageRegistration[] = [];
clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
appPreferences: AppPreferenceRegistration[] = [];
entitySettings: registries.EntitySettingRegistration[] = [];
statusBarItems: StatusBarRegistration[] = [];

View File

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

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
import { BaseRegistry } from "./base-registry";
export interface KubeObjectStatusRegistration {
kind: string;
apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus;
}
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.getItems()
.filter((item) => (
item.kind === kind
&& item.apiVersions.includes(apiVersion)
));
}
getItemsForObject(src: KubeObject) {
return this.getItemsForKind(src.kind, src.apiVersion)
.map(item => item.resolve(src))
.filter(Boolean);
}
}

View File

@ -0,0 +1,332 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`kube-object-status-icon given info and warning statuses are present, when rendered, renders with statuses 1`] = `
<body>
<div>
<i
class="Icon KubeObjectStatusIcon warning material focusable"
id="tooltip_target_5"
>
<span
class="icon"
data-icon-name="warning"
>
warning
</span>
<div />
</i>
</div>
<div
class="Tooltip narrow formatter"
>
<div
class="KubeObjectStatusTooltip"
>
<div
class="level warning"
>
<span
class="title"
>
Warning
</span>
<div
class="status msg"
>
-
Some warning status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
<div
class="level info"
>
<span
class="title"
>
Info
</span>
<div
class="status msg"
>
-
Some info status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
</div>
</div>
</body>
`;
exports[`kube-object-status-icon given level "critical" status, when rendered, renders with status 1`] = `
<body>
<div>
<i
class="Icon KubeObjectStatusIcon error material focusable"
id="tooltip_target_1"
>
<span
class="icon"
data-icon-name="error"
>
error
</span>
<div />
</i>
</div>
<div
class="Tooltip narrow formatter"
>
<div
class="KubeObjectStatusTooltip"
>
<div
class="level error"
>
<span
class="title"
>
Critical
</span>
<div
class="status msg"
>
-
Some critical status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
</div>
</div>
</body>
`;
exports[`kube-object-status-icon given level "info" status, when rendered, renders with status 1`] = `
<body>
<div>
<i
class="Icon KubeObjectStatusIcon info material focusable"
id="tooltip_target_2"
>
<span
class="icon"
data-icon-name="info"
>
info
</span>
<div />
</i>
</div>
<div
class="Tooltip narrow formatter"
>
<div
class="KubeObjectStatusTooltip"
>
<div
class="level info"
>
<span
class="title"
>
Info
</span>
<div
class="status msg"
>
-
Some info status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
</div>
</div>
</body>
`;
exports[`kube-object-status-icon given level "warning" status, when rendered, renders with status 1`] = `
<body>
<div>
<i
class="Icon KubeObjectStatusIcon warning material focusable"
id="tooltip_target_3"
>
<span
class="icon"
data-icon-name="warning"
>
warning
</span>
<div />
</i>
</div>
<div
class="Tooltip narrow formatter"
>
<div
class="KubeObjectStatusTooltip"
>
<div
class="level warning"
>
<span
class="title"
>
Warning
</span>
<div
class="status msg"
>
-
Some warning status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
</div>
</div>
</body>
`;
exports[`kube-object-status-icon given no statuses, when rendered, renders as empty 1`] = `<div />`;
exports[`kube-object-status-icon given registration for wrong api version, when rendered, renders as empty 1`] = `
<body>
<div />
</body>
`;
exports[`kube-object-status-icon given registration for wrong kind, when rendered, renders as empty 1`] = `
<body>
<div />
</body>
`;
exports[`kube-object-status-icon given registration without status for exact kube object, when rendered, renders as empty 1`] = `
<body>
<div />
</body>
`;
exports[`kube-object-status-icon given status for all levels is present, when rendered, renders with statuses 1`] = `
<body>
<div>
<i
class="Icon KubeObjectStatusIcon error material focusable"
id="tooltip_target_4"
>
<span
class="icon"
data-icon-name="error"
>
error
</span>
<div />
</i>
</div>
<div
class="Tooltip narrow formatter"
>
<div
class="KubeObjectStatusTooltip"
>
<div
class="level error"
>
<span
class="title"
>
Critical
</span>
<div
class="status msg"
>
-
Some critical status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
<div
class="level warning"
>
<span
class="title"
>
Warning
</span>
<div
class="status msg"
>
-
Some warning status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
<div
class="level info"
>
<span
class="title"
>
Info
</span>
<div
class="status msg"
>
-
Some info status for some-name
<span
class="age"
>
·
2d
</span>
</div>
</div>
</div>
</div>
</body>
`;

View File

@ -0,0 +1,263 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import { DiRender, renderFor } from "../test-utils/renderFor";
import { computed } from "mobx";
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import { KubeObjectStatusLevel } from "../../../extensions/renderer-api/kube-object-status";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { KubeObjectStatusIcon } from "./kube-object-status-icon";
import React from "react";
import type { KubeObjectStatusRegistration } from "./kube-object-status-registration";
describe("kube-object-status-icon", () => {
let render: DiRender;
let kubeObjectStatusRegistrations: KubeObjectStatusRegistration[];
beforeEach(async () => {
// TODO: Make mocking of date in unit tests global
global.Date.now = () => new Date("2015-10-21T07:28:00Z").getTime();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
kubeObjectStatusRegistrations = [];
const someTestExtension = new SomeTestExtension(
kubeObjectStatusRegistrations,
);
di.override(rendererExtensionsInjectable, () =>
computed(() => [someTestExtension]),
);
await di.runSetups();
});
it("given no statuses, when rendered, renders as empty", () => {
const kubeObject = getKubeObjectStub("irrelevant", "irrelevant");
const { container } = render(<KubeObjectStatusIcon object={kubeObject} />);
expect(container).toMatchSnapshot();
});
it('given level "critical" status, when rendered, renders with status', () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = getStatusRegistration(
KubeObjectStatusLevel.CRITICAL,
"critical",
"some-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it('given level "info" status, when rendered, renders with status', () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = getStatusRegistration(
KubeObjectStatusLevel.INFO,
"info",
"some-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it('given level "warning" status, when rendered, renders with status', () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = getStatusRegistration(
KubeObjectStatusLevel.WARNING,
"warning",
"some-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it("given status for all levels is present, when rendered, renders with statuses", () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const critical = getStatusRegistration(
KubeObjectStatusLevel.CRITICAL,
"critical",
"some-kind",
["some-api-version"],
);
const warning = getStatusRegistration(
KubeObjectStatusLevel.WARNING,
"warning",
"some-kind",
["some-api-version"],
);
const info = getStatusRegistration(
KubeObjectStatusLevel.INFO,
"info",
"some-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(critical);
kubeObjectStatusRegistrations.push(warning);
kubeObjectStatusRegistrations.push(info);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it("given info and warning statuses are present, when rendered, renders with statuses", () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const warning = getStatusRegistration(
KubeObjectStatusLevel.WARNING,
"warning",
"some-kind",
["some-api-version"],
);
const info = getStatusRegistration(
KubeObjectStatusLevel.INFO,
"info",
"some-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(warning);
kubeObjectStatusRegistrations.push(info);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it("given registration for wrong api version, when rendered, renders as empty", () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = getStatusRegistration(
KubeObjectStatusLevel.CRITICAL,
"irrelevant",
"some-kind",
["some-other-api-version"],
);
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it("given registration for wrong kind, when rendered, renders as empty", () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = getStatusRegistration(
KubeObjectStatusLevel.CRITICAL,
"irrelevant",
"some-other-kind",
["some-api-version"],
);
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
it("given registration without status for exact kube object, when rendered, renders as empty", () => {
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
const statusRegistration = {
apiVersions: ["some-api-version"],
kind: "some-kind",
resolve: (): void => {},
};
// @ts-ignore
kubeObjectStatusRegistrations.push(statusRegistration);
const { baseElement } = render(
<KubeObjectStatusIcon object={kubeObject} />,
);
expect(baseElement).toMatchSnapshot();
});
});
const getKubeObjectStub = (kind: string, apiVersion: string) => KubeObject.create({
apiVersion,
kind,
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: "some-namespace",
},
});
const getStatusRegistration = (level: KubeObjectStatusLevel, title: string, kind: string, apiVersions: string[]) => ({
apiVersions,
kind,
resolve: (kubeObject: KubeObject) => ({
level,
text: `Some ${title} status for ${kubeObject.getName()}`,
timestamp: "2015-10-19T07:28:00Z",
}),
});
class SomeTestExtension extends LensRendererExtension {
constructor(kubeObjectStatusTexts: KubeObjectStatusRegistration[]) {
super({
id: "some-id",
absolutePath: "irrelevant",
isBundled: false,
isCompatible: false,
isEnabled: false,
manifest: { name: "some-id", version: "some-version" },
manifestPath: "irrelevant",
});
this.kubeObjectStatusTexts = kubeObjectStatusTexts;
}
}

View File

@ -9,7 +9,8 @@ import React from "react";
import { Icon } from "../icon";
import { cssNames, formatDuration } from "../../utils";
import { KubeObject, KubeObjectStatus, KubeObjectStatusLevel } from "../../..//extensions/renderer-api/k8s-api";
import { KubeObjectStatusRegistry } from "../../../extensions/registries";
import { withInjectables } from "@ogre-tools/injectable-react";
import statusesForKubeObjectInjectable from "./statuses-for-kube-object.injectable";
function statusClassName(level: KubeObjectStatusLevel): string {
switch (level) {
@ -68,7 +69,11 @@ export interface KubeObjectStatusIconProps {
object: KubeObject;
}
export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconProps> {
interface Dependencies {
statuses: KubeObjectStatus[];
}
class NonInjectedKubeObjectStatusIcon extends React.Component<KubeObjectStatusIconProps & Dependencies> {
renderStatuses(statuses: KubeObjectStatus[], level: number) {
const filteredStatuses = statuses.filter((item) => item.level == level);
@ -89,7 +94,7 @@ export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconPr
}
render() {
const statuses = KubeObjectStatusRegistry.getInstance().getItemsForObject(this.props.object);
const statuses = this.props.statuses;
if (statuses.length === 0) {
return null;
@ -114,3 +119,14 @@ export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconPr
);
}
}
export const KubeObjectStatusIcon = withInjectables<Dependencies, KubeObjectStatusIconProps>(
NonInjectedKubeObjectStatusIcon,
{
getProps: (di, props) => ({
statuses: di.inject(statusesForKubeObjectInjectable, props.object),
...props,
}),
},
);

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type { KubeObjectStatus } from "../../../extensions/renderer-api/kube-object-status";
export interface KubeObjectStatusRegistration {
kind: string;
apiVersions: string[];
resolve: (object: KubeObject) => KubeObjectStatus;
}

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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
const statusRegistrationsInjectable = getInjectable({
id: "status-registrations",
instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable);
return computed(() =>
extensions.get().flatMap((extension) => extension.kubeObjectStatusTexts),
);
},
lifecycle: lifecycleEnum.singleton,
});
export default statusRegistrationsInjectable;

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import statusRegistrationsInjectable from "./status-registrations.injectable";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { conforms, eq, includes } from "lodash/fp";
import type { KubeObjectStatusRegistration } from "./kube-object-status-registration";
const statusesForKubeObjectInjectable = getInjectable({
id: "statuses-for-kube-object",
instantiate: (di, kubeObject: KubeObject) =>
di
.inject(statusRegistrationsInjectable)
.get()
.filter(toKubeObjectRelated(kubeObject))
.map(toStatus(kubeObject))
.filter(Boolean),
lifecycle: lifecycleEnum.transient,
});
const toKubeObjectRelated = (kubeObject: KubeObject) =>
conforms({
kind: eq(kubeObject.kind),
apiVersions: includes(kubeObject.apiVersion),
});
const toStatus =
(kubeObject: KubeObject) => (item: KubeObjectStatusRegistration) =>
item.resolve(kubeObject);
export default statusesForKubeObjectInjectable;

View File

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