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

Readd space between key and value within ContainerEnv

- Add some snapshot tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-06-16 09:52:57 -04:00
parent 7ad7c89061
commit db2664a82e
11 changed files with 553 additions and 102 deletions

View File

@ -22,7 +22,7 @@ interface Dependencies {
clusterId: string | undefined;
}
export function NonInjectedClusterNoMetrics({ className, navigateToEntitySettings, clusterId }: Dependencies & ClusterNoMetricsProps) {
function NonInjectedClusterNoMetrics({ className, navigateToEntitySettings, clusterId }: Dependencies & ClusterNoMetricsProps) {
function openMetricSettingsPage() {
if (clusterId) {
navigateToEntitySettings(clusterId, "metrics");

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React, { useState } from "react";
import type { Secret } from "../../../common/k8s-api/endpoints";
import { base64, prevDefault } from "../../utils";
import { Icon } from "../icon";
export interface SecretKeyProps {
secret: Secret;
key: string;
}
export const SecretKey = ({ secret, key }: SecretKeyProps) => {
const [showValue, setShowValue] = useState(false);
const showKey = () => setShowValue(true);
const value = secret?.data?.[key];
if (showValue && value) {
return <>{base64.decode(value)}</>;
}
return (
<>
{`secretKeyRef(${name}.${key})`}
&nbsp;
<Icon
className="secret-button"
material="visibility"
tooltip="Show"
onClick={prevDefault(showKey)}
/>
</>
);
};

View File

@ -30,7 +30,7 @@ interface Dependencies {
openAddNamespaceDialog: () => void;
}
export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies) => (
const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies) => (
<TabLayout>
<KubeObjectListLayout
isConfigurable

View File

@ -0,0 +1,174 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ContainerEnv /> renders 1`] = `
<body>
<div>
<div
class="DrawerItem ContainerEnvironment"
>
<span
class="name"
>
Environment
</span>
<span
class="value"
/>
</div>
</div>
</body>
`;
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>
</div>
</body>
`;

View File

@ -0,0 +1,269 @@
/**
* 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 { PodContainer } 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(),
getByName: jest.fn(),
});
configMapStore = ({
load: jest.fn(),
getByName: jest.fn(),
});
di.override(secretStoreInjectable, () => secretStore as jest.Mocked<SecretStore>);
di.override(configMapStoreInjectable, () => configMapStore as jest.Mocked<ConfigMapStore>);
render = renderFor(di);
});
it("renders", () => {
const container: PodContainer = {
image: "my-image",
name: "my-first-container",
};
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: PodContainer = {
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: PodContainer = {
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,
});
});
const container: PodContainer = {
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: PodContainer = {
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: PodContainer = {
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();
});
});

View File

@ -5,26 +5,39 @@
import "./pod-container-env.scss";
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import type { PodContainer, Secret } from "../../../common/k8s-api/endpoints";
import type { PodContainer } 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 { iter } from "../../utils";
import _ from "lodash";
import type { SecretStore } from "../+config-secrets/store";
import type { ConfigMapStore } from "../+config-maps/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import configMapStoreInjectable from "../+config-maps/store.injectable";
import secretStoreInjectable from "../+config-secrets/store.injectable";
import { SecretKey } from "../+config-secrets/secret-key";
export interface ContainerEnvironmentProps {
container: PodContainer;
namespace: string;
}
export const ContainerEnvironment = observer((props: ContainerEnvironmentProps) => {
const { container: { env, envFrom = [] }, namespace } = props;
interface Dependencies {
secretStore: SecretStore;
configMapStore: ConfigMapStore;
}
useEffect( () => autorun(() => {
const NonInjectedContainerEnvironment = observer((props: ContainerEnvironmentProps & Dependencies) => {
const {
container: { env, envFrom },
namespace,
configMapStore,
secretStore,
} = props;
useEffect(() => autorun(() => {
for (const { valueFrom } of env ?? []) {
if (valueFrom?.configMapKeyRef) {
configMapStore.load({ name: valueFrom.configMapKeyRef.name, namespace });
@ -63,28 +76,32 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
}
if (secretKeyRef) {
secretValue = (
<SecretKey
reference={secretKeyRef}
namespace={namespace}
/>
);
const secret = secretStore.getByName(secretKeyRef.name, namespace);
if (secret) {
secretValue = (
<SecretKey
secret={secret}
key={secretKeyRef.key}
/>
);
}
}
if (configMapKeyRef) {
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>
);
@ -92,7 +109,7 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
};
const renderEnvFrom = () => {
return Array.from(iter.filterFlatMap(envFrom, vars => {
return Array.from(iter.filterFlatMap(envFrom ?? [], vars => {
if (vars.configMapRef?.name) {
return renderEnvFromConfigMap(vars.configMapRef.name);
}
@ -113,7 +130,7 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
return Object.entries(configMap.data).map(([name, value]) => (
<div className="variable" key={name}>
<span className="var-name">{name}</span>
:
{`= `}
{value}
</div>
));
@ -124,75 +141,31 @@ export const ContainerEnvironment = observer((props: ContainerEnvironmentProps)
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}
{`= `}
<SecretKey
secret={secret}
key={key}
/>
</div>
);
});
));
};
return (
<DrawerItem name="Environment" className="ContainerEnvironment">
{env && renderEnv()}
{envFrom && renderEnvFrom()}
{renderEnv()}
{renderEnvFrom()}
</DrawerItem>
);
});
export interface SecretKeyProps {
reference: {
name: string;
key: string;
};
namespace: string;
}
const SecretKey = (props: SecretKeyProps) => {
const { reference: { name, key }, namespace } = props;
const [loading, setLoading] = useState(false);
const [secret, setSecret] = useState<Secret>();
const showKey = async (evt: React.MouseEvent) => {
evt.preventDefault();
setLoading(true);
const secret = await secretStore.load({ name, namespace });
setLoading(false);
setSecret(secret);
};
const value = secret?.data?.[key];
if (value) {
return <>{base64.decode(value)}</>;
}
return (
<>
{`secretKeyRef(${name}.${key})`}
&nbsp;
<Icon
className={cssNames("secret-button", { loading })}
material="visibility"
tooltip="Show"
onClick={showKey}
/>
</>
);
};
export const ContainerEnvironment = withInjectables<Dependencies, ContainerEnvironmentProps>(NonInjectedContainerEnvironment, {
getProps: (di, props) => ({
...props,
configMapStore: di.inject(configMapStoreInjectable),
secretStore: di.inject(secretStoreInjectable),
}),
});

View File

@ -122,7 +122,7 @@ class DefaultedAnimate extends React.Component<AnimateProps & Dependencies & typ
}
}
export const NonInjectedAnimate = (props: AnimateProps & Dependencies) => <DefaultedAnimate {...props} />;
const NonInjectedAnimate = (props: AnimateProps & Dependencies) => <DefaultedAnimate {...props} />;
export const Animate = withInjectables<Dependencies, AnimateProps>(
NonInjectedAnimate,

View File

@ -22,7 +22,7 @@ interface Dependencies {
entityRegistry: CatalogEntityRegistry;
}
export const NonInjectedSidebar = observer(({
const NonInjectedSidebar = observer(({
sidebarItems,
entityRegistry,
}: Dependencies) => (

View File

@ -21,18 +21,15 @@ export enum NotificationStatus {
export interface CreateNotificationOptions {
id?: NotificationId;
timeout?: number;
onClose?(): void;
}
export interface Notification {
id?: NotificationId;
message: NotificationMessage;
status?: NotificationStatus;
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
onClose?(): void; // additional logic on when the notification times out or is closed by the "x"
}
export interface Notification extends CreateNotificationOptions {
message: NotificationMessage;
status: NotificationStatus;
}
export class NotificationsStore {
public notifications = observable.array<SetRequired<Notification, "id">>([], { deep: false });

View File

@ -13,13 +13,12 @@ const showErrorNotificationInjectable = getInjectable({
instantiate: (di): ShowNotification => {
const notificationsStore = di.inject(notificationsStoreInjectable);
return (message, customOpts = {}) =>
notificationsStore.add({
status: NotificationStatus.ERROR,
timeout: 5000,
message,
...customOpts,
});
return (message, customOpts = {}) => notificationsStore.add({
status: NotificationStatus.ERROR,
timeout: 5000,
message,
...customOpts,
});
},
});

View File

@ -22,7 +22,7 @@ interface Dependencies {
watchHistoryState: () => () => void;
}
export const NonInjectedClusterFrame = observer(({
const NonInjectedClusterFrame = observer(({
namespaceStore,
subscribeStores,
childComponents,