mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Technical requirements for behavioural unit tests (#5084)
* Implement a lot of technical requirements for behavioural unit tests Note: the crux of this was to make routing env-agnostic, and not based on URLs as magic strings, but instead something type-enforced. Note: extension-based routes comply to same exact interface by "late-registering" their routes when installed. Routes are just injectables. Note: another chunk of global shared state is no more. Note: a lot of explicit side effects have been cornered to injectables. Note: a lot of stuff has become reactive as part if this. Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make a directory commonly available Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Require id for <Select /> to prevent non-deterministic renders This was caused by global state in a 3rd party lib: "react-select". Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Specify id for all <Select /> to satisfy previous commit Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent explicit side effect in component by using existing dependency instead Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract instantiation of "conf" as injectables for causing side effects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce a legacy-helper to make gradual refactoring of inheritors of Singleton easier Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make legacy unit tests for hotbar green and more simple by using the new legacy helper Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Temporarily kludge all unit tests green with a disclaimer about allowing side-effects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove kludge in previous commit by explicitly permitting specific side effects where old unit tests require it Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent old unit test with side effects from accessing file system Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate to actual typing for di.permitSideEffects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent unit tests from failing because of non-standard method of HTML-element not present in js-dom Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt integration tests to recent changes Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix code style Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix artifact from bad rebase Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add a deprecation from a review comment Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove change that is not required Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant comment Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix bad merge Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Improve variable name Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Tweak logger interface to be more descriptive Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make injecting legacy singleton always provide new instance Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove conditional typing when not needed Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Improve naming of variable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unnecessary code style changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove flag for causing side effects from too broad scope Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Override side-effects in unit test using injectable instead of monkey patching Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Flag some side-effects and add general overrides Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix unit tests in CI by removing explicit side-effect Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove explicit side-effect from getting default shell Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for getting absolute paths Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using abstraction for getting absolute path to control explicit side effect Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for joining paths Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using abstraction for joining paths to control explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix fake implementation for join paths Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix test after removing explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove explicit side effects from kubeconfig-syncs Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix arguments after removing explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make registrators not async for not being needed anymore Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make generalCatalogEntities non-observable, as there is no requirement Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove redundant code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify logic for registering general catalog entity sources Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Replace function for getting application menu items with reactive solution Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typo in interface name Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove global shared state usages of hot bar store Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant enum Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
ab2cd27a27
commit
a277cfcf02
583 files changed
+13073
-6556
No files matched your search
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
|
||||
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
|
||||
import { CustomResourceDefinitions } from "./crd-list";
|
||||
|
||||
const crdListRouteComponentInjectable = getInjectable({
|
||||
id: "crd-list-route-component",
|
||||
|
||||
instantiate: (di) => ({
|
||||
route: di.inject(crdListRouteInjectable),
|
||||
Component: CustomResourceDefinitions,
|
||||
}),
|
||||
|
||||
injectionToken: routeSpecificComponentInjectionToken,
|
||||
});
|
||||
|
||||
export default crdListRouteComponentInjectable;
|
||||
@@ -16,6 +16,7 @@ import { Select, SelectOption } from "../select";
|
||||
import { createPageParam } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
import { TabLayout } from "../layout/tab-layout-2";
|
||||
|
||||
export const crdGroupsUrlParam = createPageParam<string[]>({
|
||||
name: "groups",
|
||||
@@ -64,81 +65,84 @@ export class CustomResourceDefinitions extends React.Component {
|
||||
const { items, selectedGroups } = this;
|
||||
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="crd"
|
||||
className="CrdList"
|
||||
store={crdStore}
|
||||
// Don't subscribe the `crdStore` because <Sidebar> already has and is always mounted
|
||||
subscribeStores={false}
|
||||
items={items}
|
||||
sortingCallbacks={{
|
||||
[columnId.kind]: crd => crd.getResourceKind(),
|
||||
[columnId.group]: crd => crd.getGroup(),
|
||||
[columnId.version]: crd => crd.getVersion(),
|
||||
[columnId.scope]: crd => crd.getScope(),
|
||||
[columnId.age]: crd => -crd.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
crd => crd.getResourceKind(),
|
||||
crd => crd.getGroup(),
|
||||
crd => crd.getVersion(),
|
||||
crd => crd.getScope(),
|
||||
crd => -crd.getCreationTimestamp(),
|
||||
]}
|
||||
renderHeaderTitle="Custom Resources"
|
||||
customizeHeader={({ filters, ...headerPlaceholders }) => {
|
||||
let placeholder = <>All groups</>;
|
||||
<TabLayout>
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="crd"
|
||||
className="CrdList"
|
||||
store={crdStore}
|
||||
// Don't subscribe the `crdStore` because <Sidebar> already has and is always mounted
|
||||
subscribeStores={false}
|
||||
items={items}
|
||||
sortingCallbacks={{
|
||||
[columnId.kind]: crd => crd.getResourceKind(),
|
||||
[columnId.group]: crd => crd.getGroup(),
|
||||
[columnId.version]: crd => crd.getVersion(),
|
||||
[columnId.scope]: crd => crd.getScope(),
|
||||
[columnId.age]: crd => -crd.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
crd => crd.getResourceKind(),
|
||||
crd => crd.getGroup(),
|
||||
crd => crd.getVersion(),
|
||||
crd => crd.getScope(),
|
||||
crd => -crd.getCreationTimestamp(),
|
||||
]}
|
||||
renderHeaderTitle="Custom Resources"
|
||||
customizeHeader={({ filters, ...headerPlaceholders }) => {
|
||||
let placeholder = <>All groups</>;
|
||||
|
||||
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
|
||||
if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}</>;
|
||||
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
|
||||
if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}</>;
|
||||
|
||||
return {
|
||||
// todo: move to global filters
|
||||
filters: (
|
||||
<>
|
||||
{filters}
|
||||
<Select
|
||||
className="group-select"
|
||||
placeholder={placeholder}
|
||||
options={Object.keys(crdStore.groups)}
|
||||
onChange={({ value: group }: SelectOption) => this.toggleSelection(group)}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
formatOptionLabel={({ value: group }: SelectOption) => {
|
||||
const isSelected = selectedGroups.includes(group);
|
||||
return {
|
||||
// todo: move to global filters
|
||||
filters: (
|
||||
<>
|
||||
{filters}
|
||||
<Select
|
||||
id="crd-input"
|
||||
className="group-select"
|
||||
placeholder={placeholder}
|
||||
options={Object.keys(crdStore.groups)}
|
||||
onChange={({ value: group }: SelectOption) => this.toggleSelection(group)}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
formatOptionLabel={({ value: group }: SelectOption) => {
|
||||
const isSelected = selectedGroups.includes(group);
|
||||
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
<Icon small material="folder"/>
|
||||
<span>{group}</span>
|
||||
{isSelected && <Icon small material="check" className="box right"/>}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
...headerPlaceholders,
|
||||
};
|
||||
}}
|
||||
renderTableHeader={[
|
||||
{ title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind },
|
||||
{ title: "Group", className: "group", sortBy: columnId.group, id: columnId.group },
|
||||
{ title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
|
||||
{ title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={crd => [
|
||||
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
|
||||
{crd.getResourceKind()}
|
||||
</Link>,
|
||||
crd.getGroup(),
|
||||
crd.getVersion(),
|
||||
crd.getScope(),
|
||||
<KubeObjectAge key="age" object={crd} />,
|
||||
]}
|
||||
/>
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
<Icon small material="folder"/>
|
||||
<span>{group}</span>
|
||||
{isSelected && <Icon small material="check" className="box right"/>}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
...headerPlaceholders,
|
||||
};
|
||||
}}
|
||||
renderTableHeader={[
|
||||
{ title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind },
|
||||
{ title: "Group", className: "group", sortBy: columnId.group, id: columnId.group },
|
||||
{ title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
|
||||
{ title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={crd => [
|
||||
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
|
||||
{crd.getResourceKind()}
|
||||
</Link>,
|
||||
crd.getGroup(),
|
||||
crd.getVersion(),
|
||||
crd.getScope(),
|
||||
<KubeObjectAge key="age" object={crd} />,
|
||||
]}
|
||||
/>
|
||||
</TabLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,35 +8,36 @@ import "./crd-resources.scss";
|
||||
import React from "react";
|
||||
import { value } from "jsonpath";
|
||||
import { observer } from "mobx-react";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { computed, IComputedValue, makeObservable } from "mobx";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { parseJsonPath } from "../../utils/jsonPath";
|
||||
import type { CRDRouteParams } from "../../../common/routes";
|
||||
import { TabLayout } from "../layout/tab-layout-2";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import customResourcesRouteParametersInjectable from "./custom-resources-route-parameters.injectable";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export interface CustomResourceDefinitionResourcesProps extends RouteComponentProps<CRDRouteParams> {
|
||||
}
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
group: IComputedValue<string>;
|
||||
name: IComputedValue<string>;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CustomResourceDefinitionResources extends React.Component<CustomResourceDefinitionResourcesProps> {
|
||||
constructor(props: CustomResourceDefinitionResourcesProps) {
|
||||
class NonInjectedCrdResources extends React.Component<Dependencies> {
|
||||
constructor(props: Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get crd() {
|
||||
const { group, name } = this.props.match.params;
|
||||
|
||||
return crdStore.getByGroup(group, name);
|
||||
return crdStore.getByGroup(this.props.group.get(), this.props.name.get());
|
||||
}
|
||||
|
||||
@computed get store() {
|
||||
@@ -55,69 +56,87 @@ export class CustomResourceDefinitionResources extends React.Component<CustomRes
|
||||
const version = crd.getPreferedVersion();
|
||||
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
key={`crd_resources_${crd.getResourceApiBase()}`}
|
||||
tableId="crd_resources"
|
||||
className="CrdResources"
|
||||
store={store}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: customResource => customResource.getName(),
|
||||
[columnId.namespace]: customResource => customResource.getNs(),
|
||||
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
|
||||
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
|
||||
name,
|
||||
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
|
||||
])),
|
||||
}}
|
||||
searchFilters={[
|
||||
customResource => customResource.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle={crd.getResourceKind()}
|
||||
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
|
||||
searchProps: {
|
||||
...searchProps,
|
||||
placeholder: `${crd.getResourceKind()} search ...`,
|
||||
},
|
||||
...headerPlaceholders,
|
||||
})}
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
||||
...extraColumns.map(({ name }) => ({
|
||||
title: name,
|
||||
className: name.toLowerCase(),
|
||||
sortBy: name,
|
||||
id: name,
|
||||
})),
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={crdInstance => [
|
||||
crdInstance.getName(),
|
||||
isNamespaced && crdInstance.getNs(),
|
||||
...extraColumns.map((column) => {
|
||||
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
|
||||
<TabLayout>
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
key={`crd_resources_${crd.getResourceApiBase()}`}
|
||||
tableId="crd_resources"
|
||||
className="CrdResources"
|
||||
store={store}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: customResource => customResource.getName(),
|
||||
[columnId.namespace]: customResource => customResource.getNs(),
|
||||
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
|
||||
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
|
||||
name,
|
||||
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
|
||||
])),
|
||||
}}
|
||||
searchFilters={[
|
||||
customResource => customResource.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle={crd.getResourceKind()}
|
||||
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
|
||||
searchProps: {
|
||||
...searchProps,
|
||||
placeholder: `${crd.getResourceKind()} search ...`,
|
||||
},
|
||||
...headerPlaceholders,
|
||||
})}
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
||||
...extraColumns.map(({ name }) => ({
|
||||
title: name,
|
||||
className: name.toLowerCase(),
|
||||
sortBy: name,
|
||||
id: name,
|
||||
})),
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={crdInstance => [
|
||||
crdInstance.getName(),
|
||||
isNamespaced && crdInstance.getNs(),
|
||||
...extraColumns.map((column) => {
|
||||
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
|
||||
|
||||
if (Array.isArray(rawValue) || typeof rawValue === "object") {
|
||||
rawValue = JSON.stringify(rawValue);
|
||||
}
|
||||
if (Array.isArray(rawValue) || typeof rawValue === "object") {
|
||||
rawValue = JSON.stringify(rawValue);
|
||||
}
|
||||
|
||||
return {
|
||||
renderBoolean: true,
|
||||
children: rawValue,
|
||||
};
|
||||
}),
|
||||
<KubeObjectAge key="age" object={crdInstance} />,
|
||||
]}
|
||||
failedToLoadMessage={(
|
||||
<>
|
||||
<p>Failed to load {crd.getPluralName()}</p>
|
||||
{!version.served && (
|
||||
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
return {
|
||||
renderBoolean: true,
|
||||
children: rawValue,
|
||||
};
|
||||
}),
|
||||
<KubeObjectAge key="age" object={crdInstance} />,
|
||||
]}
|
||||
failedToLoadMessage={(
|
||||
<>
|
||||
<p>Failed to load {crd.getPluralName()}</p>
|
||||
{!version.served && (
|
||||
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</TabLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CrdResources = withInjectables<Dependencies>(
|
||||
NonInjectedCrdResources,
|
||||
|
||||
{
|
||||
getProps: (di) => {
|
||||
const routeParameters = di.inject(customResourcesRouteParametersInjectable);
|
||||
|
||||
return {
|
||||
group: routeParameters.group,
|
||||
name: routeParameters.name,
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 { noop, some } from "lodash/fp";
|
||||
import { computed } from "mobx";
|
||||
|
||||
import {
|
||||
SidebarItemRegistration,
|
||||
sidebarItemsInjectionToken,
|
||||
} from "../layout/sidebar-items.injectable";
|
||||
import { Icon } from "../icon";
|
||||
import React from "react";
|
||||
|
||||
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
|
||||
import sidebarItemsForDefinitionGroupsInjectable from "./sidebar-items-for-definition-groups.injectable";
|
||||
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
|
||||
import navigateToCrdListInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable";
|
||||
|
||||
const customResourceSidebarItemsInjectable = getInjectable({
|
||||
id: "custom-resource-sidebar-items",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToCrdList = di.inject(navigateToCrdListInjectable);
|
||||
const crdListRoute = di.inject(crdListRouteInjectable);
|
||||
const crdListRouteIsActive = di.inject(routeIsActiveInjectable, crdListRoute);
|
||||
|
||||
const definitionGroupSidebarItems = di.inject(
|
||||
sidebarItemsForDefinitionGroupsInjectable,
|
||||
);
|
||||
|
||||
return computed((): SidebarItemRegistration[] => {
|
||||
const definitionsItem = {
|
||||
id: "definitions",
|
||||
parentId: "custom-resources",
|
||||
title: "Definitions",
|
||||
onClick: navigateToCrdList,
|
||||
isActive: crdListRouteIsActive,
|
||||
isVisible: crdListRoute.isEnabled,
|
||||
orderNumber: 10,
|
||||
};
|
||||
|
||||
const definitionGroupItems = definitionGroupSidebarItems.get();
|
||||
|
||||
const childrenAndGrandChildren = [
|
||||
definitionsItem,
|
||||
...definitionGroupItems,
|
||||
];
|
||||
|
||||
const parentItem: SidebarItemRegistration = {
|
||||
id: "custom-resources",
|
||||
parentId: null,
|
||||
title: "Custom Resources",
|
||||
getIcon: () => <Icon material="extension" />,
|
||||
onClick: noop,
|
||||
isVisible: computed(() => some(item => item.isVisible.get(), childrenAndGrandChildren)),
|
||||
orderNumber: 110,
|
||||
};
|
||||
|
||||
return [parentItem, definitionsItem, ...definitionGroupItems];
|
||||
});
|
||||
},
|
||||
|
||||
injectionToken: sidebarItemsInjectionToken,
|
||||
});
|
||||
|
||||
export default customResourceSidebarItemsInjectable;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { CrdResources } from "./crd-resources";
|
||||
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
|
||||
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
|
||||
|
||||
const customResourcesRouteComponentInjectable = getInjectable({
|
||||
id: "custom-resources-route-component",
|
||||
|
||||
instantiate: (di) => ({
|
||||
route: di.inject(customResourcesRouteInjectable),
|
||||
Component: CrdResources,
|
||||
}),
|
||||
|
||||
injectionToken: routeSpecificComponentInjectionToken,
|
||||
});
|
||||
|
||||
export default customResourcesRouteComponentInjectable;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
|
||||
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
|
||||
|
||||
const customResourcesRouteParametersInjectable = getInjectable({
|
||||
id: "custom-resources-route-parameters",
|
||||
|
||||
instantiate: (di) => {
|
||||
const route = di.inject(customResourcesRouteInjectable);
|
||||
const pathParameters = di.inject(routePathParametersInjectable, route);
|
||||
|
||||
return {
|
||||
group: computed(() => pathParameters.get().group),
|
||||
name: computed(() => pathParameters.get().name),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default customResourcesRouteParametersInjectable;
|
||||
@@ -6,11 +6,23 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { crdStore } from "./crd.store";
|
||||
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
|
||||
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
|
||||
|
||||
const customResourceDefinitionsInjectable = getInjectable({
|
||||
id: "custom-resource-definitions",
|
||||
|
||||
instantiate: () => computed(() => [...crdStore.items]),
|
||||
instantiate: (di) => {
|
||||
const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable);
|
||||
|
||||
if (currentlyInClusterFrame) {
|
||||
const subscribeStores = di.inject(subscribeStoresInjectable);
|
||||
|
||||
subscribeStores([crdStore]);
|
||||
}
|
||||
|
||||
return computed(() => [...crdStore.items]);
|
||||
},
|
||||
});
|
||||
|
||||
export default customResourceDefinitionsInjectable;
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 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 { computed, IComputedValue } from "mobx";
|
||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
||||
import { getOrInsert } from "../../utils";
|
||||
import customResourceDefinitionsInjectable from "./custom-resources.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
definitions: IComputedValue<CustomResourceDefinition[]>;
|
||||
}
|
||||
|
||||
function getGroupedCustomResourceDefinitions({ definitions }: Dependencies) {
|
||||
return computed(() => {
|
||||
const groups = new Map<string, CustomResourceDefinition[]>();
|
||||
|
||||
for (const crd of definitions.get()) {
|
||||
getOrInsert(groups, crd.getGroup(), []).push(crd);
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
const groupedCustomResourceDefinitionsInjectable = getInjectable({
|
||||
id: "grouped-custom-resource-definitions",
|
||||
|
||||
instantiate: (di) => getGroupedCustomResourceDefinitions({
|
||||
definitions: di.inject(customResourceDefinitionsInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default groupedCustomResourceDefinitionsInjectable;
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* 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 { computed, IComputedValue } from "mobx";
|
||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
||||
import { crdURL, crdDefinitionsRoute } from "../../../common/routes";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable";
|
||||
|
||||
export interface CustomResourceTabLayoutRoute extends Omit<TabLayoutRoute, "component"> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute {
|
||||
subRoutes?: CustomResourceTabLayoutRoute[];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
customResourcesDefinitions: IComputedValue<Map<string, CustomResourceDefinition[]>>;
|
||||
}
|
||||
|
||||
function getRouteTabs({ customResourcesDefinitions }: Dependencies) {
|
||||
return computed(() => {
|
||||
const tabs: CustomResourceGroupTabLayoutRoute[] = [
|
||||
{
|
||||
id: "definitions",
|
||||
title: "Definitions",
|
||||
url: crdURL(),
|
||||
routePath: String(crdDefinitionsRoute.path),
|
||||
exact: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (const [group, definitions] of customResourcesDefinitions.get()) {
|
||||
tabs.push({
|
||||
id: `crd-group:${group}`,
|
||||
title: group,
|
||||
routePath: crdURL({ query: { groups: group }}),
|
||||
subRoutes: definitions.map(crd => ({
|
||||
id: `crd-resource:${crd.getResourceApiBase()}`,
|
||||
title: crd.getResourceKind(),
|
||||
routePath: crd.getResourceUrl(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
});
|
||||
}
|
||||
|
||||
const customResourcesRouteTabsInjectable = getInjectable({
|
||||
id: "custom-resources-route-tabs",
|
||||
|
||||
instantiate: (di) => getRouteTabs({
|
||||
customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default customResourcesRouteTabsInjectable;
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { crdDefinitionsRoute, crdResourcesRoute, crdURL } from "../../../common/routes";
|
||||
import { CustomResourceDefinitions } from "./crd-list";
|
||||
import { CustomResourceDefinitionResources } from "./crd-resources";
|
||||
|
||||
export const CustomResourcesRoute = () => (
|
||||
<TabLayout>
|
||||
<Switch>
|
||||
<Route component={CustomResourceDefinitions} {...crdDefinitionsRoute} exact/>
|
||||
<Route component={CustomResourceDefinitionResources} {...crdResourcesRoute}/>
|
||||
<Redirect to={crdURL()}/>
|
||||
</Switch>
|
||||
</TabLayout>
|
||||
);
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React, { useEffect } from "react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { crdURL, crdRoute } from "../../../common/routes";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
|
||||
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { Spinner } from "../spinner";
|
||||
|
||||
export interface CustomResourcesSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<CustomResourceGroupTabLayoutRoute[]>;
|
||||
isAllowedResource: IsAllowedResource;
|
||||
subscribeStores: SubscribeStores;
|
||||
}
|
||||
|
||||
const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResource, subscribeStores }: Dependencies & CustomResourcesSidebarItemProps) => {
|
||||
useEffect(() => subscribeStores([
|
||||
crdStore,
|
||||
]), []);
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
id="custom-resources"
|
||||
text="Custom Resources"
|
||||
url={crdURL()}
|
||||
isActive={isActiveRoute(crdRoute)}
|
||||
isHidden={!isAllowedResource("customresourcedefinitions")}
|
||||
icon={<Icon material="extension"/>}
|
||||
>
|
||||
{routes.get().map((route) => (
|
||||
<SidebarItem
|
||||
key={route.id}
|
||||
id={route.id}
|
||||
text={route.title}
|
||||
url={route.routePath}
|
||||
>
|
||||
{route.subRoutes?.map((subRoute) => (
|
||||
<SidebarItem
|
||||
key={subRoute.id}
|
||||
id={subRoute.id}
|
||||
url={subRoute.routePath}
|
||||
text={subRoute.title}
|
||||
/>
|
||||
))}
|
||||
</SidebarItem>
|
||||
))}
|
||||
{crdStore.isLoading && (
|
||||
<div className="flex justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const CustomResourcesSidebarItem = withInjectables<Dependencies, CustomResourcesSidebarItemProps>(NonInjectedCustomResourcesSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(customResourcesRouteTabsInjectable),
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
subscribeStores: di.inject(subscribeStoresInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
|
||||
import customResourceDefinitionsInjectable from "./custom-resources.injectable";
|
||||
import { groupBy, matches, noop, some, toPairs } from "lodash/fp";
|
||||
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
|
||||
import currentPathParametersInjectable from "../../routes/current-path-parameters.injectable";
|
||||
import type { SidebarItemRegistration } from "../layout/sidebar-items.injectable";
|
||||
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
|
||||
import navigateToCustomResourcesInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable";
|
||||
|
||||
const sidebarItemsForDefinitionGroupsInjectable = getInjectable({
|
||||
id: "sidebar-items-for-definition-groups",
|
||||
|
||||
instantiate: (di) => {
|
||||
const customResourceDefinitions = di.inject(
|
||||
customResourceDefinitionsInjectable,
|
||||
);
|
||||
|
||||
const crdRoute = di.inject(customResourcesRouteInjectable);
|
||||
const crdRouteIsActive = di.inject(routeIsActiveInjectable, crdRoute);
|
||||
const crdListRoute = di.inject(crdListRouteInjectable);
|
||||
const pathParameters = di.inject(currentPathParametersInjectable);
|
||||
const navigateToCustomResources = di.inject(navigateToCustomResourcesInjectable);
|
||||
|
||||
return computed((): SidebarItemRegistration[] => {
|
||||
const definitions = customResourceDefinitions.get();
|
||||
|
||||
const groupedCrds = toPairs(
|
||||
groupBy((crd) => crd.getGroup(), definitions),
|
||||
);
|
||||
|
||||
return groupedCrds.flatMap(([group, definitions]) => {
|
||||
const childItems = definitions.map((crd) => {
|
||||
const title = crd.getResourceKind();
|
||||
|
||||
const crdPathParameters = {
|
||||
group: crd.getGroup(),
|
||||
name: crd.getPluralName(),
|
||||
};
|
||||
|
||||
return {
|
||||
id: `custom-resource-definition-group-${group}-crd-${crd.getId()}`,
|
||||
parentId: `custom-resource-definition-group-${group}`,
|
||||
title,
|
||||
|
||||
onClick: () => navigateToCustomResources(crdPathParameters),
|
||||
|
||||
isActive: computed(
|
||||
() =>
|
||||
crdRouteIsActive.get() &&
|
||||
matches(crdPathParameters, pathParameters.get()),
|
||||
),
|
||||
|
||||
isVisible: crdListRoute.isEnabled,
|
||||
orderNumber: 10,
|
||||
};
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: `custom-resource-definition-group-${group}`,
|
||||
parentId: "custom-resources",
|
||||
title: group,
|
||||
onClick: noop,
|
||||
isVisible: computed(() => some(item => item.isVisible.get(), childItems)),
|
||||
orderNumber: 10,
|
||||
},
|
||||
|
||||
...childItems,
|
||||
];
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default sidebarItemsForDefinitionGroupsInjectable;
|
||||
Reference in new issue
Block a user