Merge branch 'master' into wrap-pod-logs

This commit is contained in:
alexfront committed 2022-09-09 11:34:15 +03:00
commit 66dbcede1c
140 files changed
+15617 -12817

No files matched your search

@@ -80,7 +80,7 @@ describe("CatalogEntityRegistry", () => {
let catalogCategoryRegistry: CatalogCategoryRegistry;
beforeEach(() => {
const di = getDiForUnitTesting();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
entityRegistry = di.inject(catalogEntityRegistryInjectable);
catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable);
@@ -43,7 +43,7 @@ describe("Custom Category Columns", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting();
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(hotbarStoreInjectable, () => ({}));
});
@@ -16,7 +16,7 @@ describe("Custom Category Views", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting();
di = getDiForUnitTesting({ doGeneralOverrides: true });
});
it("should order items correctly over all extensions", () => {
@@ -10,7 +10,7 @@ import extensionInstallationStateStoreInjectable
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
const installFromInputInjectable = getInjectable({
id: "install-from-input",
id: "install-extension-from-input",
instantiate: (di) =>
installFromInput({
@@ -8,8 +8,8 @@ import "./release-details.scss";
import React from "react";
import { Link } from "react-router-dom";
import { Drawer, DrawerItem, DrawerTitle } from "../../drawer";
import { cssNames, stopPropagation } from "../../../utils";
import { DrawerItem, DrawerTitle } from "../../drawer";
import { stopPropagation } from "../../../utils";
import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { ConfigurationInput, MinimalResourceGroup, OnlyUserSuppliedValuesAreShownToggle, ReleaseDetailsModel } from "./release-details-model/release-details-model.injectable";
@@ -20,7 +20,6 @@ import { Badge } from "../../badge";
import { SubTitle } from "../../layout/sub-title";
import { Table, TableCell, TableHead, TableRow } from "../../table";
import { ReactiveDuration } from "../../duration/reactive-duration";
import { HelmReleaseMenu } from "../release-menu";
import { Checkbox } from "../../checkbox";
import { MonacoEditor } from "../../monaco-editor";
import { Spinner } from "../../spinner";
@@ -35,97 +34,86 @@ interface Dependencies {
}
const NonInjectedReleaseDetailsContent = observer(({ model }: Dependencies & ReleaseDetailsContentProps) => {
const isLoading = model.isLoading.get();
const loadingError = model.loadingError.get();
if (loadingError) {
return (
<div data-testid="helm-release-detail-error">
Failed to load release:
{" "}
{loadingError}
</div>
);
}
return (
<Drawer
className={cssNames("ReleaseDetails", model.activeTheme)}
usePortal={true}
open={true}
title={isLoading ? "" : model.release.getName()}
onClose={model.close}
testIdForClose="close-helm-release-detail"
toolbar={
!isLoading && (
<HelmReleaseMenu
release={model.release}
toolbar
hideDetails={model.close}
<div>
<DrawerItem name="Chart" className="chart">
<div className="flex gaps align-center">
<span>{model.release.chart}</span>
<Button
primary
label="Upgrade"
className="box right upgrade"
onClick={model.startUpgradeProcess}
data-testid="helm-release-upgrade-button"
/>
)
}
data-testid={`helm-release-details-for-${model.id}`}
>
{isLoading ? (
<Spinner center data-testid="helm-release-detail-content-spinner" />
) : (
<div>
<DrawerItem name="Chart" className="chart">
<div className="flex gaps align-center">
<span>{model.release.chart}</span>
</div>
</DrawerItem>
<Button
primary
label="Upgrade"
className="box right upgrade"
onClick={model.startUpgradeProcess}
data-testid="helm-release-upgrade-button"
/>
</div>
</DrawerItem>
<DrawerItem name="Updated">
{`${model.release.getUpdated()} ago (${model.release.updated})`}
</DrawerItem>
<DrawerItem name="Updated">
{model.release.getUpdated()}
{` ago (${model.release.updated})`}
</DrawerItem>
<DrawerItem name="Namespace">{model.release.getNs()}</DrawerItem>
<DrawerItem name="Namespace">{model.release.getNs()}</DrawerItem>
<DrawerItem name="Version" onClick={stopPropagation}>
<div className="version flex gaps align-center">
<span>{model.release.getVersion()}</span>
</div>
</DrawerItem>
<DrawerItem name="Version" onClick={stopPropagation}>
<div className="version flex gaps align-center">
<span>{model.release.getVersion()}</span>
</div>
</DrawerItem>
<DrawerItem
name="Status"
className="status"
labelsOnly>
<Badge
label={model.release.getStatus()}
className={kebabCase(model.release.getStatus())}
/>
</DrawerItem>
<DrawerItem
name="Status"
className="status"
labelsOnly>
<Badge
label={model.release.getStatus()}
className={kebabCase(model.release.getStatus())}
/>
</DrawerItem>
<ReleaseValues
configuration={model.configuration}
onlyUserSuppliedValuesAreShown={
model.onlyUserSuppliedValuesAreShown
}
/>
<ReleaseValues
configuration={model.configuration}
onlyUserSuppliedValuesAreShown={
model.onlyUserSuppliedValuesAreShown
}
/>
<DrawerTitle>Notes</DrawerTitle>
<DrawerTitle>Notes</DrawerTitle>
{model.notes && <div className="notes">{model.notes}</div>}
{model.notes && <div className="notes">{model.notes}</div>}
<DrawerTitle>Resources</DrawerTitle>
<DrawerTitle>Resources</DrawerTitle>
{model.groupedResources.length > 0 && (
<div className="resources">
{model.groupedResources.map((group) => (
<ResourceGroup key={group.kind} group={group} />
))}
</div>
)}
{model.groupedResources.length > 0 && (
<div className="resources">
{model.groupedResources.map((group) => (
<ResourceGroup key={group.kind} group={group} />
))}
</div>
)}
</Drawer>
</div>
);
});
export const ReleaseDetailsContent = withInjectables<Dependencies, ReleaseDetailsContentProps>(NonInjectedReleaseDetailsContent, {
getProps: (di, props) => ({
model: di.inject(releaseDetailsModelInjectable, props.targetRelease),
getPlaceholder: () => <Spinner center data-testid="helm-release-detail-content-spinner" />,
getProps: async (di, props) => ({
model: await di.inject(releaseDetailsModelInjectable, props.targetRelease),
...props,
}),
});
@@ -0,0 +1,53 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./release-details.scss";
import React from "react";
import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import type { ReleaseDetailsModel } from "./release-details-model/release-details-model.injectable";
import releaseDetailsModelInjectable from "./release-details-model/release-details-model.injectable";
import { HelmReleaseMenu } from "../release-menu";
interface ReleaseDetailsDrawerProps {
targetRelease: TargetHelmRelease;
}
interface Dependencies {
model: ReleaseDetailsModel;
navigateToHelmReleases: () => void;
}
const NonInjectedReleaseDetailsDrawerToolbar = observer(({
model,
navigateToHelmReleases,
}: Dependencies & ReleaseDetailsDrawerProps) => (
model.loadingError.get()
? null
: (
<HelmReleaseMenu
release={model.release}
toolbar
hideDetails={navigateToHelmReleases}
/>
)
));
export const ReleaseDetailsDrawerToolbar = withInjectables<
Dependencies,
ReleaseDetailsDrawerProps
>(NonInjectedReleaseDetailsDrawerToolbar, {
getPlaceholder: () => <></>,
getProps: async (di, props) => ({
model: await di.inject(releaseDetailsModelInjectable, props.targetRelease),
navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable),
...props,
}),
});
@@ -0,0 +1,58 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./release-details.scss";
import React from "react";
import { Drawer } from "../../drawer";
import { cssNames } from "../../../utils";
import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import type { ActiveThemeType } from "../../../themes/active-type.injectable";
import activeThemeTypeInjectable from "../../../themes/active-type.injectable";
import { ReleaseDetailsContent } from "./release-details-content";
import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import { ReleaseDetailsDrawerToolbar } from "./release-details-drawer-toolbar";
interface ReleaseDetailsDrawerProps {
targetRelease: TargetHelmRelease;
}
interface Dependencies {
activeThemeType: ActiveThemeType;
closeDrawer: () => void;
}
const NonInjectedReleaseDetailsDrawer = observer(({
activeThemeType,
closeDrawer,
targetRelease,
}: Dependencies & ReleaseDetailsDrawerProps) => (
<Drawer
className={cssNames("ReleaseDetails", activeThemeType.get())}
usePortal={true}
open={true}
title={targetRelease.name}
onClose={closeDrawer}
testIdForClose="close-helm-release-detail"
toolbar={<ReleaseDetailsDrawerToolbar targetRelease={targetRelease} />}
data-testid={`helm-release-details-for-${targetRelease.namespace}/${targetRelease.name}`}
>
<ReleaseDetailsContent targetRelease={targetRelease} />
</Drawer>
));
export const ReleaseDetailsDrawer = withInjectables<
Dependencies,
ReleaseDetailsDrawerProps
>(NonInjectedReleaseDetailsDrawer, {
getProps: (di, props) => ({
activeThemeType: di.inject(activeThemeTypeInjectable),
closeDrawer: di.inject(navigateToHelmReleasesInjectable),
...props,
}),
});
@@ -7,6 +7,7 @@ import type { HelmReleaseDto } from "../../../../../../common/k8s-api/endpoints/
import callForHelmReleasesInjectable from "../../../call-for-helm-releases/call-for-helm-releases.injectable";
import type { HelmReleaseDetails } from "./call-for-helm-release-details/call-for-helm-release-details.injectable";
import callForHelmReleaseDetailsInjectable from "./call-for-helm-release-details/call-for-helm-release-details.injectable";
import type { AsyncResult } from "../../../../../../common/utils/async-result";
export interface DetailedHelmRelease {
release: HelmReleaseDto;
@@ -16,7 +17,7 @@ export interface DetailedHelmRelease {
export type CallForHelmRelease = (
name: string,
namespace: string
) => Promise<DetailedHelmRelease | undefined>;
) => Promise<AsyncResult<DetailedHelmRelease | undefined>>;
const callForHelmReleaseInjectable = getInjectable({
id: "call-for-helm-release",
@@ -36,10 +37,13 @@ const callForHelmReleaseInjectable = getInjectable({
);
if (!release) {
return undefined;
return {
callWasSuccessful: false,
error: `Release ${name} didn't exist in ${namespace} namespace.`,
};
}
return { release, details };
return { callWasSuccessful: true, response: { release, details }};
};
},
});
@@ -26,16 +26,14 @@ import showSuccessNotificationInjectable from "../../../notifications/show-succe
import React from "react";
import createUpgradeChartTabInjectable from "../../../dock/upgrade-chart/create-upgrade-chart-tab.injectable";
import type { HelmRelease } from "../../../../../common/k8s-api/endpoints/helm-releases.api";
import type { NavigateToHelmReleases } from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import navigateToHelmReleasesInjectable from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import assert from "assert";
import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
import activeThemeInjectable from "../../../../themes/active.injectable";
const releaseDetailsModelInjectable = getInjectable({
id: "release-details-model",
instantiate: (di, targetRelease: TargetHelmRelease) => {
instantiate: async (di, targetRelease: TargetHelmRelease) => {
const callForHelmRelease = di.inject(callForHelmReleaseInjectable);
const callForHelmReleaseConfiguration = di.inject(callForHelmReleaseConfigurationInjectable);
const activeTheme = di.inject(activeThemeInjectable);
@@ -45,7 +43,6 @@ const releaseDetailsModelInjectable = getInjectable({
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
const createUpgradeChartTab = di.inject(createUpgradeChartTabInjectable);
const navigateToHelmReleases = di.inject(navigateToHelmReleasesInjectable);
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
const model = new ReleaseDetailsModel({
callForHelmRelease,
@@ -60,56 +57,52 @@ const releaseDetailsModelInjectable = getInjectable({
navigateToHelmReleases,
});
const load = withOrphanPromise(model.load);
// TODO: Reorganize Drawer to allow setting of header-bar in children to make "getPlaceholder" from injectable usable.
load();
await model.load();
return model;
},
lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, release: TargetHelmRelease) =>
`${release.namespace}/${release.name}`,
getInstanceKey: (di, release: TargetHelmRelease) => `${release.namespace}/${release.name}`,
}),
});
export default releaseDetailsModelInjectable;
export interface OnlyUserSuppliedValuesAreShownToggle {
value: IObservableValue<boolean>;
readonly value: IObservableValue<boolean>;
toggle: () => Promise<void>;
}
export interface ConfigurationInput {
nonSavedValue: IObservableValue<string>;
isLoading: IObservableValue<boolean>;
isSaving: IObservableValue<boolean>;
readonly nonSavedValue: IObservableValue<string>;
readonly isLoading: IObservableValue<boolean>;
readonly isSaving: IObservableValue<boolean>;
onChange: (value: string) => void;
save: () => Promise<void>;
}
interface Dependencies {
readonly targetRelease: TargetHelmRelease;
readonly activeTheme: IComputedValue<LensTheme>;
callForHelmRelease: CallForHelmRelease;
targetRelease: TargetHelmRelease;
activeTheme: IComputedValue<LensTheme>;
callForHelmReleaseConfiguration: CallForHelmReleaseConfiguration;
getResourceDetailsUrl: GetResourceDetailsUrl;
updateRelease: CallForHelmReleaseUpdate;
showCheckedErrorNotification: ShowCheckedErrorNotification;
showSuccessNotification: ShowNotification;
createUpgradeChartTab: (release: HelmRelease) => string;
navigateToHelmReleases: NavigateToHelmReleases;
navigateToHelmReleases: () => void;
}
export class ReleaseDetailsModel {
id = `${this.dependencies.targetRelease.namespace}/${this.dependencies.targetRelease.name}`;
readonly id = `${this.dependencies.targetRelease.namespace}/${this.dependencies.targetRelease.name}`;
constructor(private dependencies: Dependencies) {}
constructor(private readonly dependencies: Dependencies) {}
private detailedRelease = observable.box<DetailedHelmRelease | undefined>();
private readonly detailedRelease = observable.box<DetailedHelmRelease | undefined>();
readonly isLoading = observable.box(false);
readonly loadingError = observable.box<string>();
readonly configuration: ConfigurationInput = {
nonSavedValue: observable.box(""),
@@ -176,26 +169,26 @@ export class ReleaseDetailsModel {
};
load = async () => {
runInAction(() => {
this.isLoading.set(true);
});
const { name, namespace } = this.dependencies.targetRelease;
const detailedRelease = await this.dependencies.callForHelmRelease(
const result = await this.dependencies.callForHelmRelease(
name,
namespace,
);
if (!result.callWasSuccessful) {
runInAction(() => {
this.loadingError.set(result.error);
});
return;
}
runInAction(() => {
this.detailedRelease.set(detailedRelease);
this.detailedRelease.set(result.response);
});
await this.loadConfiguration();
runInAction(() => {
this.isLoading.set(false);
});
};
private loadConfiguration = async () => {
@@ -269,7 +262,7 @@ export class ReleaseDetailsModel {
startUpgradeProcess = () => {
this.dependencies.createUpgradeChartTab(this.release);
this.close();
this.dependencies.navigateToHelmReleases();
};
}
@@ -49,16 +49,19 @@
.TableCell {
text-overflow: unset;
word-break: break-word;
min-width: 100px;
&.name {
flex-basis: auto;
flex-grow: 0;
width: 230px;
flex-grow: 3;
}
&.volume {
flex-basis: 30%;
&.namespace {
flex-grow: 1;
}
&.age {
flex-basis: 100px;
flex-grow: 0;
flex-shrink: 0;
}
}
}
@@ -11,9 +11,9 @@ import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { ReleaseDetailsContent } from "./release-details-content";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import targetHelmReleaseInjectable from "./target-helm-release.injectable";
import { ReleaseDetailsDrawer } from "./release-details-drawer";
interface Dependencies {
targetRelease: IComputedValue<
@@ -25,7 +25,7 @@ const NonInjectedReleaseDetails = observer(
({ targetRelease }: Dependencies) => {
const release = targetRelease.get();
return release ? <ReleaseDetailsContent targetRelease={release} /> : null;
return release ? <ReleaseDetailsDrawer targetRelease={release} /> : null;
},
);
@@ -47,8 +47,8 @@ const dummyDeployment = new Deployment({
},
},
terminationMessagePath: "dummy",
terminationMessagePolicy: "dummy",
imagePullPolicy: "dummy",
terminationMessagePolicy: "File",
imagePullPolicy: "Always",
}],
restartPolicy: "dummy",
terminationGracePeriodSeconds: 10,
@@ -0,0 +1,182 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ContainerEnv /> renders both env and configMapRef envFrom 1`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
>
<div
class="variable"
>
<span
class="var-name"
>
foobar
</span>
:
https://localhost:12345
</div>
<div
class="variable"
>
<span
class="var-name"
>
configFoo
</span>
:
configBar
</div>
</span>
</div>
</div>
</body>
`;
exports[`<ContainerEnv /> renders env 1`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
>
<div
class="variable"
>
<span
class="var-name"
>
foobar
</span>
:
https://localhost:12345
</div>
</span>
</div>
</div>
</body>
`;
exports[`<ContainerEnv /> renders env 2`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
>
<div
class="variable"
>
<span
class="var-name"
>
foobar
</span>
:
https://localhost:12345
</div>
</span>
</div>
</div>
</body>
`;
exports[`<ContainerEnv /> renders envFrom when given a configMapRef 1`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
>
<div
class="variable"
>
<span
class="var-name"
>
configFoo
</span>
:
configBar
</div>
</span>
</div>
</div>
</body>
`;
exports[`<ContainerEnv /> renders envFrom when given a secretRef 1`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
>
<div
class="variable"
>
<span
class="var-name"
>
bar
</span>
:
secretKeyRef(my-secret.bar)
 
<i
class="Icon secret-button material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="visibility"
>
visibility
</span>
</i>
<div>
Show
</div>
</div>
</span>
</div>
</div>
</body>
`;
@@ -0,0 +1,252 @@
/**
* 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 { ConfigMapStore } from "../../+config-maps/store";
import configMapStoreInjectable from "../../+config-maps/store.injectable";
import type { SecretStore } from "../../+config-secrets/store";
import secretStoreInjectable from "../../+config-secrets/store.injectable";
import type { Container } from "../../../../common/k8s-api/endpoints";
import { Secret, ConfigMap, Pod, SecretType } from "../../../../common/k8s-api/endpoints";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import { ContainerEnvironment } from "../pod-container-env";
describe("<ContainerEnv />", () => {
let render: DiRender;
let secretStore: jest.Mocked<Pick<SecretStore, "load" | "getByName">>;
let configMapStore: jest.Mocked<Pick<ConfigMapStore, "load" | "getByName">>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
secretStore = ({
load: jest.fn().mockImplementation(async () => {
return {} as Secret;
}),
getByName: jest.fn(),
});
configMapStore = ({
load: jest.fn().mockImplementation(async () => {
return {} as ConfigMap;
}),
getByName: jest.fn(),
});
di.override(secretStoreInjectable, () => secretStore as jest.Mocked<SecretStore>);
di.override(configMapStoreInjectable, () => configMapStore as jest.Mocked<ConfigMapStore>);
render = renderFor(di);
});
it("renders env", () => {
const container: Container = {
image: "my-image",
name: "my-first-container",
env: [{
name: "foobar",
value: "https://localhost:12345",
}],
};
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "default",
resourceVersion: "1",
selfLink: "/api/v1/pods/default/my-pod",
uid: "1234",
},
spec: {
containers: [container],
},
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.baseElement).toMatchSnapshot();
});
it("renders envFrom when given a configMapRef", () => {
configMapStore.getByName.mockImplementation((name, namespace) => {
expect(name).toBe("my-config-map");
expect(namespace).toBe("default");
return new ConfigMap({
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: "my-config-map",
namespace: "default",
resourceVersion: "2",
selfLink: "/api/v1/configmaps/default/my-config-map",
uid: "456",
},
data: {
configFoo: "configBar",
},
});
});
const container: Container = {
image: "my-image",
name: "my-first-container",
envFrom: [{
configMapRef: {
name: "my-config-map",
},
}],
};
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "default",
resourceVersion: "1",
selfLink: "/api/v1/pods/default/my-pod",
uid: "1234",
},
spec: {
containers: [container],
},
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.baseElement).toMatchSnapshot();
});
it("renders envFrom when given a secretRef", () => {
secretStore.getByName.mockImplementation((name, namespace) => {
expect(name).toBe("my-secret");
expect(namespace).toBe("default");
return new Secret({
apiVersion: "v1",
kind: "Secret",
metadata: {
name: "my-secret",
namespace: "default",
resourceVersion: "3",
selfLink: "/api/v1/secrets/default/my-secret",
uid: "237",
},
type: SecretType.BasicAuth,
data: {
bar: "bat",
},
});
});
const container: Container = {
image: "my-image",
name: "my-first-container",
envFrom: [{
secretRef: {
name: "my-secret",
},
}],
};
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "default",
resourceVersion: "1",
selfLink: "/api/v1/pods/default/my-pod",
uid: "1234",
},
spec: {
containers: [container],
},
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.baseElement).toMatchSnapshot();
});
it("renders env", () => {
const container: Container = {
image: "my-image",
name: "my-first-container",
env: [{
name: "foobar",
value: "https://localhost:12345",
}],
};
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "default",
resourceVersion: "1",
selfLink: "/api/v1/pods/default/my-pod",
uid: "1234",
},
spec: {
containers: [container],
},
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.baseElement).toMatchSnapshot();
});
it("renders both env and configMapRef envFrom", () => {
configMapStore.getByName.mockImplementation((name, namespace) => {
expect(name).toBe("my-config-map");
expect(namespace).toBe("default");
return new ConfigMap({
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: "my-config-map",
namespace: "default",
resourceVersion: "2",
selfLink: "/api/v1/configmaps/default/my-config-map",
uid: "456",
},
data: {
configFoo: "configBar",
},
});
});
const container: Container = {
image: "my-image",
name: "my-first-container",
envFrom: [{
configMapRef: {
name: "my-config-map",
},
}],
env: [{
name: "foobar",
value: "https://localhost:12345",
}],
};
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "default",
resourceVersion: "1",
selfLink: "/api/v1/pods/default/my-pod",
uid: "1234",
},
spec: {
containers: [container],
},
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.baseElement).toMatchSnapshot();
});
});
@@ -7,26 +7,39 @@ import "./pod-container-env.scss";
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react";
import type { PodContainer, Secret } from "../../../common/k8s-api/endpoints";
import type { Container, EnvVarKeySelector, Secret } from "../../../common/k8s-api/endpoints";
import { DrawerItem } from "../drawer";
import { autorun } from "mobx";
import { secretStore } from "../+config-secrets/legacy-store";
import { configMapStore } from "../+config-maps/legacy-store";
import { Icon } from "../icon";
import { base64, cssNames, iter } from "../../utils";
import { base64, cssNames, object } from "../../utils";
import _ from "lodash";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { ConfigMapStore } from "../+config-maps/store";
import type { SecretStore } from "../+config-secrets/store";
import configMapStoreInjectable from "../+config-maps/store.injectable";
import secretStoreInjectable from "../+config-secrets/store.injectable";
export interface ContainerEnvironmentProps {
container: PodContainer;
container: Container;
namespace: string;
}
export const ContainerEnvironment = observer((props: ContainerEnvironmentProps) => {
const { container: { env, envFrom = [] }, namespace } = props;
interface Dependencies {
configMapStore: ConfigMapStore;
secretStore: SecretStore;
}
const NonInjectedContainerEnvironment = observer((props: Dependencies & ContainerEnvironmentProps) => {
const {
container: { env, envFrom = [] },
namespace,
configMapStore,
secretStore,
} = props;
useEffect( () => autorun(() => {
for (const { valueFrom } of env ?? []) {
if (valueFrom?.configMapKeyRef) {
if (valueFrom?.configMapKeyRef?.name) {
configMapStore.load({ name: valueFrom.configMapKeyRef.name, namespace });
}
}
@@ -51,100 +64,97 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
if (value) {
secretValue = value;
}
if (valueFrom) {
} else if (valueFrom) {
const { fieldRef, secretKeyRef, configMapKeyRef } = valueFrom;
if (fieldRef) {
const { apiVersion, fieldPath } = fieldRef;
secretValue = `fieldRef(${apiVersion}:${fieldPath})`;
}
if (secretKeyRef) {
} else if (secretKeyRef?.name) {
secretValue = (
<SecretKey
reference={secretKeyRef}
namespace={namespace}
secretStore={secretStore}
/>
);
}
if (configMapKeyRef) {
} else if (configMapKeyRef?.name) {
const { name, key } = configMapKeyRef;
const configMap = configMapStore.getByName(name, namespace);
secretValue = configMap ?
configMap.data[key] :
`configMapKeyRef(${name}${key})`;
secretValue = configMap
? configMap.data[key]
: `configMapKeyRef(${name}${key})`;
}
}
return (
<div className="variable" key={name}>
<span className="var-name">{name}</span>
:
{` : `}
{secretValue}
</div>
);
});
};
const renderEnvFrom = () => {
return Array.from(iter.filterFlatMap(envFrom, vars => {
if (vars.configMapRef?.name) {
return renderEnvFromConfigMap(vars.configMapRef.name);
}
const renderEnvFrom = () => (
envFrom
.flatMap(({ configMapRef, secretRef, prefix }) => {
if (configMapRef?.name) {
return renderEnvFromConfigMap(configMapRef.name, prefix);
}
if (vars.secretRef?.name) {
return renderEnvFromSecret(vars.secretRef.name);
}
if (secretRef?.name) {
return renderEnvFromSecret(secretRef.name, prefix);
}
return null;
}));
};
return null;
})
);
const renderEnvFromConfigMap = (configMapName: string) => {
const renderEnvFromConfigMap = (configMapName: string, prefix: string | undefined) => {
const configMap = configMapStore.getByName(configMapName, namespace);
if (!configMap) return null;
return Object.entries(configMap.data).map(([name, value]) => (
<div className="variable" key={name}>
<span className="var-name">{name}</span>
:
{value}
</div>
));
return object.entries(configMap.data)
.map(([name, value]) => (
<div className="variable" key={name}>
<span className="var-name">
{prefix}
{name}
</span>
{` : `}
{value}
</div>
));
};
const renderEnvFromSecret = (secretName: string) => {
const renderEnvFromSecret = (secretName: string, prefix: string | undefined) => {
const secret = secretStore.getByName(secretName, namespace);
if (!secret) return null;
return Object.keys(secret.data).map(key => {
const secretKeyRef = {
name: secret.getName(),
key,
};
const value = (
<SecretKey
reference={secretKeyRef}
namespace={namespace}
/>
);
return (
return Object.keys(secret.data)
.map(key => (
<div className="variable" key={key}>
<span className="var-name">{key}</span>
:
{value}
<span className="var-name">
{prefix}
{key}
</span>
{` : `}
<SecretKey
reference={{
name: secret.getName(),
key,
}}
namespace={namespace}
secretStore={secretStore}
/>
</div>
);
});
));
};
return (
@@ -155,19 +165,34 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
);
});
export interface SecretKeyProps {
reference: {
name: string;
key: string;
};
export const ContainerEnvironment = withInjectables<Dependencies, ContainerEnvironmentProps>(NonInjectedContainerEnvironment, {
getProps: (di, props) => ({
...props,
configMapStore: di.inject(configMapStoreInjectable),
secretStore: di.inject(secretStoreInjectable),
}),
});
interface SecretKeyProps {
reference: EnvVarKeySelector;
namespace: string;
secretStore: SecretStore;
}
const SecretKey = (props: SecretKeyProps) => {
const { reference: { name, key }, namespace } = props;
const {
reference: { name, key },
namespace,
secretStore,
} = props;
const [loading, setLoading] = useState(false);
const [secret, setSecret] = useState<Secret>();
if (!name) {
return null;
}
const showKey = async (evt: React.MouseEvent) => {
evt.preventDefault();
setLoading(true);
@@ -6,7 +6,7 @@
import "./pod-details-container.scss";
import React from "react";
import type { PodContainer, PodContainerStatus, Pod } from "../../../common/k8s-api/endpoints";
import type { Container, PodContainerStatus, Pod } from "../../../common/k8s-api/endpoints";
import { DrawerItem } from "../drawer";
import { cssNames, isDefined } from "../../utils";
import { StatusBrick } from "../status-brick";
@@ -27,7 +27,7 @@ import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-activ
export interface PodDetailsContainerProps {
pod: Pod;
container: PodContainer;
container: Container;
metrics?: Partial<Record<string, MetricData>>;
}
@@ -43,12 +43,12 @@ const dummyReplicaSet = new ReplicaSet({
containers: [{
name: "dummy",
image: "dummy",
imagePullPolicy: "dummy",
imagePullPolicy: "Always",
}],
initContainers: [{
name: "dummy",
image: "dummy",
imagePullPolicy: "dummy",
imagePullPolicy: "Always",
}],
priority: 1,
serviceAccountName: "dummy",
@@ -23,8 +23,8 @@ const spec: PodSpec = {
},
},
terminationMessagePath: "test",
terminationMessagePolicy: "test",
imagePullPolicy: "test",
terminationMessagePolicy: "File",
imagePullPolicy: "Always",
}],
restartPolicy: "restart",
terminationGracePeriodSeconds: 1200,
@@ -22,7 +22,7 @@ export const dockerPod = new Pod({
{
name: "docker-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
serviceAccountName: "dummy",
@@ -66,24 +66,24 @@ export const deploymentPod1 = new Pod({
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
serviceAccountName: "dummy",
@@ -127,24 +127,24 @@ export const deploymentPod2 = new Pod({
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
serviceAccountName: "dummy",
@@ -188,12 +188,12 @@ export const deploymentPod3 = new Pod({
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull",
imagePullPolicy: "IfNotPresent",
},
],
serviceAccountName: "dummy",
@@ -3,13 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { PodContainer, Pod } from "../../../../common/k8s-api/endpoints";
import type { Container, Pod } from "../../../../common/k8s-api/endpoints";
import type { TabId } from "../dock/store";
import createLogsTabInjectable from "./create-logs-tab.injectable";
export interface PodLogsTabData {
selectedPod: Pod;
selectedContainer: PodContainer;
selectedContainer: Container;
}
const createPodLogsTabInjectable = getInjectable({
@@ -12,7 +12,7 @@ import { Badge } from "../../badge";
import type { SelectOption } from "../../select";
import { Select } from "../../select";
import type { LogTabViewModel } from "./logs-view-model";
import type { PodContainer, Pod } from "../../../../common/k8s-api/endpoints";
import type { Container, Pod } from "../../../../common/k8s-api/endpoints";
import type { SingleValue } from "react-select";
export interface LogResourceSelectorProps {
@@ -40,7 +40,7 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
}));
const allContainers = pod.getAllContainers();
const container = allContainers.find(container => container.name === selectedContainer) ?? null;
const onContainerChange = (option: SingleValue<SelectOption<PodContainer>>) => {
const onContainerChange = (option: SingleValue<SelectOption<Container>>) => {
if (!option) {
return;
}
@@ -105,7 +105,7 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
menuClass="pod-selector-menu"
/>
<span>Container</span>
<Select<PodContainer, SelectOption<PodContainer>, false>
<Select<Container, SelectOption<Container>, false>
id="container-selector-input"
options={containerSelectOptions}
value={container}
@@ -14,7 +14,7 @@ import { ConfirmDialog } from "../confirm-dialog";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { computed } from "mobx";
import { computed, runInAction } from "mobx";
import clusterInjectable from "./dependencies/cluster.injectable";
import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor";
@@ -34,11 +34,13 @@ describe("kube-object-menu", () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.register(
someMenuItemInjectable,
someOtherMenuItemInjectable,
someAnotherMenuItemInjectable,
);
runInAction(() => {
di.register(
someMenuItemInjectable,
someOtherMenuItemInjectable,
someAnotherMenuItemInjectable,
);
});
render = renderFor(di);
@@ -4,6 +4,7 @@
*/
import type { Injectable } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
@@ -38,6 +39,7 @@ const toItemInjectableFor = (extension: LensRendererExtension, getRandomId: () =
const id = `${getRandomId()}-status-bar-item-for-extension-${extension.sanitizedExtensionId}`;
let component: React.ComponentType;
let position: "left" | "right";
const visible: IComputedValue<boolean> | undefined = registration?.visible;
if (registration?.item) {
const { item } = registration;
@@ -74,7 +76,7 @@ const toItemInjectableFor = (extension: LensRendererExtension, getRandomId: () =
instantiate: () => ({
component,
position,
visible: computed(() => true),
visible: visible ?? computed(() => true),
}),
injectionToken: statusBarItemInjectionToken,
@@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IComputedValue } from "mobx";
/**
* The props for StatusBar item component
*/
@@ -38,4 +40,9 @@ export interface StatusBarRegistration {
* The newer API, allows for registering a component instead of a ReactNode
*/
components?: StatusBarComponents;
/**
* If specified, controls item visibility
*/
visible?: IComputedValue<boolean>;
}
@@ -143,7 +143,9 @@ export const getApplicationBuilder = () => {
doGeneralOverrides: true,
});
mainDi.register(mainExtensionsStateInjectable);
runInAction(() => {
mainDi.register(mainExtensionsStateInjectable);
});
const overrideChannelsForWindow = overrideChannels(mainDi);
@@ -184,7 +186,9 @@ export const getApplicationBuilder = () => {
overrideChannelsForWindow(windowDi, windowId);
windowDi.register(rendererExtensionsStateInjectable);
runInAction(() => {
windowDi.register(rendererExtensionsStateInjectable);
});
windowDi.override(
currentlyInClusterFrameInjectable,
@@ -552,9 +556,11 @@ export const getApplicationBuilder = () => {
getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}),
);
mainExtensionInstances.forEach(
enableExtensionFor(mainDi, mainExtensionsStateInjectable),
);
runInAction(() => {
mainExtensionInstances.forEach(
enableExtensionFor(mainDi, mainExtensionsStateInjectable),
);
});
});
},
+15 -12
View File
@@ -6,24 +6,27 @@
import { createContainer } from "@ogre-tools/injectable";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import { runInAction } from "mobx";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
export const getDi = () => {
const di = createContainer("renderer");
registerMobX(di);
autoRegister({
di,
requireContexts: [
require.context("./", true, /\.injectable\.(ts|tsx)$/),
require.context("../common", true, /\.injectable\.(ts|tsx)$/),
require.context("../extensions", true, /\.injectable\.(ts|tsx)$/),
require.context("../features", true, /.*\/(renderer|common)\/.*\.injectable\.(ts|tsx)$/),
],
});
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
runInAction(() => {
registerMobX(di);
autoRegister({
di,
requireContexts: [
require.context("./", true, /\.injectable\.(ts|tsx)$/),
require.context("../common", true, /\.injectable\.(ts|tsx)$/),
require.context("../extensions", true, /\.injectable\.(ts|tsx)$/),
require.context("../features", true, /.*\/(renderer|common)\/.*\.injectable\.(ts|tsx)$/),
],
});
});
return di;
};
+15 -11
View File
@@ -33,7 +33,7 @@ import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.inject
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable";
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
import { observable, computed } from "mobx";
import { observable, computed, runInAction } from "mobx";
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
import appVersionInjectable from "../common/vars/app-version.injectable";
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
@@ -68,26 +68,30 @@ import getEntitySettingCommandsInjectable from "./components/command-palette/reg
import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable";
import type { GlobalOverride } from "../common/test-utils/get-global-override";
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
const {
doGeneralOverrides = false,
} = opts;
export const getDiForUnitTesting = (
opts: { doGeneralOverrides?: boolean } = {},
) => {
const { doGeneralOverrides = false } = opts;
const di = createContainer("renderer");
registerMobX(di);
di.preventSideEffects();
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
const injectables: Injectable<any, any, any>[] = (global as any).rendererInjectablePaths.map(
const injectables: Injectable<any, any, any>[] = (
global as any
).rendererInjectablePaths.map(
(filePath: string) => require(filePath).default,
);
chunk(100)(injectables).forEach(chunkInjectables => {
di.register(...chunkInjectables);
});
runInAction(() => {
registerMobX(di);
di.preventSideEffects();
chunk(100)(injectables).forEach((chunkInjectables) => {
di.register(...chunkInjectables);
});
});
if (doGeneralOverrides) {
const globalOverrides: GlobalOverride[] = (global as any).rendererGlobalOverridePaths.map(
@@ -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 } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import type { LensThemeType } from "./store";
import themeStoreInjectable from "./store.injectable";
export type ActiveThemeType = IComputedValue<LensThemeType>;
const activeThemeTypeInjectable = getInjectable({
id: "active-theme-type",
instantiate: (di) => {
const store = di.inject(themeStoreInjectable);
return computed(() => store.activeTheme.type);
},
});
export default activeThemeTypeInjectable;
+2 -2
View File
@@ -17,10 +17,10 @@ import type { ReadonlyDeep } from "type-fest/source/readonly-deep";
import assert from "assert";
export type ThemeId = string;
export type LensThemeType = "dark" | "light";
export interface LensTheme {
name: string;
type: "dark" | "light";
type: LensThemeType;
colors: Record<string, string>;
description: string;
author: string;
@@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { runInAction } from "mobx";
import type { CreateStorage } from "./create-storage";
import createStorageInjectable from "./create-storage.injectable";
@@ -19,8 +20,10 @@ export const controlWhenStoragesAreReady = (di: DiContainer) => {
return storage;
};
// TODO: Remove when typing is added to the library
(di as any).decorateFunction(createStorageInjectable, decorated);
runInAction(() => {
// TODO: Remove when typing is added to the library
(di as any).decorateFunction(createStorageInjectable, decorated);
});
return async () => {
await Promise.all(storagesAreReady);