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

Merge branch 'master' into feature/add-native-theme-option

This commit is contained in:
Alex Andreev 2022-03-06 12:53:16 +03:00
commit 468dd86bb9
14 changed files with 251 additions and 76 deletions

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.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
];
this.events.on("remove", (removedExtension: LensRendererExtension) => {

View File

@ -20,6 +20,7 @@ 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 { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
export class LensRendererExtension extends LensExtension {
@ -32,7 +33,7 @@ export class LensRendererExtension extends LensExtension {
statusBarItems: StatusBarRegistration[] = [];
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
kubeWorkloadsOverviewItems: WorkloadsOverviewDetailRegistration[] = [];
commands: CommandRegistration[] = [];
welcomeMenus: WelcomeMenuRegistration[] = [];
welcomeBanners: WelcomeBannerRegistration[] = [];

View File

@ -10,5 +10,4 @@ export * from "./page-menu-registry";
export * from "./kube-object-detail-registry";
export * from "./entity-setting-registry";
export * from "./catalog-entity-detail-registry";
export * from "./workloads-overview-detail-registry";
export * from "./protocol-handler";

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { orderBy } from "lodash";
import type React from "react";
import { BaseRegistry } from "./base-registry";
export interface WorkloadsOverviewDetailComponents {
Details: React.ComponentType<{}>;
}
export interface WorkloadsOverviewDetailRegistration {
components: WorkloadsOverviewDetailComponents;
priority?: number;
}
type RegisteredWorkloadsOverviewDetail = Required<WorkloadsOverviewDetailRegistration>;
export class WorkloadsOverviewDetailRegistry extends BaseRegistry<WorkloadsOverviewDetailRegistration, RegisteredWorkloadsOverviewDetail> {
getItems() {
return orderBy(super.getItems(), "priority", "desc");
}
protected getRegisteredItem(item: WorkloadsOverviewDetailRegistration): RegisteredWorkloadsOverviewDetail {
const { priority = 50, ...rest } = item;
return { priority, ...rest };
}
}

View File

@ -76,9 +76,6 @@ export async function bootstrap(di: DiContainer) {
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
initializers.initKubeObjectDetailRegistry();
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
initializers.initWorkloadsOverviewDetailRegistry();
logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`);
initializers.initCatalogEntityDetailRegistry();

View File

@ -0,0 +1,70 @@
/**
* 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";
import { OverviewStatuses } from "./overview-statuses";
import { WorkloadEvents } from "../../initializers/workload-events";
import { orderBy } from "lodash/fp";
import type { WorkloadsOverviewDetailRegistration } from "./workloads-overview-detail-registration";
const detailComponentsInjectable = getInjectable({
id: "workload-detail-components",
instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable);
return computed(() => {
const extensionRegistrations = extensions
.get()
.flatMap((extension) => extension.kubeWorkloadsOverviewItems);
const allRegistrations = [
...coreRegistrations,
...extensionRegistrations,
];
return getRegistrationsInPriorityOrder(allRegistrations).map(
(item) => item.components.Details,
);
});
},
lifecycle: lifecycleEnum.singleton,
});
const coreRegistrations = [
{
components: {
Details: OverviewStatuses,
},
},
{
priority: 5,
components: {
Details: WorkloadEvents,
},
},
];
const toRegistrationWithDefaultPriority = ({
priority = 50,
...rest
}: WorkloadsOverviewDetailRegistration) => ({
priority,
...rest,
});
const getRegistrationsInPriorityOrder = (
allRegistrations: WorkloadsOverviewDetailRegistration[],
) =>
orderBy(
"priority",
"desc",
allRegistrations.map(toRegistrationWithDefaultPriority),
);
export default detailComponentsInjectable;

View File

@ -16,9 +16,8 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
import { makeObservable, observable, reaction } from "mobx";
import { IComputedValue, makeObservable, observable, reaction } from "mobx";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip";
@ -30,11 +29,13 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type { Disposer } from "../../../common/utils";
import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable";
import type { KubeWatchSubscribeStoreOptions } from "../../kube-watch-api/kube-watch-api";
import detailComponentsInjectable from "./detail-components.injectable";
export interface WorkloadsOverviewProps extends RouteComponentProps<WorkloadsOverviewRouteParams> {
}
interface Dependencies {
detailComponents: IComputedValue<React.ComponentType<{}>[]>;
clusterFrameContext: ClusterFrameContext;
subscribeStores: (stores: KubeObjectStore<KubeObject>[], options: KubeWatchSubscribeStoreOptions) => Disposer;
}
@ -91,13 +92,6 @@ class NonInjectedWorkloadsOverview extends React.Component<WorkloadsOverviewProp
}
render() {
const items = WorkloadsOverviewDetailRegistry
.getInstance()
.getItems()
.map(({ components: { Details }}, index) => (
<Details key={`workload-overview-${index}`}/>
));
return (
<div className="WorkloadsOverview flex column gaps">
<div className="header flex gaps align-center">
@ -105,7 +99,10 @@ class NonInjectedWorkloadsOverview extends React.Component<WorkloadsOverviewProp
{this.renderLoadErrors()}
<NamespaceSelectFilter />
</div>
{items}
{this.props.detailComponents.get().map((Details, index) => (
<Details key={`workload-overview-${index}`} />
))}
</div>
);
}
@ -116,6 +113,7 @@ export const WorkloadsOverview = withInjectables<Dependencies, WorkloadsOverview
{
getProps: (di, props) => ({
detailComponents: di.inject(detailComponentsInjectable),
clusterFrameContext: di.inject(clusterFrameContextInjectable),
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
...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.
*/
interface WorkloadsOverviewDetailComponents {
Details: React.ComponentType<{}>;
}
export interface WorkloadsOverviewDetailRegistration {
components: WorkloadsOverviewDetailComponents;
priority?: number;
}

View File

@ -39,7 +39,7 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
model.reloadLogs();
return model.stopLoadingLogs;
}, []);
}, [tab.id]);
useEffect(() => subscribeStores([
podsStore,
], {

View File

@ -0,0 +1,157 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { Select } from "./select";
import { UserStore } from "../../../common/user-store";
import { ThemeStore } from "../../theme.store";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable";
import { DiRender, renderFor } from "../test-utils/renderFor";
import mockFs from "mock-fs";
import directoryForUserDataInjectable
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import { computed } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
describe("<Select />", () => {
let di: DiContainer;
let render: DiRender;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
mockFs();
await di.runSetups();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
UserStore.createInstance();
ThemeStore.createInstance();
});
afterEach(() => {
ThemeStore.resetInstance();
UserStore.resetInstance();
mockFs.restore();
});
it("should render the select", async () => {
const options = [
{
label: "Option one label",
value: "optionOneValue",
},
{
label: "Option two label",
value: "optionTwoValue",
},
];
const onChange = jest.fn();
const { container } = render(<Select onChange={onChange} options={options} />);
expect(container).toBeInstanceOf(HTMLElement);
});
it("should show selected option", async () => {
const options = [
{
label: "Option one label",
value: "optionOneValue",
},
{
label: "Option two label",
value: "optionTwoValue",
},
];
const onChange = jest.fn();
const { container } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer.textContent).toBe(options[0].label);
});
it("should reflect to change value", async () => {
const options = [
{
label: "Option one label",
value: "optionOneValue",
},
{
label: "Option two label",
value: "optionTwoValue",
},
];
const onChange = jest.fn();
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer.textContent).toBe(options[0].label);
rerender(<Select value={options[1].value} onChange={onChange} options={options} />);
expect(container.querySelector(".Select__single-value").textContent).toBe(options[1].label);
});
it("should unselect value if null is passed as a value", async () => {
const options = [
{
label: "Option one label",
value: "optionOneValue",
},
{
label: "Option two label",
value: "optionTwoValue",
},
];
const onChange = jest.fn();
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer.textContent).toBe(options[0].label);
rerender(<Select value={null} onChange={onChange} options={options} />);
expect(container.querySelector(".Select__single-value")).not.toBeInTheDocument();
});
it("should unselect value if undefined is passed as a value", async () => {
const options = [
{
label: "Option one label",
value: "optionOneValue",
},
{
label: "Option two label",
value: "optionTwoValue",
},
];
const onChange = jest.fn();
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer.textContent).toBe(options[0].label);
rerender(<Select value={undefined} onChange={onChange} options={options} />);
expect(container.querySelector(".Select__single-value")).not.toBeInTheDocument();
});
});

View File

@ -80,7 +80,7 @@ export class Select extends React.Component<SelectProps> {
});
}
return this.options.find(opt => opt === value || opt.value === value);
return this.options.find(opt => opt === value || opt.value === value) || null;
}
@computed get options(): SelectOption[] {

View File

@ -9,5 +9,4 @@ export * from "./entity-settings-registry";
export * from "./ipc";
export * from "./kube-object-detail-registry";
export * from "./registries";
export * from "./workloads-overview-detail-registry";
export * from "./catalog-category-registry";

View File

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

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { WorkloadsOverviewDetailRegistry } from "../../extensions/registries";
import { OverviewStatuses } from "../components/+workloads-overview/overview-statuses";
import { WorkloadEvents } from "./workload-events";
export function initWorkloadsOverviewDetailRegistry() {
WorkloadsOverviewDetailRegistry.getInstance()
.add([
{
components: {
Details: OverviewStatuses,
},
},
{
priority: 5,
components: {
Details: WorkloadEvents,
},
},
]);
}