Support filtering catalog entities (#3647)

* Support filtering catalog entities

- This allows extensions and OpenLens to restrict which of the entities
  the sources have provied are "visible" to the rest of Lens

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* switch to filtering only for catalog view

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

* Add test to iter reduce

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

Co-authored-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
Sebastian MaltonandPanu Horsmalahti authored and GitHub committed 2021-08-31 16:00:56 +03:00
1 parent 7981e79cbd
commit 6fdb2f0b58
8 files changed
+192 -27

No files matched your search

@@ -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<CatalogEntityData & CatalogEntityKindData>) {
@@ -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: "test3",
name: "test-link",
source: "test",
labels: {}
},
spec: {
kubeconfigPath: "",
kubeconfigContext: "",
},
status: {
phase: "connected"
}
});
describe("CatalogEntityRegistry", () => {
describe("updateItems", () => {
@@ -250,4 +294,25 @@ 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);
expect(catalog.filteredItems.length).toBe(3);
const d = catalog.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind);
expect(catalog.items.length).toBe(3);
expect(catalog.filteredItems.length).toBe(1);
// Remove filter
d();
expect(catalog.items.length).toBe(3);
expect(catalog.filteredItems.length).toBe(3);
});
});
+46 -10
View File
@@ -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<string, CatalogEntity>([], { deep: true });
protected filters = observable.set<EntityFilter>([], {
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<string, CatalogEntity> {
this.processRawEntities();
@computed get filteredItems() {
return Array.from(
iter.reduce(
this.filters,
iter.filter,
this.items,
)
);
}
return this._entities;
@computed get entities(): Map<string, CatalogEntity> {
return new Map(
this.items.map(entity => [entity.getId(), entity])
);
}
@computed get filteredEntities(): Map<string, CatalogEntity> {
return new Map(
this.filteredItems.map(entity => [entity.getId(), entity])
);
}
getById<T extends CatalogEntity>(id: string) {
return this.entities.get(id) as T;
}
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {
const items = this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind);
getItemsForApiKind<T extends CatalogEntity>(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<T extends CatalogEntity>(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<T extends CatalogEntity>(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));
}
}
@@ -129,10 +129,10 @@ export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntit
@computed get entities() {
if (!this.activeCategory) {
return catalogEntityRegistry.items.map(entity => 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() {
@@ -52,7 +52,7 @@ export class HotbarMenu extends React.Component<Props> {
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) {