Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Make the quick access filter more flexible #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions ui/src/stores/kubeExplorer/allExplorerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ import { ResourceWatcher } from "../../common/watchers";
import { useKubeDataStore } from "../kubeDataStore";
import { useKubeWatchStore } from "../kubeWatchStore";

import { filterObjects, filterResources, filterResourceGroups, isApplicableObjectFilterExpr } from "./filter";
import {
type Filter,
filterObjects,
filterResources,
filterResourceGroups,
isApplicableObjectFilterExpr,
parseFilterExpr,
} from "./filter";
import { useRelatedExplorerStore } from "./relatedExplorerStore";

interface TreeNode {
Expand Down Expand Up @@ -46,6 +53,7 @@ export const useAllExplorerStore = defineStore({
} as Tree,
selectors: {} as Record<string, Record<string, KubeSelector>>, // { clusterName => { resource { selector }}
filterExpr: null as string | null,
filters: [] as Filter[],
},
localStorage,
{ mergeDefaults: true },
Expand All @@ -60,14 +68,14 @@ export const useAllExplorerStore = defineStore({

resourceGroups(): (ctx: KubeContext) => KubeResourceGroup[] {
return (ctx) => {
return filterResourceGroups(useKubeDataStore().resourceGroups(ctx), this.filterExpr)
return filterResourceGroups(useKubeDataStore().resourceGroups(ctx), this._persistent.filters)
.sort((a, b) => a.groupVersion.localeCompare(b.groupVersion));
};
},

resources(): (ctx: KubeContext, group: KubeResourceGroup) => KubeResource[] {
return (ctx, group) => {
return filterResources(group.resources || [], this.filterExpr)
return filterResources(group.resources || [], this._persistent.filters)
.sort((a, b) => a.name.localeCompare(b.name));
};
},
Expand All @@ -76,7 +84,7 @@ export const useAllExplorerStore = defineStore({
return (ctx, res) => {
const w = this._watcher(ctx, res);
return w
? filterObjects(w.objects(), this.filterExpr).sort((a, b) => a.name.localeCompare(b.name))
? filterObjects(w.objects(), this._persistent.filters).sort((a, b) => a.name.localeCompare(b.name))
: [];
};
},
Expand Down Expand Up @@ -111,7 +119,7 @@ export const useAllExplorerStore = defineStore({
},

isResourceOpen(state): (ctx: KubeContext, res: KubeResource) => boolean {
return (ctx, res) => (!!this.filterExpr && isApplicableObjectFilterExpr(res, this.filterExpr)) ||
return (ctx, res) => isApplicableObjectFilterExpr(res, this.filterExpr, this._persistent.filters) ||
!!_getOrCreateResourceNode(state._persistent.tree, ctx.name, res.groupVersion, res.kind).open;
},

Expand Down Expand Up @@ -241,10 +249,12 @@ export const useAllExplorerStore = defineStore({

setFilterExpr(f: string) {
this._persistent.filterExpr = f.trim();
this._persistent.filters = parseFilterExpr(this._persistent.filterExpr);
},

clearFilterExpr() {
this._persistent.filterExpr = null;
this._persistent.filters = [];
},

_initWatcher(ctx: KubeContext, res: KubeResource) {
Expand Down
132 changes: 83 additions & 49 deletions ui/src/stores/kubeExplorer/filter.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,116 @@
import type { KubeObject, KubeResource, KubeResourceGroup } from "../../common/types";

export function filterObjects(objects: KubeObject[], filterExpr: string | null): KubeObject[] {
const [resFilters, nameFilters] = _parseFilterExpr(filterExpr);
if (objects.length === 0 || resFilters.length === 0 || nameFilters.length === 0) {
export interface Filter {
resources: string[];
names: string[];
}

export function parseFilterExpr(filterExpr: string | null): Filter[] {
const filters: Filter[] = [];

for (const subExpr of (filterExpr || "").trim().split(";")) {
console.log("parsing filter expr", subExpr);
const [resFilterExpr, nameFilterExpr] = subExpr.trim().split(/\s+/);

const resFilters = resFilterExpr.toLowerCase().split(",").filter((f) => !!f);
const nameFilters = (nameFilterExpr || "").split(",").filter((f) => !!f);

if (resFilters.length > 0) {
filters.push({
resources: resFilters,
names: nameFilters,
});
}
}

console.log("parsed filters", filters);
return filters;
}

export function filterObjects(objects: KubeObject[], filters: Filter[]): KubeObject[] {
if (objects.length === 0 || filters.every((f) => f.resources.length === 0 || f.names.length === 0)) {
console.log("filtering objects - noop");
return objects;
}

if (_matchingResources([objects[0].resource], resFilters).length === 0) {
return [];
console.log("filtering objects", objects[0].resource.groupVersion + "/" + objects[0].resource.kind);

const matched = new Set<string>();
for (const f of filters) {
console.log("matching object resource filter", f);
if (_matchingResources([objects[0].resource], f.resources).length > 0) {
console.log("matched object resource filter", f.resources);
for (const obj of _matchingObjects(objects, f.names)) {
matched.add(obj.ident);
}
}
}

return _matchingObjects(objects, nameFilters);
return objects.filter((obj) => !!matched.has(obj.ident));
}

export function filterResources(resources: KubeResource[], filterExpr: string | null): KubeResource[] {
if (!filterExpr) {
export function filterResources(resources: KubeResource[], filters: Filter[]): KubeResource[] {
console.log("filtering resources");
if (filters.length === 0) {
return resources;
}

const [resFilters] = _parseFilterExpr(filterExpr);
const resFilters = filters.flatMap((f) => f.resources);
console.log("matched resources", _matchingResources(resources, resFilters));
return _matchingResources(resources, resFilters);
}

export function filterResourceGroups(groups: KubeResourceGroup[], filterExpr: string | null): KubeResourceGroup[] {
if (!filterExpr) {
export function filterResourceGroups(groups: KubeResourceGroup[], filters: Filter[]): KubeResourceGroup[] {
if (filters.length === 0) {
return groups;
}

const [resFilters] = _parseFilterExpr(filterExpr);
const resFilters = filters.flatMap((f) => f.resources);
return groups.filter((group) => {
return _matchingResources(group.resources || [], resFilters).length > 0;
});
}

export function isApplicableObjectFilterExpr(res: KubeResource, filterExpr: string | null): boolean {
export function isApplicableObjectFilterExpr(res: KubeResource, filterExpr: string | null, filters: Filter[]): boolean {
if (!filterExpr) {
return true;
}

const expr = filterExpr.split(/\s+/)[1];
if (!expr) {
return false;
}

if (expr === "*") {
return true;
}

const hasSlash = expr.indexOf("/") !== -1;
if (!res.namespaced && hasSlash) {
return false;
}
if (res.namespaced && !hasSlash) {
return false;
}

const [ns, name] = expr.split("/");
if (ns !== "*" && ns.length < 2) {
return false;
}
for (const f of filters) {
if (_matchingResources([res], f.resources).length === 0) {
continue;
}

if (hasSlash) {
return name === "*" || (name || "").length > 1;
console.log("matched resource filter", f.resources);
for (const expr of f.names) {
if (expr === "*") {
return true;
}

const hasSlash = expr.indexOf("/") !== -1;
if (!res.namespaced && hasSlash) {
continue;
}
if (res.namespaced && !hasSlash) {
continue;
}

const [ns, name] = expr.split("/");
if (ns !== "*" && ns.length < 2) {
continue;
}

if (hasSlash && (name === "*" || (name || "").length > 1)) {
return true;
}

if (!hasSlash && (expr === "*" || (expr || "").length > 1)) {
return true;
}
}
}

return true;
}

function _parseFilterExpr(filterExpr: string | null): [string[], string[]] {
const [resFilterExpr, nameFilterExpr] = (filterExpr || "").trim().split(/\s+/);

const resFilters = resFilterExpr.toLowerCase().split(",");
const nameFilters = (nameFilterExpr || "").split(",");

return [
resFilters.filter((f) => !!f),
nameFilters.filter((f) => !!f),
];
return false;
}

function _matchingObjects(objects: KubeObject[], filters: string[]): KubeObject[] {
Expand Down
19 changes: 14 additions & 5 deletions ui/src/stores/kubeExplorer/quickExplorerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { ResourceWatcher } from "../../common/watchers";
import { useKubeDataStore } from "../kubeDataStore";
import { useKubeWatchStore } from "../kubeWatchStore";

import { filterObjects, filterResources, isApplicableObjectFilterExpr } from "./filter";
import {
type Filter,
filterObjects,
filterResources,
isApplicableObjectFilterExpr,
parseFilterExpr,
} from "./filter";
import { useRelatedExplorerStore } from "./relatedExplorerStore";

interface TreeNode {
Expand Down Expand Up @@ -92,6 +98,7 @@ export const useQuickExplorerStore = defineStore({
} as Tree,
selectors: {} as Record<string, Record<string, KubeSelector>>, // { clusterName => { resource { selector }}
filterExpr: null as string | null,
filters: [] as Filter[],
},
localStorage,
{ mergeDefaults: true },
Expand All @@ -113,14 +120,14 @@ export const useQuickExplorerStore = defineStore({
groupVersion: quickGroup,
resources: groups.flatMap((group) => (group.resources || []).filter((res) => gvkToQuickGroup[res.groupVersion + "/" + res.kind as keyof typeof gvkToQuickGroup] === quickGroup)),
}))
.filter((group) => filterResources(group.resources, this.filterExpr).length > 0)
.filter((group) => filterResources(group.resources, this._persistent.filters).length > 0)
.sort((a, b) => quickGroupRank[b.groupVersion as keyof typeof quickGroupRank] - quickGroupRank[a.groupVersion as keyof typeof quickGroupRank]);
};
},

resources(): (ctx: KubeContext, group: KubeResourceGroup) => KubeResource[] {
return (ctx, group) => {
return filterResources((group.resources || []), this.filterExpr)
return filterResources((group.resources || []), this._persistent.filters)
.sort((a, b) => a.name.localeCompare(b.name));
};
},
Expand All @@ -129,7 +136,7 @@ export const useQuickExplorerStore = defineStore({
return (ctx, res) => {
const w = this._watcher(ctx, res);
return w
? filterObjects(w.objects(), this.filterExpr).sort((a, b) => a.name.localeCompare(b.name))
? filterObjects(w.objects(), this._persistent.filters).sort((a, b) => a.name.localeCompare(b.name))
: [];
};
},
Expand Down Expand Up @@ -164,7 +171,7 @@ export const useQuickExplorerStore = defineStore({
},

isResourceOpen(state): (ctx: KubeContext, res: KubeResource) => boolean {
return (ctx, res) => (!!this.filterExpr && isApplicableObjectFilterExpr(res, this.filterExpr)) ||
return (ctx, res) => isApplicableObjectFilterExpr(res, this.filterExpr, this._persistent.filters) ||
!!_getOrCreateResourceNode(state._persistent.tree, ctx.name, res.groupVersion, res.kind).open;
},

Expand Down Expand Up @@ -294,10 +301,12 @@ export const useQuickExplorerStore = defineStore({

setFilterExpr(f: string) {
this._persistent.filterExpr = f.trim();
this._persistent.filters = parseFilterExpr(this._persistent.filterExpr);
},

clearFilterExpr() {
this._persistent.filterExpr = null;
this._persistent.filters = [];
},

_initWatcher(ctx: KubeContext, res: KubeResource) {
Expand Down
20 changes: 15 additions & 5 deletions ui/src/stores/kubeExplorer/relatedExplorerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ import { RelatedWatcher } from "../../common/watchers";
import { useKubeDataStore } from "../kubeDataStore";
import { useKubeWatchStore } from "../kubeWatchStore";

import { filterObjects, filterResources, filterResourceGroups, isApplicableObjectFilterExpr } from "./filter";
import {
type Filter,
filterObjects,
filterResources,
filterResourceGroups,
isApplicableObjectFilterExpr,
parseFilterExpr,
} from "./filter";

interface TreeNode {
open?: boolean;
Expand Down Expand Up @@ -42,6 +49,7 @@ export const useRelatedExplorerStore = defineStore({
} as Tree,

filterExpr: null as string | null,
filters: [] as Filter[],
}),

getters: {
Expand All @@ -61,7 +69,7 @@ export const useRelatedExplorerStore = defineStore({
return [];
}

return filterResourceGroups(useKubeDataStore().resourceGroups(ctx), this.filterExpr)
return filterResourceGroups(useKubeDataStore().resourceGroups(ctx), this.filters)
.filter((group) => (group.resources || []).some((res) => this.objects(ctx, res).length > 0))
.sort((a, b) => a.groupVersion.localeCompare(b.groupVersion));
};
Expand All @@ -73,7 +81,7 @@ export const useRelatedExplorerStore = defineStore({
return [];
}

return filterResources(group.resources || [], this.filterExpr)
return filterResources(group.resources || [], this.filters)
.filter((res) => this.objects(ctx, res).length > 0)
.sort((a, b) => a.name.localeCompare(b.name));
};
Expand All @@ -90,7 +98,7 @@ export const useRelatedExplorerStore = defineStore({
return [];
}

return filterObjects(w.objects(), this.filterExpr)
return filterObjects(w.objects(), this.filters)
.filter((obj) => obj.resource.groupVersion === res.groupVersion && obj.resource.kind === res.kind)
.sort((a, b) => a.name.localeCompare(b.name));
};
Expand All @@ -114,7 +122,7 @@ export const useRelatedExplorerStore = defineStore({
},

isResourceOpen(state): (ctx: KubeContext, res: KubeResource) => boolean {
return (ctx, res) => (!!this.filterExpr && isApplicableObjectFilterExpr(res, this.filterExpr)) ||
return (ctx, res) => isApplicableObjectFilterExpr(res, this.filterExpr, this.filters) ||
!!_getOrCreateResourceNode(state.tree, ctx.name, res.groupVersion, res.kind).open;
},

Expand Down Expand Up @@ -239,10 +247,12 @@ export const useRelatedExplorerStore = defineStore({

setFilterExpr(f: string) {
this.filterExpr = f.trim();
this.filters = parseFilterExpr(this.filterExpr);
},

clearFilterExpr() {
this.filterExpr = null;
this.filters = [];
},

_ensureWatcher(ctx: KubeContext) {
Expand Down
Loading