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 { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { LensExtension } from "../lens-extension";
|
import type { LensExtension } from "../lens-extension";
|
||||||
|
|
||||||
export const extensionRegistratorInjectionToken = getInjectionToken<
|
export type ExtensionRegistrator = (extension: LensExtension) => Injectable<any, any, any>[];
|
||||||
(extension: LensExtension) => Injectable<any, any, any>[]
|
|
||||||
>({
|
export const extensionRegistratorInjectionToken = getInjectionToken<ExtensionRegistrator>({
|
||||||
id: "extension-registrator-token",
|
id: "extension-registrator-token",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,16 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import { computed } from "mobx";
|
|
||||||
import type { CatalogCategorySpec } from "../../../../common/catalog";
|
import type { CatalogCategorySpec } from "../../../../common/catalog";
|
||||||
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
import { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
||||||
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
|
|
||||||
import { CatalogCategory } from "../../../api/catalog-entity";
|
import { CatalogCategory } from "../../../api/catalog-entity";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
||||||
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
|
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
|
||||||
import getCategoryColumnsInjectable from "../columns/get.injectable";
|
import getCategoryColumnsInjectable from "../columns/get.injectable";
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
||||||
|
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
||||||
|
|
||||||
class TestCategory extends CatalogCategory {
|
class TestCategory extends CatalogCategory {
|
||||||
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
@ -41,21 +40,16 @@ class TestCategory extends CatalogCategory {
|
|||||||
|
|
||||||
describe("Custom Category Columns", () => {
|
describe("Custom Category Columns", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
|
let getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(hotbarStoreInjectable, () => ({}));
|
di.override(hotbarStoreInjectable, () => ({}));
|
||||||
|
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("without extensions", () => {
|
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", () => {
|
it("should contain a kind column if activeCategory is falsy", () => {
|
||||||
expect(getCategoryColumns({ activeCategory: null }).renderTableHeader.find(elem => elem?.title === "Kind")).toBeTruthy();
|
expect(getCategoryColumns({ activeCategory: null }).renderTableHeader.find(elem => elem?.title === "Kind")).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -88,13 +82,9 @@ describe("Custom Category Columns", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("with extensions", () => {
|
describe("with extensions", () => {
|
||||||
let getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di.override(rendererExtensionsInjectable, () => computed(() => [
|
const ext = di.inject(extensionInjectable, new (class extends LensRendererExtension {
|
||||||
{
|
additionalCategoryColumns = [
|
||||||
name: "test-extension",
|
|
||||||
additionalCategoryColumns: [
|
|
||||||
{
|
{
|
||||||
group: "foo.bar.bat",
|
group: "foo.bar.bat",
|
||||||
id: "high",
|
id: "high",
|
||||||
@ -113,10 +103,24 @@ describe("Custom Category Columns", () => {
|
|||||||
title: "High2",
|
title: "High2",
|
||||||
},
|
},
|
||||||
} as AdditionalCategoryColumnRegistration,
|
} as AdditionalCategoryColumnRegistration,
|
||||||
],
|
];
|
||||||
} as LensRendererExtension,
|
})({
|
||||||
]));
|
absolutePath: "/some-absolute-path",
|
||||||
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
|
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", () => {
|
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);
|
tableRowRenderers.push(registration.renderCell);
|
||||||
renderTableHeader.push(registration.titleProps);
|
renderTableHeader.push({
|
||||||
|
id: registration.id,
|
||||||
|
...registration.titleProps,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
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 { computed } from "mobx";
|
||||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
|
||||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
|
||||||
import { getOrInsert, getOrInsertMap } from "../../utils";
|
import { getOrInsert, getOrInsertMap } from "../../utils";
|
||||||
|
import { customCatalogCategoryColumnInjectionToken } from "./columns/custom-token";
|
||||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
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({
|
const categoryColumnsInjectable = getInjectable({
|
||||||
id: "category-columns",
|
id: "category-columns",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||||
|
const columnRegistrations = computedInjectMany(customCatalogCategoryColumnInjectionToken);
|
||||||
|
|
||||||
instantiate: (di) => getAdditionCategoryColumns({
|
return computed(() => {
|
||||||
extensions: di.inject(rendererExtensionsInjectable),
|
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;
|
export default categoryColumnsInjectable;
|
||||||
|
|||||||
@ -252,7 +252,7 @@ class NonInjectedItemListLayoutContent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTableHeader() {
|
renderTableHeader() {
|
||||||
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store } = this.props;
|
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, tableId } = this.props;
|
||||||
|
|
||||||
if (!renderTableHeader) {
|
if (!renderTableHeader) {
|
||||||
return null;
|
return null;
|
||||||
@ -283,7 +283,10 @@ class NonInjectedItemListLayoutContent<
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
<TableCell className="menu">
|
<TableCell className="menu">
|
||||||
{isConfigurable && this.renderColumnVisibilityMenu()}
|
{(isConfigurable && tableId)
|
||||||
|
? this.renderColumnVisibilityMenu(tableId)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
);
|
);
|
||||||
@ -341,8 +344,8 @@ class NonInjectedItemListLayoutContent<
|
|||||||
return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
|
return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderColumnVisibilityMenu() {
|
renderColumnVisibilityMenu(tableId: string) {
|
||||||
const { renderTableHeader = [], tableId } = this.props;
|
const { renderTableHeader = [] } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuActions
|
<MenuActions
|
||||||
@ -354,20 +357,16 @@ class NonInjectedItemListLayoutContent<
|
|||||||
{
|
{
|
||||||
renderTableHeader
|
renderTableHeader
|
||||||
.filter(isDefined)
|
.filter(isDefined)
|
||||||
.map((cellProps, index) => (
|
.filter((props): props is TableCellProps & { id: string } => !!props.id)
|
||||||
!cellProps.showWithColumn && (
|
.filter(props => !props.showWithColumn)
|
||||||
<MenuItem key={index} className="input">
|
.map((cellProps) => (
|
||||||
<Checkbox
|
<MenuItem key={cellProps.id} className="input">
|
||||||
label={cellProps.title ?? `<${cellProps.className}>`}
|
<Checkbox
|
||||||
value={this.showColumn(cellProps)}
|
label={cellProps.title ?? `<${cellProps.className}>`}
|
||||||
onChange={(
|
value={this.showColumn(cellProps)}
|
||||||
tableId
|
onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)}
|
||||||
? (() => cellProps.id && this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id))
|
/>
|
||||||
: undefined
|
</MenuItem>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user