diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index 59fce41829..eadd5fc7cd 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -156,3 +156,19 @@ export function find(src: Iterable, match: (i: T) => any): T | undefined { return void 0; } + +/** + * Iterate over `src` calling `reducer` with the previous produced value and the current + * yielded value until `src` is exausted. Then return the final value. + * @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: R, cur: T) => R, initial: R): R { + let acc = initial; + + for (const item of src) { + acc = reducer(acc, item); + } + + return acc; +} diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index cbdba9e07c..8cc9d5734d 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -19,12 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { LensExtension } from "./lens-extension"; +import { Disposers, LensExtension } from "./lens-extension"; import { WindowManager } from "../main/window-manager"; -import { catalogEntityRegistry } from "../main/catalog"; +import { catalogEntityRegistry, EntityFilter } 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[] = []; @@ -33,6 +34,19 @@ 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/main/catalog/__tests__/catalog-entity-registry.test.ts b/src/main/catalog/__tests__/catalog-entity-registry.test.ts index ee8450ae26..42dde66511 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 { WebLink, WebLinkSpec, WebLinkStatus } from "../../../common/catalog-entities"; +import { KubernetesCluster, WebLink, WebLinkSpec, WebLinkStatus } from "../../../common/catalog-entities"; import { catalogCategoryRegistry, CatalogEntity, CatalogEntityMetadata } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../catalog-entity-registry"; @@ -61,6 +61,35 @@ 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", @@ -132,5 +161,17 @@ 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 f2188132eb..bc6bf0e309 100644 --- a/src/main/catalog/catalog-entity-registry.ts +++ b/src/main/catalog/catalog-entity-registry.ts @@ -19,12 +19,20 @@ * 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 { iter } from "../../common/utils"; +import { Disposer, iter } from "../../common/utils"; + +export type EntityFilter = (entity: CatalogEntity) => any; export class CatalogEntityRegistry { protected sources = observable.map>(); + protected filters = observable.set([ + entity => this.categoryRegistry.getCategoryForEntity(entity) + ], { + deep: false, + }); constructor(private categoryRegistry: CatalogCategoryRegistry) { makeObservable(this); @@ -43,9 +51,13 @@ export class CatalogEntityRegistry { } @computed get items(): CatalogEntity[] { - const allItems = Array.from(iter.flatMap(this.sources.values(), source => source.get())); - - return allItems.filter((entity) => this.categoryRegistry.getCategoryForEntity(entity) !== undefined); + return Array.from( + iter.reduce( + this.filters, + iter.filter, + iter.flatMap(this.sources.values(), source => source.get()), + ) + ); } getById(id: string): T | undefined { @@ -53,7 +65,6 @@ export class CatalogEntityRegistry { if (item) return item as T; - return undefined; } @@ -66,6 +77,17 @@ export class CatalogEntityRegistry { 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);