From 8711fec4ad0c0b62373b6718df68380354880498 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 26 Aug 2021 10:02:29 -0400 Subject: [PATCH] switch to filtering only for catalog view Signed-off-by: Sebastian Malton --- src/common/utils/iter.ts | 2 + src/extensions/lens-main-extension.ts | 18 +----- src/extensions/lens-renderer-extension.ts | 17 +++++- .../__tests__/catalog-entity-registry.test.ts | 44 +------------- src/main/catalog/catalog-entity-registry.ts | 36 ++--------- .../__tests__/catalog-entity-registry.test.ts | 60 ++++++++++++++++++- src/renderer/api/catalog-entity-registry.ts | 56 +++++++++++++---- .../+catalog/catalog-entity.store.tsx | 4 +- .../components/hotbar/hotbar-menu.tsx | 2 +- 9 files changed, 134 insertions(+), 105 deletions(-) diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index eadd5fc7cd..2a91b6afd9 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -163,6 +163,8 @@ export function find(src: Iterable, match: (i: T) => any): T | undefined { * @param reducer A function for producing the next item from an accumilation and the current item * @param initial The initial value for the iteration */ +export function reduce(src: Iterable, reducer: (acc: Iterable, cur: T) => Iterable, initial: Iterable): Iterable; + export function reduce(src: Iterable, reducer: (acc: R, cur: T) => R, initial: R): R { let acc = initial; diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index 8cc9d5734d..cbdba9e07c 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -19,13 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Disposers, LensExtension } from "./lens-extension"; +import { LensExtension } from "./lens-extension"; import { WindowManager } from "../main/window-manager"; -import { catalogEntityRegistry, EntityFilter } from "../main/catalog"; +import { catalogEntityRegistry } from "../main/catalog"; import type { CatalogEntity } from "../common/catalog"; import type { IObservableArray } from "mobx"; import type { MenuRegistration } from "./registries"; -import type { Disposer } from "../common/utils"; export class LensMainExtension extends LensExtension { appMenus: MenuRegistration[] = []; @@ -34,19 +33,6 @@ export class LensMainExtension extends LensExtension { return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId); } - /** - * Add a filtering function for the catalog. This will be removed if the extension is disabled. - * @param fn The function which should return a truthy value for those entities which should be kepted - * @returns A function to clean up the filter - */ - addCatalogFilter(fn: EntityFilter): Disposer { - const dispose = catalogEntityRegistry.addCatalogFilter(fn); - - this[Disposers].push(dispose); - - return dispose; - } - addCatalogSource(id: string, source: IObservableArray) { catalogEntityRegistry.addObservableSource(`${this.name}:${id}`, source); } diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 0ae8a44323..b56ffb3d7e 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -21,9 +21,11 @@ import type * as registries from "./registries"; import type { Cluster } from "../main/cluster"; -import { LensExtension } from "./lens-extension"; +import { Disposers, LensExtension } from "./lens-extension"; import { getExtensionPageUrl } from "./registries/page-registry"; import type { CatalogEntity } from "../common/catalog"; +import type { Disposer } from "../common/utils"; +import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -59,4 +61,17 @@ export class LensRendererExtension extends LensExtension { async isEnabledForCluster(cluster: Cluster): Promise { return (void cluster) || true; } + + /** + * Add a filtering function for the catalog. This will be removed if the extension is disabled. + * @param fn The function which should return a truthy value for those entities which should be kepted + * @returns A function to clean up the filter + */ + addCatalogFilter(fn: EntityFilter): Disposer { + const dispose = catalogEntityRegistry.addCatalogFilter(fn); + + this[Disposers].push(dispose); + + return dispose; + } } diff --git a/src/main/catalog/__tests__/catalog-entity-registry.test.ts b/src/main/catalog/__tests__/catalog-entity-registry.test.ts index c38c2be348..ee8450ae26 100644 --- a/src/main/catalog/__tests__/catalog-entity-registry.test.ts +++ b/src/main/catalog/__tests__/catalog-entity-registry.test.ts @@ -20,7 +20,7 @@ */ import { observable, reaction } from "mobx"; -import { KubernetesCluster, WebLink, WebLinkSpec, WebLinkStatus } from "../../../common/catalog-entities"; +import { WebLink, WebLinkSpec, WebLinkStatus } from "../../../common/catalog-entities"; import { catalogCategoryRegistry, CatalogEntity, CatalogEntityMetadata } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../catalog-entity-registry"; @@ -61,35 +61,6 @@ describe("CatalogEntityRegistry", () => { phase: "available" } }); - const entity2 = new WebLink({ - metadata: { - uid: "test2", - name: "test-link", - source: "test", - labels: {} - }, - spec: { - url: "https://k8slens.dev" - }, - status: { - phase: "available" - } - }); - const entitykc = new KubernetesCluster({ - metadata: { - uid: "test2", - name: "test-link", - source: "test", - labels: {} - }, - spec: { - kubeconfigPath: "", - kubeconfigContext: "", - }, - status: { - phase: "connected" - } - }); const invalidEntity = new InvalidEntity({ metadata: { uid: "invalid", @@ -161,18 +132,5 @@ describe("CatalogEntityRegistry", () => { registry.addObservableSource("test", source); expect(registry.items.length).toBe(0); }); - - it("does not return items that are filtered out", () => { - const source = observable.array([entity, entity2, entitykc]); - - registry.addObservableSource("test", source); - expect(registry.items.length).toBe(3); - - const d = registry.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind); - - expect(registry.items.length).toBe(1); - d(); - expect(registry.items.length).toBe(3); - }); }); }); diff --git a/src/main/catalog/catalog-entity-registry.ts b/src/main/catalog/catalog-entity-registry.ts index bc6bf0e309..e9e441ae2b 100644 --- a/src/main/catalog/catalog-entity-registry.ts +++ b/src/main/catalog/catalog-entity-registry.ts @@ -19,20 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { once } from "lodash"; import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx"; import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityKindData } from "../../common/catalog"; -import { Disposer, iter } from "../../common/utils"; - -export type EntityFilter = (entity: CatalogEntity) => any; +import { iter } from "../../common/utils"; export class CatalogEntityRegistry { protected sources = observable.map>(); - protected filters = observable.set([ - entity => this.categoryRegistry.getCategoryForEntity(entity) - ], { - deep: false, - }); constructor(private categoryRegistry: CatalogCategoryRegistry) { makeObservable(this); @@ -52,42 +44,24 @@ export class CatalogEntityRegistry { @computed get items(): CatalogEntity[] { return Array.from( - iter.reduce( - this.filters, - iter.filter, + iter.filter( iter.flatMap(this.sources.values(), source => source.get()), + entity => this.categoryRegistry.getCategoryForEntity(entity) ) ); } getById(id: string): T | undefined { - const item = this.items.find((entity) => entity.metadata.uid === id); - - if (item) return item as T; - - return undefined; + return this.items.find((entity) => entity.metadata.uid === id) as T | undefined; } getItemsForApiKind(apiVersion: string, kind: string): T[] { - const items = this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind); - - return items as T[]; + return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind) as T[]; } getItemsByEntityClass({ apiVersion, kind }: CatalogEntityKindData): T[] { return this.getItemsForApiKind(apiVersion, kind); } - - /** - * Add a new filter to the set of item filters - * @param fn The function that should return a truthy value if that entity should be sent currently "active" - * @returns A function to remove that filter - */ - addCatalogFilter(fn: EntityFilter): Disposer { - this.filters.add(fn); - - return once(() => void this.filters.delete(fn)); - } } export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); diff --git a/src/renderer/api/__tests__/catalog-entity-registry.test.ts b/src/renderer/api/__tests__/catalog-entity-registry.test.ts index 4c3bcb48c3..a6f7ee1163 100644 --- a/src/renderer/api/__tests__/catalog-entity-registry.test.ts +++ b/src/renderer/api/__tests__/catalog-entity-registry.test.ts @@ -23,7 +23,8 @@ import { CatalogEntityRegistry } from "../catalog-entity-registry"; import "../../../common/catalog-entities"; import { catalogCategoryRegistry } from "../../../common/catalog/catalog-category-registry"; import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "../catalog-entity"; -import { WebLink } from "../../../common/catalog-entities"; +import { KubernetesCluster, WebLink } from "../../../common/catalog-entities"; +import { observable } from "mobx"; class TestCatalogEntityRegistry extends CatalogEntityRegistry { replaceItems(items: Array) { @@ -51,6 +52,49 @@ class FooBarCategory extends CatalogCategory { } }; } +const entity = new WebLink({ + metadata: { + uid: "test", + name: "test-link", + source: "test", + labels: {} + }, + spec: { + url: "https://k8slens.dev" + }, + status: { + phase: "available" + } +}); +const entity2 = new WebLink({ + metadata: { + uid: "test2", + name: "test-link", + source: "test", + labels: {} + }, + spec: { + url: "https://k8slens.dev" + }, + status: { + phase: "available" + } +}); +const entitykc = new KubernetesCluster({ + metadata: { + uid: "test2", + name: "test-link", + source: "test", + labels: {} + }, + spec: { + kubeconfigPath: "", + kubeconfigContext: "", + }, + status: { + phase: "connected" + } +}); describe("CatalogEntityRegistry", () => { describe("updateItems", () => { @@ -250,4 +294,18 @@ describe("CatalogEntityRegistry", () => { catalogCategoryRegistry.add(new FooBarCategory()); expect(catalog.items.length).toBe(1); }); + + it("does not return items that are filtered out", () => { + const source = observable.array([entity, entity2, entitykc]); + const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); + + catalog.replaceItems(source); + expect(catalog.items.length).toBe(3); + + const d = catalog.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind); + + expect(catalog.items.length).toBe(1); + d(); + expect(catalog.items.length).toBe(3); + }); }); diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index a067b520d7..7b09ff273b 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -25,10 +25,17 @@ import { CatalogCategory, CatalogEntity, CatalogEntityData, catalogCategoryRegis import "../../common/catalog-entities"; import type { Cluster } from "../../main/cluster"; import { ClusterStore } from "../../common/cluster-store"; +import { Disposer, iter } from "../utils"; +import { once } from "lodash"; + +export type EntityFilter = (entity: CatalogEntity) => any; export class CatalogEntityRegistry { @observable.ref activeEntity: CatalogEntity; protected _entities = observable.map([], { deep: true }); + protected filters = observable.set([], { + deep: false, + }); /** * Buffer for keeping entities that don't yet have CatalogCategory synced @@ -95,27 +102,56 @@ export class CatalogEntityRegistry { return Array.from(this._entities.values()); } - @computed get entities(): Map { - this.processRawEntities(); + @computed get filteredItems() { + return Array.from( + iter.reduce( + this.filters, + iter.filter, + this.items, + ) + ); + } - return this._entities; + @computed get entities(): Map { + return new Map( + this.items.map(entity => [entity.getId(), entity]) + ); + } + + @computed get filteredEntities(): Map { + return new Map( + this.filteredItems.map(entity => [entity.getId(), entity]) + ); } getById(id: string) { return this.entities.get(id) as T; } - getItemsForApiKind(apiVersion: string, kind: string): T[] { - const items = this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind); + getItemsForApiKind(apiVersion: string, kind: string, { filtered = false } = {}): T[] { + const byApiKind = (item: CatalogEntity) => item.apiVersion === apiVersion && item.kind === kind; + const entities = filtered ? this.filteredItems : this.items; - return items as T[]; + return entities.filter(byApiKind) as T[]; } - getItemsForCategory(category: CatalogCategory): T[] { - const supportedVersions = category.spec.versions.map((v) => `${category.spec.group}/${v.name}`); - const items = this.items.filter((item) => supportedVersions.includes(item.apiVersion) && item.kind === category.spec.names.kind); + getItemsForCategory(category: CatalogCategory, { filtered = false } = {}): T[] { + const supportedVersions = new Set(category.spec.versions.map((v) => `${category.spec.group}/${v.name}`)); + const byApiVersionKind = (item: CatalogEntity) => supportedVersions.has(item.apiVersion) && item.kind === category.spec.names.kind; + const entities = filtered ? this.filteredItems : this.items; - return items as T[]; + return entities.filter(byApiVersionKind) as T[]; + } + + /** + * Add a new filter to the set of item filters + * @param fn The function that should return a truthy value if that entity should be sent currently "active" + * @returns A function to remove that filter + */ + addCatalogFilter(fn: EntityFilter): Disposer { + this.filters.add(fn); + + return once(() => void this.filters.delete(fn)); } } diff --git a/src/renderer/components/+catalog/catalog-entity.store.tsx b/src/renderer/components/+catalog/catalog-entity.store.tsx index c5bb96e5f2..05f317b7b1 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.tsx +++ b/src/renderer/components/+catalog/catalog-entity.store.tsx @@ -129,10 +129,10 @@ export class CatalogEntityStore extends ItemStore new CatalogEntityItem(entity)); + return catalogEntityRegistry.filteredItems.map(entity => new CatalogEntityItem(entity)); } - return catalogEntityRegistry.getItemsForCategory(this.activeCategory).map(entity => new CatalogEntityItem(entity)); + return catalogEntityRegistry.getItemsForCategory(this.activeCategory, { filtered: true }).map(entity => new CatalogEntityItem(entity)); } @computed get selectedItem() { diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index 5df1f48049..cc2c1903c0 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -52,7 +52,7 @@ export class HotbarMenu extends React.Component { return null; } - return item ? catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid) : null; + return catalogEntityRegistry.getById(item?.entity.uid) ?? null; } onDragEnd(result: DropResult) {