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

Start resolving kube object status texts on each re-render to mimic reactivity

Note: This is done due the current implementation exposed in Extension API expects it to work so. However, this is bad.

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-07-22 15:48:55 +03:00
parent 49d26505e4
commit 1244ef5d80
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
3 changed files with 182 additions and 110 deletions

View File

@ -8,16 +8,18 @@ import React from "react";
import { useFakeTime } from "../../../common/test-utils/use-fake-time"; import { useFakeTime } from "../../../common/test-utils/use-fake-time";
import type { DiContainer } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { IObservableValue } from "mobx"; import type { IAtom } from "mobx";
import { observable, computed, runInAction } from "mobx"; import { createAtom, computed } from "mobx";
import { KubeObjectStatusIcon } from "../../../renderer/components/kube-object-status-icon";
import { kubeObjectStatusTextInjectionToken } from "../../../renderer/components/kube-object-status-icon/kube-object-status-text-injection-token";
import { frontEndRouteInjectionToken } from "../../../common/front-end-routing/front-end-route-injection-token"; import { frontEndRouteInjectionToken } from "../../../common/front-end-routing/front-end-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../renderer/routes/route-specific-component-injection-token"; import { routeSpecificComponentInjectionToken } from "../../../renderer/routes/route-specific-component-injection-token";
import type { ApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder"; import type { ApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import { navigateToRouteInjectionToken } from "../../../common/front-end-routing/navigate-to-route-injection-token"; import { navigateToRouteInjectionToken } from "../../../common/front-end-routing/navigate-to-route-injection-token";
import type { RenderResult } from "@testing-library/react"; import type { RenderResult } from "@testing-library/react";
import { act } from "@testing-library/react";
import { observer } from "mobx-react";
import { kubeObjectStatusTextInjectionToken } from "../../../renderer/components/kube-object-status-icon/kube-object-status-text-injection-token";
import { KubeObjectStatusIcon } from "../../../renderer/components/kube-object-status-icon";
// TODO: Make tooltips free of side effects by making it deterministic // TODO: Make tooltips free of side effects by making it deterministic
jest.mock("../../../renderer/components/tooltip/withTooltip", () => ({ jest.mock("../../../renderer/components/tooltip/withTooltip", () => ({
@ -29,8 +31,13 @@ jest.mock("../../../renderer/components/tooltip/withTooltip", () => ({
return ( return (
<> <>
<Target tooltip={tooltip.children ? undefined : tooltip} {...props} /> <Target
<div data-testid={testId && `tooltip-content-for-${testId}`}>{tooltip.children || tooltip}</div> tooltip={tooltip.children ? undefined : tooltip}
{...props}
/>
<div data-testid={testId && `tooltip-content-for-${testId}`}>
{tooltip.children || tooltip}
</div>
</> </>
); );
} }
@ -41,9 +48,9 @@ jest.mock("../../../renderer/components/tooltip/withTooltip", () => ({
describe("show status for a kube object", () => { describe("show status for a kube object", () => {
let builder: ApplicationBuilder; let builder: ApplicationBuilder;
let infoStatusIsShown: IObservableValue<boolean>; let infoStatusIsShown: boolean;
let warningStatusIsShown: IObservableValue<boolean>; let warningStatusIsShown: boolean;
let criticalStatusIsShown: IObservableValue<boolean>; let criticalStatusIsShown: boolean;
beforeEach(() => { beforeEach(() => {
useFakeTime("2015-10-21T07:28:00Z"); useFakeTime("2015-10-21T07:28:00Z");
@ -52,33 +59,66 @@ describe("show status for a kube object", () => {
const rendererDi = builder.dis.rendererDi; const rendererDi = builder.dis.rendererDi;
infoStatusIsShown = observable.box(false); infoStatusIsShown = false;
warningStatusIsShown = observable.box(false);
criticalStatusIsShown = observable.box(false);
const infoStatusInjectable = getStatusTextInjectable( const infoStatusInjectable = getInjectable({
KubeObjectStatusLevel.INFO, id: "some-info-status",
"info", injectionToken: kubeObjectStatusTextInjectionToken,
"some-kind",
["some-api-version"],
infoStatusIsShown,
);
const warningStatusInjectable = getStatusTextInjectable( instantiate: () => ({
KubeObjectStatusLevel.WARNING, apiVersions: ["some-api-version"],
"warning", kind: "some-kind",
"some-kind", enabled: computed(() => true),
["some-api-version"],
warningStatusIsShown,
);
const criticalStatusInjectable = getStatusTextInjectable( resolve: (resource) => infoStatusIsShown ? ({
KubeObjectStatusLevel.CRITICAL, level: KubeObjectStatusLevel.INFO,
"critical", text: `Some info status for ${resource.getName()}`,
"some-kind", timestamp: "2015-10-19T07:28:00Z",
["some-api-version"], }) : null,
criticalStatusIsShown, }),
); });
warningStatusIsShown = false;
const warningStatusInjectable = getInjectable({
id: "some-warning-status",
injectionToken: kubeObjectStatusTextInjectionToken,
instantiate: () => ({
apiVersions: ["some-api-version"],
kind: "some-kind",
enabled: computed(() => true),
resolve: (resource) => warningStatusIsShown ? ({
level: KubeObjectStatusLevel.WARNING,
text: `Some warning status for ${resource.getName()}`,
timestamp: "2015-10-19T07:28:00Z",
}) : null,
}),
});
criticalStatusIsShown = false;
const criticalStatusInjectable = getInjectable({
id: "some-critical-status",
injectionToken: kubeObjectStatusTextInjectionToken,
instantiate: () => ({
apiVersions: ["some-api-version"],
kind: "some-kind",
enabled: computed(() => true),
resolve: (resource) => {
return criticalStatusIsShown
? {
level: KubeObjectStatusLevel.CRITICAL,
text: `Some critical status for ${resource.getName()}`,
timestamp: "2015-10-19T07:28:00Z",
}
: null;
},
}),
});
rendererDi.register( rendererDi.register(
testRouteInjectable, testRouteInjectable,
@ -86,6 +126,7 @@ describe("show status for a kube object", () => {
infoStatusInjectable, infoStatusInjectable,
warningStatusInjectable, warningStatusInjectable,
criticalStatusInjectable, criticalStatusInjectable,
someAtomInjectable,
); );
builder.setEnvironmentToClusterFrame(); builder.setEnvironmentToClusterFrame();
@ -94,12 +135,17 @@ describe("show status for a kube object", () => {
describe("given application starts and in test page", () => { describe("given application starts and in test page", () => {
let rendererDi: DiContainer; let rendererDi: DiContainer;
let rendered: RenderResult; let rendered: RenderResult;
let rerenderParent: () => void;
beforeEach(async () => { beforeEach(async () => {
rendered = await builder.render(); rendered = await builder.render();
rendererDi = builder.dis.rendererDi; rendererDi = builder.dis.rendererDi;
const someAtom = rendererDi.inject(someAtomInjectable);
rerenderParent = rerenderParentFor(someAtom);
const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken); const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
const testRoute = rendererDi.inject(testRouteInjectable); const testRoute = rendererDi.inject(testRouteInjectable);
@ -121,6 +167,8 @@ describe("show status for a kube object", () => {
describe("when status for irrelevant kube object kind emerges", () => { describe("when status for irrelevant kube object kind emerges", () => {
beforeEach(() => { beforeEach(() => {
rendererDi.register(statusForIrrelevantKubeObjectKindInjectable); rendererDi.register(statusForIrrelevantKubeObjectKindInjectable);
rerenderParent();
}); });
it("renders", () => { it("renders", () => {
@ -139,6 +187,8 @@ describe("show status for a kube object", () => {
describe("when status for irrelevant kube object api version emerges", () => { describe("when status for irrelevant kube object api version emerges", () => {
beforeEach(() => { beforeEach(() => {
rendererDi.register(statusForIrrelevantKubeObjectApiVersionInjectable); rendererDi.register(statusForIrrelevantKubeObjectApiVersionInjectable);
rerenderParent();
}); });
it("renders", () => { it("renders", () => {
@ -156,9 +206,9 @@ describe("show status for a kube object", () => {
describe("when info status emerges", () => { describe("when info status emerges", () => {
beforeEach(() => { beforeEach(() => {
runInAction(() => { infoStatusIsShown = true;
infoStatusIsShown.set(true);
}); rerenderParent();
}); });
it("renders", () => { it("renders", () => {
@ -178,13 +228,17 @@ describe("show status for a kube object", () => {
"tooltip-content-for-kube-object-status-icon-for-some-uid", "tooltip-content-for-kube-object-status-icon-for-some-uid",
); );
expect(tooltipContent).toHaveTextContent("Some info status for some-name"); expect(tooltipContent).toHaveTextContent(
"Some info status for some-name",
);
}); });
}); });
describe("when warning status emerges", () => { describe("when warning status emerges", () => {
beforeEach(() => { beforeEach(() => {
warningStatusIsShown.set(true); warningStatusIsShown = true;
rerenderParent();
}); });
it("renders", () => { it("renders", () => {
@ -196,13 +250,17 @@ describe("show status for a kube object", () => {
"tooltip-content-for-kube-object-status-icon-for-some-uid", "tooltip-content-for-kube-object-status-icon-for-some-uid",
); );
expect(tooltipContent).toHaveTextContent("Some warning status for some-name"); expect(tooltipContent).toHaveTextContent(
"Some warning status for some-name",
);
}); });
}); });
describe("when critical status emerges", () => { describe("when critical status emerges", () => {
beforeEach(() => { beforeEach(() => {
criticalStatusIsShown.set(true); criticalStatusIsShown = true;
rerenderParent();
}); });
it("renders", () => { it("renders", () => {
@ -214,7 +272,9 @@ describe("show status for a kube object", () => {
"tooltip-content-for-kube-object-status-icon-for-some-uid", "tooltip-content-for-kube-object-status-icon-for-some-uid",
); );
expect(tooltipContent).toHaveTextContent("Some critical status for some-name"); expect(tooltipContent).toHaveTextContent(
"Some critical status for some-name",
);
}); });
}); });
}); });
@ -232,76 +292,85 @@ const testRouteInjectable = getInjectable({
injectionToken: frontEndRouteInjectionToken, injectionToken: frontEndRouteInjectionToken,
}); });
const rerenderParentFor = (atom: IAtom) => () => {
act(() => {
atom.reportChanged();
});
};
const TestComponent = observer(({ someAtom }: { someAtom: IAtom }) => {
void someAtom.reportObserved();
return (
<KubeObjectStatusIcon
object={getKubeObjectStub("some-kind", "some-api-version")}
/>
);
});
const testRouteComponentInjectable = getInjectable({ const testRouteComponentInjectable = getInjectable({
id: "test-route-component", id: "test-route-component",
instantiate: (di) => ({ instantiate: (di) => {
route: di.inject(testRouteInjectable), const someAtom = di.inject(someAtomInjectable);
Component: () => ( return {
<KubeObjectStatusIcon route: di.inject(testRouteInjectable),
object={getKubeObjectStub("some-kind", "some-api-version")} Component: () => <TestComponent someAtom={someAtom} />,
/> };
), },
}),
injectionToken: routeSpecificComponentInjectionToken, injectionToken: routeSpecificComponentInjectionToken,
}); });
const getKubeObjectStub = (kind: string, apiVersion: string) => KubeObject.create({ const someAtomInjectable = getInjectable({
apiVersion, id: "some-atom",
kind, instantiate: () => createAtom("some-atom"),
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: "some-namespace",
selfLink: "/foo",
},
}); });
const getStatusTextInjectable = ( const getKubeObjectStub = (kind: string, apiVersion: string) =>
level: KubeObjectStatusLevel, KubeObject.create({
title: string, apiVersion,
kind: string, kind,
apiVersions: string[], metadata: {
statusIsShown?: IObservableValue<boolean>, uid: "some-uid",
) => name: "some-name",
getInjectable({ resourceVersion: "some-resource-version",
id: title, namespace: "some-namespace",
selfLink: "/foo",
instantiate: () => ({ },
apiVersions,
kind,
resolve: (kubeObject: KubeObject) => {
if (statusIsShown && !statusIsShown.get()) {
return null;
}
return {
level,
text: `Some ${title} status for ${kubeObject.getName()}`,
timestamp: "2015-10-19T07:28:00Z",
};
},
enabled: computed(() => true),
}),
injectionToken: kubeObjectStatusTextInjectionToken,
}); });
const statusForIrrelevantKubeObjectKindInjectable = getStatusTextInjectable( const statusForIrrelevantKubeObjectKindInjectable = getInjectable({
KubeObjectStatusLevel.INFO, id: "status-for-irrelevant-kube-object-kind",
"irrelevant", injectionToken: kubeObjectStatusTextInjectionToken,
"some-other-kind",
["some-api-version"],
);
const statusForIrrelevantKubeObjectApiVersionInjectable = getStatusTextInjectable( instantiate: () => ({
KubeObjectStatusLevel.INFO, apiVersions: ["some-api-version"],
"irrelevant", kind: "some-other-kind",
"some-kind", enabled: computed(() => true),
["some-other-api-version"],
); resolve: () => ({
level: KubeObjectStatusLevel.INFO,
text: "irrelevant",
timestamp: "2015-10-19T07:28:00Z",
}),
}),
});
const statusForIrrelevantKubeObjectApiVersionInjectable = getInjectable({
id: "status-for-irrelevant-kube-object-api-version",
injectionToken: kubeObjectStatusTextInjectionToken,
instantiate: () => ({
apiVersions: ["some-other-api-version"],
kind: "some-kind",
enabled: computed(() => true),
resolve: () => ({
level: KubeObjectStatusLevel.INFO,
text: "irrelevant",
timestamp: "2015-10-19T07:28:00Z",
}),
}),
});

View File

@ -15,6 +15,7 @@ import type { KubeObjectStatus } from "../../../common/k8s-api/kube-object-statu
import { KubeObjectStatusLevel } from "../../../common/k8s-api/kube-object-status"; import { KubeObjectStatusLevel } from "../../../common/k8s-api/kube-object-status";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { KubeObjectStatusText } from "./kube-object-status-text-injection-token";
function statusClassName(level: KubeObjectStatusLevel): string { function statusClassName(level: KubeObjectStatusLevel): string {
switch (level) { switch (level) {
@ -78,7 +79,7 @@ export interface KubeObjectStatusIconProps {
} }
interface Dependencies { interface Dependencies {
statuses: IComputedValue<KubeObjectStatus[]>; statuses: IComputedValue<KubeObjectStatusText[]>;
} }
@observer @observer
@ -107,7 +108,11 @@ class NonInjectedKubeObjectStatusIcon extends React.Component<KubeObjectStatusIc
} }
render() { render() {
const statuses = this.props.statuses.get(); const statusTexts = this.props.statuses.get();
const statuses = statusTexts
.map((statusText) => statusText.resolve(this.props.object))
.filter(isNotEmpty);
if (statuses.length === 0) { if (statuses.length === 0) {
return null; return null;
@ -134,6 +139,10 @@ class NonInjectedKubeObjectStatusIcon extends React.Component<KubeObjectStatusIc
} }
} }
function isNotEmpty<T>(item: T | null | undefined): item is T {
return !!item;
}
export const KubeObjectStatusIcon = withInjectables<Dependencies, KubeObjectStatusIconProps>( export const KubeObjectStatusIcon = withInjectables<Dependencies, KubeObjectStatusIconProps>(
NonInjectedKubeObjectStatusIcon, NonInjectedKubeObjectStatusIcon,

View File

@ -8,10 +8,6 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { conforms, eq, includes } from "lodash/fp"; import { conforms, eq, includes } from "lodash/fp";
import { computed } from "mobx"; import { computed } from "mobx";
function isNotEmpty<T>(item: T | null | undefined): item is T {
return !!item;
}
const kubeObjectStatusTextsForObjectInjectable = getInjectable({ const kubeObjectStatusTextsForObjectInjectable = getInjectable({
id: "kube-object-status-texts-for-object", id: "kube-object-status-texts-for-object",
@ -21,9 +17,7 @@ const kubeObjectStatusTextsForObjectInjectable = getInjectable({
return computed(() => return computed(() =>
allStatusTexts allStatusTexts
.get() .get()
.filter(toKubeObjectRelated(kubeObject)) .filter(toKubeObjectRelated(kubeObject)),
.map(item => item.resolve(kubeObject))
.filter(isNotEmpty),
); );
}, },