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:
parent
1677a016c0
commit
5e6cf163a2
@ -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",
|
||||
});
|
||||
|
||||
@ -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", () => {
|
||||
|
||||
17
src/renderer/components/+catalog/columns/custom-token.ts
Normal file
17
src/renderer/components/+catalog/columns/custom-token.ts
Normal 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",
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
@ -47,7 +47,10 @@ const getCategoryColumnsInjectable = getInjectable({
|
||||
}
|
||||
|
||||
tableRowRenderers.push(registration.renderCell);
|
||||
renderTableHeader.push(registration.titleProps);
|
||||
renderTableHeader.push({
|
||||
id: registration.id,
|
||||
...registration.titleProps,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user