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

Add distribution and version columns for KubernetesClusters (#6166)

* Add distribution and version columns for KubernetesClusters

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix ItemObjectList to not display column toggles for columns without IDs

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Default the title cell props ID to be the registration ID

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-10-07 09:16:05 -04:00 committed by GitHub
parent 1677a016c0
commit 5e6cf163a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 214 additions and 85 deletions

View File

@ -6,8 +6,8 @@ import type { Injectable } from "@ogre-tools/injectable";
import { getInjectionToken } from "@ogre-tools/injectable";
import type { LensExtension } from "../lens-extension";
export const extensionRegistratorInjectionToken = getInjectionToken<
(extension: LensExtension) => Injectable<any, any, any>[]
>({
id: "extension-registrator-token",
});
export type ExtensionRegistrator = (extension: LensExtension) => Injectable<any, any, any>[];
export const extensionRegistratorInjectionToken = getInjectionToken<ExtensionRegistrator>({
id: "extension-registrator-token",
});

View File

@ -4,16 +4,15 @@
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { CatalogCategorySpec } from "../../../../common/catalog";
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
import { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import { CatalogCategory } from "../../../api/catalog-entity";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
import getCategoryColumnsInjectable from "../columns/get.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
@ -41,21 +40,16 @@ class TestCategory extends CatalogCategory {
describe("Custom Category Columns", () => {
let di: DiContainer;
let getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(hotbarStoreInjectable, () => ({}));
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
});
describe("without extensions", () => {
let getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
beforeEach(() => {
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
});
it("should contain a kind column if activeCategory is falsy", () => {
expect(getCategoryColumns({ activeCategory: null }).renderTableHeader.find(elem => elem?.title === "Kind")).toBeTruthy();
});
@ -88,13 +82,9 @@ describe("Custom Category Columns", () => {
});
describe("with extensions", () => {
let getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
beforeEach(() => {
di.override(rendererExtensionsInjectable, () => computed(() => [
{
name: "test-extension",
additionalCategoryColumns: [
const ext = di.inject(extensionInjectable, new (class extends LensRendererExtension {
additionalCategoryColumns = [
{
group: "foo.bar.bat",
id: "high",
@ -113,10 +103,24 @@ describe("Custom Category Columns", () => {
title: "High2",
},
} as AdditionalCategoryColumnRegistration,
],
} as LensRendererExtension,
]));
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
];
})({
absolutePath: "/some-absolute-path",
id: "some-id",
isBundled: false,
isCompatible: true,
isEnabled: true,
manifest: {
engines: {
lens: "",
},
name: "some-extension-name",
version: "1.0.0",
},
manifestPath: "/some-manifest-path",
}));
ext.register();
});
it("should include columns from extensions that match", () => {

View File

@ -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 { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
export interface CustomCatalogCategoryRegistration {
kind: string;
group: string;
registration: RegisteredAdditionalCategoryColumn;
}
export const customCatalogCategoryColumnInjectionToken = getInjectionToken<CustomCatalogCategoryRegistration>({
id: "custom-catalog-category-column-token",
});

View File

@ -0,0 +1,58 @@
/**
* 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 { ExtensionRegistrator } from "../../../../extensions/extension-loader/extension-registrator-injection-token";
import { extensionRegistratorInjectionToken } from "../../../../extensions/extension-loader/extension-registrator-injection-token";
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import type { AdditionalCategoryColumnRegistration } from "../custom-category-columns";
import { customCatalogCategoryColumnInjectionToken } from "./custom-token";
const customCategoryColumnsRegistratorInjectable = getInjectable({
id: "custom-category-columns-registrator",
instantiate: (): ExtensionRegistrator => {
return (ext) => {
const extension = ext as LensRendererExtension;
return extension.additionalCategoryColumns.map(getInjectableForColumnRegistrationFor(extension));
};
},
injectionToken: extensionRegistratorInjectionToken,
});
export default customCategoryColumnsRegistratorInjectable;
const getInjectableForColumnRegistrationFor = (extension: LensRendererExtension) => ({
group,
id,
kind,
renderCell,
titleProps,
priority = 50,
searchFilter,
sortCallback,
}: AdditionalCategoryColumnRegistration) => {
return getInjectable({
id: `${extension.manifest.name}:${group}/${kind}:${id}`,
instantiate: () => ({
group,
kind,
registration: {
renderCell,
priority,
id,
titleProps: {
id,
...titleProps,
sortBy: sortCallback
? id
: undefined,
},
searchFilter,
sortCallback,
},
}),
injectionToken: customCatalogCategoryColumnInjectionToken,
});
};

View File

@ -47,7 +47,10 @@ const getCategoryColumnsInjectable = getInjectable({
}
tableRowRenderers.push(registration.renderCell);
renderTableHeader.push(registration.titleProps);
renderTableHeader.push({
id: registration.id,
...registration.titleProps,
});
}
return {

View File

@ -0,0 +1,35 @@
/**
* 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 React from "react";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { customCatalogCategoryColumnInjectionToken } from "./custom-token";
const kubernetesApiVersionColumnInjectable = getInjectable({
id: "kubernetes-api-version-column",
instantiate: () => ({
group: "entity.k8slens.dev",
kind: "KubernetesCluster",
registration: {
id: "version",
priority: 30,
renderCell: entity => {
const k8sVersion = (entity as KubernetesCluster).metadata.kubeVersion;
return (
<span key="version">
{k8sVersion === "unknown" ? "" : k8sVersion}
</span>
);
},
titleProps: {
title: "Version",
},
},
}),
injectionToken: customCatalogCategoryColumnInjectionToken,
});
export default kubernetesApiVersionColumnInjectable;

View File

@ -0,0 +1,36 @@
/**
* 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 React from "react";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { customCatalogCategoryColumnInjectionToken } from "./custom-token";
const kubernetesDistributionColumnInjectable = getInjectable({
id: "kubernetes-distribution-column",
instantiate: () => ({
group: "entity.k8slens.dev",
kind: "KubernetesCluster",
registration: {
id: "distro",
priority: 30,
renderCell: entity => {
const k8sDistro = (entity as KubernetesCluster).metadata.distro;
return (
<span key="distro">
{k8sDistro === "unknown" ? "" : k8sDistro}
</span>
);
},
titleProps: {
title: "Distro",
},
},
}),
injectionToken: customCatalogCategoryColumnInjectionToken,
});
export default kubernetesDistributionColumnInjectable;

View File

@ -3,54 +3,31 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { computed } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import { getOrInsert, getOrInsertMap } from "../../utils";
import { customCatalogCategoryColumnInjectionToken } from "./columns/custom-token";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
interface Dependencies {
extensions: IComputedValue<LensRendererExtension[]>;
}
function getAdditionCategoryColumns({ extensions }: Dependencies): IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>> {
return computed(() => {
const res = new Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>();
for (const ext of extensions.get()) {
for (const { renderCell, titleProps, priority = 50, searchFilter, sortCallback, ...registration } of ext.additionalCategoryColumns) {
const byGroup = getOrInsertMap(res, registration.group);
const byKind = getOrInsert(byGroup, registration.kind, []);
const id = `${ext.name}:${registration.id}`;
byKind.push({
renderCell,
priority,
id,
titleProps: {
id,
...titleProps,
sortBy: sortCallback
? id
: undefined,
},
searchFilter,
sortCallback,
});
}
}
return res;
});
}
const categoryColumnsInjectable = getInjectable({
id: "category-columns",
instantiate: (di) => {
const computedInjectMany = di.inject(computedInjectManyInjectable);
const columnRegistrations = computedInjectMany(customCatalogCategoryColumnInjectionToken);
instantiate: (di) => getAdditionCategoryColumns({
extensions: di.inject(rendererExtensionsInjectable),
}),
return computed(() => {
const res = new Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>();
for (const { group, kind, registration } of columnRegistrations.get()) {
const byGroup = getOrInsertMap(res, group);
const byKind = getOrInsert(byGroup, kind, []);
byKind.push(registration);
}
return res;
});
},
});
export default categoryColumnsInjectable;

View File

@ -252,7 +252,7 @@ class NonInjectedItemListLayoutContent<
}
renderTableHeader() {
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store } = this.props;
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, tableId } = this.props;
if (!renderTableHeader) {
return null;
@ -283,7 +283,10 @@ class NonInjectedItemListLayoutContent<
))
}
<TableCell className="menu">
{isConfigurable && this.renderColumnVisibilityMenu()}
{(isConfigurable && tableId)
? this.renderColumnVisibilityMenu(tableId)
: undefined
}
</TableCell>
</TableHead>
);
@ -341,8 +344,8 @@ class NonInjectedItemListLayoutContent<
return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
}
renderColumnVisibilityMenu() {
const { renderTableHeader = [], tableId } = this.props;
renderColumnVisibilityMenu(tableId: string) {
const { renderTableHeader = [] } = this.props;
return (
<MenuActions
@ -354,20 +357,16 @@ class NonInjectedItemListLayoutContent<
{
renderTableHeader
.filter(isDefined)
.map((cellProps, index) => (
!cellProps.showWithColumn && (
<MenuItem key={index} className="input">
<Checkbox
label={cellProps.title ?? `<${cellProps.className}>`}
value={this.showColumn(cellProps)}
onChange={(
tableId
? (() => cellProps.id && this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id))
: undefined
)}
/>
</MenuItem>
)
.filter((props): props is TableCellProps & { id: string } => !!props.id)
.filter(props => !props.showWithColumn)
.map((cellProps) => (
<MenuItem key={cellProps.id} className="input">
<Checkbox
label={cellProps.title ?? `<${cellProps.className}>`}
value={this.showColumn(cellProps)}
onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)}
/>
</MenuItem>
))
}
</MenuActions>