Split root and cluster frames in smaller pieces (#5737)

* Make root frame child components comply with open closed principle and include it in the behavioural unit tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make cluster frame child components comply with open closed principle and include it in behavioural unit tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplication

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make a component more determistic in unit tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Mock uninterested, non-deterministic third party library in unit tests

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Optimize registration of injectables in unit tests to make tests faster

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove import time side-effect causing memory leak

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne SavolainenandMikko Aspiala authored and GitHub committed 2022-07-04 09:38:29 -07:00
1 parent 1a9fa699c9
commit 14d5a1c3cc
66 files changed
+48472 -35711

No files matched your search

@@ -2,6 +2,9 @@
exports[`<ClusterFrame /> given cluster with list nodes and namespaces permissions given no matching component given current url is starting url renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
<div
class="mainLayout"
style="--sidebar-width: 200px;"
@@ -467,14 +470,14 @@ exports[`<ClusterFrame /> given cluster with list nodes and namespaces permissio
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`<ClusterFrame /> given cluster with list nodes and namespaces permissions renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
<div
class="mainLayout"
style="--sidebar-width: 200px;"
@@ -947,14 +950,14 @@ exports[`<ClusterFrame /> given cluster with list nodes and namespaces permissio
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
exports[`<ClusterFrame /> given cluster without list nodes, but with namespaces permissions renders 1`] = `
<div>
<div
class="Notifications flex column align-flex-end"
/>
<div
class="mainLayout"
style="--sidebar-width: 200px;"
@@ -1483,8 +1486,5 @@ exports[`<ClusterFrame /> given cluster without list nodes, but with namespaces
</div>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
`;
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
export interface ClusterFrameChildComponent {
id: string;
Component: React.ElementType;
shouldRender: IComputedValue<boolean>;
}
export const clusterFrameChildComponentInjectionToken = getInjectionToken<ClusterFrameChildComponent>({
id: "cluster-frame-child-component",
});
@@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { getInjectable } from "@ogre-tools/injectable";
import { clusterFrameChildComponentInjectionToken } from "./cluster-frame-child-component-injection-token";
import { MainLayout } from "../../components/layout/main-layout";
import { Sidebar } from "../../components/layout/sidebar";
import { Dock } from "../../components/dock";
import styles from "./cluster-frame.module.css";
import { computed } from "mobx";
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
import { Redirect } from "react-router";
import startUrlInjectable from "./start-url.injectable";
import currentPathInjectable from "../../routes/current-path.injectable";
import { observer } from "mobx-react";
const clusterFrameLayoutChildComponentInjectable = getInjectable({
id: "cluster-frame-layout-child-component",
instantiate: (di) => {
const currentRouteComponent = di.inject(currentRouteComponentInjectable);
const startUrl = di.inject(startUrlInjectable);
const currentPath = di.inject(currentPathInjectable);
return {
id: "cluster-frame-layout",
shouldRender: computed(() => true),
Component: observer(() => {
const Component = currentRouteComponent.get();
const starting = startUrl.get();
const current = currentPath.get();
return (
<MainLayout sidebar={<Sidebar />} footer={<Dock />}>
{Component ? (
<Component />
) : // NOTE: this check is to prevent an infinite loop
starting !== current ? (
<Redirect to={startUrl.get()} />
) : (
<div className={styles.centering}>
<div className="error">
An error has occured. No route can be found matching the
current route, which is also the starting route.
</div>
</div>
)}
</MainLayout>
);
}),
};
},
injectionToken: clusterFrameChildComponentInjectionToken,
});
export default clusterFrameLayoutChildComponentInjectable;
@@ -2,52 +2,30 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./cluster-frame.module.css";
import React, { useEffect } from "react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import { Redirect } from "react-router";
import { ConfirmDialog } from "../../components/confirm-dialog";
import { DeploymentScaleDialog } from "../../components/+workloads-deployments/scale/dialog";
import { CronJobTriggerDialog } from "../../components/+workloads-cronjobs/cronjob-trigger-dialog";
import { StatefulSetScaleDialog } from "../../components/+workloads-statefulsets/scale/dialog";
import { ReplicaSetScaleDialog } from "../../components/+workloads-replicasets/scale-dialog/dialog";
import { CommandContainer } from "../../components/command-palette/command-container";
import { Observer, observer } from "mobx-react";
import { ErrorBoundary } from "../../components/error-boundary";
import { MainLayout } from "../../components/layout/main-layout";
import { Notifications } from "../../components/notifications";
import { KubeObjectDetails } from "../../components/kube-object-details";
import { KubeConfigDialog } from "../../components/kubeconfig-dialog";
import { Sidebar } from "../../components/layout/sidebar";
import { Dock } from "../../components/dock";
import { PortForwardDialog } from "../../port-forward";
import { DeleteClusterDialog } from "../../components/delete-cluster-dialog";
import type { NamespaceStore } from "../../components/+namespaces/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../../components/+namespaces/store.injectable";
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import { disposer } from "../../utils";
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
import startUrlInjectable from "./start-url.injectable";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import currentPathInjectable from "../../routes/current-path.injectable";
import type { ClusterFrameChildComponent } from "./cluster-frame-child-component-injection-token";
import { clusterFrameChildComponentInjectionToken } from "./cluster-frame-child-component-injection-token";
import watchHistoryStateInjectable from "../../remote-helpers/watch-history-state.injectable";
interface Dependencies {
namespaceStore: NamespaceStore;
currentRouteComponent: IComputedValue<React.ElementType<{}> | undefined>;
startUrl: IComputedValue<string>;
subscribeStores: SubscribeStores;
currentPath: IComputedValue<string>;
childComponents: ClusterFrameChildComponent[];
watchHistoryState: () => () => void;
}
export const NonInjectedClusterFrame = observer(({
namespaceStore,
currentRouteComponent,
startUrl,
subscribeStores,
currentPath,
childComponents,
watchHistoryState,
}: Dependencies) => {
useEffect(() => disposer(
@@ -57,43 +35,14 @@ export const NonInjectedClusterFrame = observer(({
watchHistoryState(),
), []);
const Component = currentRouteComponent.get();
const starting = startUrl.get();
const current = currentPath.get();
return (
<ErrorBoundary>
<MainLayout
sidebar={<Sidebar />}
footer={<Dock />}
>
{
Component
? <Component />
// NOTE: this check is to prevent an infinite loop
: starting !== current
? <Redirect to={startUrl.get()} />
: (
<div className={styles.centering}>
<div className="error">
An error has occured. No route can be found matching the current route, which is also the starting route.
</div>
</div>
)
}
</MainLayout>
<Notifications />
<ConfirmDialog />
<KubeObjectDetails />
<KubeConfigDialog />
<DeploymentScaleDialog />
<StatefulSetScaleDialog />
<ReplicaSetScaleDialog />
<CronJobTriggerDialog />
<PortForwardDialog />
<DeleteClusterDialog />
<CommandContainer />
{childComponents
.map((child) => (
<Observer key={child.id}>
{() => (child.shouldRender.get() ? <child.Component /> : null) }
</Observer>
))}
</ErrorBoundary>
);
});
@@ -102,9 +51,7 @@ export const ClusterFrame = withInjectables<Dependencies>(NonInjectedClusterFram
getProps: di => ({
namespaceStore: di.inject(namespaceStoreInjectable),
subscribeStores: di.inject(subscribeStoresInjectable),
startUrl: di.inject(startUrlInjectable),
currentRouteComponent: di.inject(currentRouteComponentInjectable),
currentPath: di.inject(currentPathInjectable),
childComponents: di.injectMany(clusterFrameChildComponentInjectionToken),
watchHistoryState: di.inject(watchHistoryStateInjectable),
}),
});
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
export interface RootFrameChildComponent {
id: string;
Component: React.ElementType;
shouldRender: IComputedValue<boolean>;
}
export const rootFrameChildComponentInjectionToken = getInjectionToken<RootFrameChildComponent>({
id: "root-frame-child-component",
});
+11 -17
View File
@@ -3,25 +3,18 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { injectSystemCAs } from "../../../common/system-ca";
import React from "react";
import { observer } from "mobx-react";
import { ClusterManager } from "../../components/cluster-manager";
import { ErrorBoundary } from "../../components/error-boundary";
import { Notifications } from "../../components/notifications";
import { ConfirmDialog } from "../../components/confirm-dialog";
import { CommandContainer } from "../../components/command-palette/command-container";
import { Observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import broadcastThatRootFrameIsRenderedInjectable from "./broadcast-that-root-frame-is-rendered.injectable";
// Todo: remove import-time side-effect.
injectSystemCAs();
import type { RootFrameChildComponent } from "./root-frame-child-component-injection-token";
import { rootFrameChildComponentInjectionToken } from "./root-frame-child-component-injection-token";
interface Dependencies {
broadcastThatRootFrameIsRendered: () => void;
childComponents: RootFrameChildComponent[];
}
@observer
class NonInjectedRootFrame extends React.Component<Dependencies> {
static displayName = "RootFrame";
@@ -32,12 +25,12 @@ class NonInjectedRootFrame extends React.Component<Dependencies> {
render() {
return (
<>
<ErrorBoundary>
<ClusterManager />
</ErrorBoundary>
<Notifications />
<ConfirmDialog />
<CommandContainer />
{this.props.childComponents
.map((child) => (
<Observer key={child.id}>
{() => (child.shouldRender.get() ? <child.Component /> : null) }
</Observer>
))}
</>
);
}
@@ -49,6 +42,7 @@ export const RootFrame = withInjectables<Dependencies>(
{
getProps: (di, props) => ({
broadcastThatRootFrameIsRendered: di.inject(broadcastThatRootFrameIsRenderedInjectable),
childComponents: di.injectMany(rootFrameChildComponentInjectionToken),
...props,
}),
},
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { injectSystemCAs } from "../../../common/system-ca";
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
const setupSystemCaInjectable = getInjectable({
id: "setup-system-ca",
instantiate: () => ({
run: async () => {
await injectSystemCAs();
},
}),
causesSideEffects: true,
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupSystemCaInjectable;