From 5e6cf163a22f0adb86d2dce9d44dc2e5a351c841 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 7 Oct 2022 09:16:05 -0400 Subject: [PATCH] Add distribution and version columns for KubernetesClusters (#6166) * Add distribution and version columns for KubernetesClusters Signed-off-by: Sebastian Malton * Fix ItemObjectList to not display column toggles for columns without IDs Signed-off-by: Sebastian Malton * Default the title cell props ID to be the registration ID Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../extension-registrator-injection-token.ts | 10 ++-- .../+catalog/__tests__/custom-columns.test.ts | 44 +++++++------- .../+catalog/columns/custom-token.ts | 17 ++++++ .../extensions-registrator.injectable.ts | 58 ++++++++++++++++++ .../+catalog/columns/get.injectable.ts | 5 +- .../kubernetes-api-version.injectable.tsx | 35 +++++++++++ .../kubernetes-distribution.injectable.tsx | 36 +++++++++++ .../custom-category-columns.injectable.tsx | 59 ++++++------------- .../components/item-object-list/content.tsx | 35 ++++++----- 9 files changed, 214 insertions(+), 85 deletions(-) create mode 100644 src/renderer/components/+catalog/columns/custom-token.ts create mode 100644 src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts create mode 100644 src/renderer/components/+catalog/columns/kubernetes-api-version.injectable.tsx create mode 100644 src/renderer/components/+catalog/columns/kubernetes-distribution.injectable.tsx diff --git a/src/extensions/extension-loader/extension-registrator-injection-token.ts b/src/extensions/extension-loader/extension-registrator-injection-token.ts index b5527cd00d..6dd725c1b5 100644 --- a/src/extensions/extension-loader/extension-registrator-injection-token.ts +++ b/src/extensions/extension-loader/extension-registrator-injection-token.ts @@ -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[] - >({ - id: "extension-registrator-token", - }); +export type ExtensionRegistrator = (extension: LensExtension) => Injectable[]; + +export const extensionRegistratorInjectionToken = getInjectionToken({ + id: "extension-registrator-token", +}); diff --git a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts index d689e8f812..c79162b55e 100644 --- a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts +++ b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts @@ -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", () => { diff --git a/src/renderer/components/+catalog/columns/custom-token.ts b/src/renderer/components/+catalog/columns/custom-token.ts new file mode 100644 index 0000000000..140f407e9e --- /dev/null +++ b/src/renderer/components/+catalog/columns/custom-token.ts @@ -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({ + id: "custom-catalog-category-column-token", +}); diff --git a/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts b/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts new file mode 100644 index 0000000000..c4223da3ea --- /dev/null +++ b/src/renderer/components/+catalog/columns/extensions-registrator.injectable.ts @@ -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, + }); +}; diff --git a/src/renderer/components/+catalog/columns/get.injectable.ts b/src/renderer/components/+catalog/columns/get.injectable.ts index 03930578c1..91a92473fd 100644 --- a/src/renderer/components/+catalog/columns/get.injectable.ts +++ b/src/renderer/components/+catalog/columns/get.injectable.ts @@ -47,7 +47,10 @@ const getCategoryColumnsInjectable = getInjectable({ } tableRowRenderers.push(registration.renderCell); - renderTableHeader.push(registration.titleProps); + renderTableHeader.push({ + id: registration.id, + ...registration.titleProps, + }); } return { diff --git a/src/renderer/components/+catalog/columns/kubernetes-api-version.injectable.tsx b/src/renderer/components/+catalog/columns/kubernetes-api-version.injectable.tsx new file mode 100644 index 0000000000..13e8127687 --- /dev/null +++ b/src/renderer/components/+catalog/columns/kubernetes-api-version.injectable.tsx @@ -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 ( + + {k8sVersion === "unknown" ? "" : k8sVersion} + + ); + }, + titleProps: { + title: "Version", + }, + }, + }), + injectionToken: customCatalogCategoryColumnInjectionToken, +}); + +export default kubernetesApiVersionColumnInjectable; diff --git a/src/renderer/components/+catalog/columns/kubernetes-distribution.injectable.tsx b/src/renderer/components/+catalog/columns/kubernetes-distribution.injectable.tsx new file mode 100644 index 0000000000..617d55b11d --- /dev/null +++ b/src/renderer/components/+catalog/columns/kubernetes-distribution.injectable.tsx @@ -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 ( + + {k8sDistro === "unknown" ? "" : k8sDistro} + + ); + }, + titleProps: { + title: "Distro", + }, + }, + }), + injectionToken: customCatalogCategoryColumnInjectionToken, +}); + +export default kubernetesDistributionColumnInjectable; + diff --git a/src/renderer/components/+catalog/custom-category-columns.injectable.tsx b/src/renderer/components/+catalog/custom-category-columns.injectable.tsx index edfbe31f6d..1c0d13964d 100644 --- a/src/renderer/components/+catalog/custom-category-columns.injectable.tsx +++ b/src/renderer/components/+catalog/custom-category-columns.injectable.tsx @@ -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; -} - -function getAdditionCategoryColumns({ extensions }: Dependencies): IComputedValue>> { - return computed(() => { - const res = new Map>(); - - 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>(); + + 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; diff --git a/src/renderer/components/item-object-list/content.tsx b/src/renderer/components/item-object-list/content.tsx index 0b8aa9ca28..530d6255cb 100644 --- a/src/renderer/components/item-object-list/content.tsx +++ b/src/renderer/components/item-object-list/content.tsx @@ -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< )) } - {isConfigurable && this.renderColumnVisibilityMenu()} + {(isConfigurable && tableId) + ? this.renderColumnVisibilityMenu(tableId) + : undefined + } ); @@ -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 ( ( - !cellProps.showWithColumn && ( - - `} - value={this.showColumn(cellProps)} - onChange={( - tableId - ? (() => cellProps.id && this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)) - : undefined - )} - /> - - ) + .filter((props): props is TableCellProps & { id: string } => !!props.id) + .filter(props => !props.showWithColumn) + .map((cellProps) => ( + + `} + value={this.showColumn(cellProps)} + onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)} + /> + )) }