diff --git a/src/common/utils/__tests__/splitArray.test.ts b/src/common/utils/__tests__/splitArray.test.ts index 1e1589fee2..8228702ea9 100644 --- a/src/common/utils/__tests__/splitArray.test.ts +++ b/src/common/utils/__tests__/splitArray.test.ts @@ -1,31 +1,61 @@ -import { splitArray } from "../splitArray"; +import { bifurcateArray, splitArray } from "../splitArray"; describe("split array on element tests", () => { - test("empty array", () => { + it("empty array", () => { expect(splitArray([], 10)).toStrictEqual([[], [], false]); }); - test("one element, not in array", () => { + it("one element, not in array", () => { expect(splitArray([1], 10)).toStrictEqual([[1], [], false]); }); - test("ten elements, not in array", () => { + it("ten elements, not in array", () => { expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [], false]); }); - test("one elements, in array", () => { + it("one elements, in array", () => { expect(splitArray([1], 1)).toStrictEqual([[], [], true]); }); - test("ten elements, in front array", () => { + it("ten elements, in front array", () => { expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0)).toStrictEqual([[], [1, 2, 3, 4, 5, 6, 7, 8, 9], true]); }); - test("ten elements, in middle array", () => { + it("ten elements, in middle array", () => { expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4)).toStrictEqual([[0, 1, 2, 3], [5, 6, 7, 8, 9], true]); }); - test("ten elements, in end array", () => { + it("ten elements, in end array", () => { expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8], [], true]); }); }); + +describe("bifurcateArray", () => { + it("should return tuple of empty arrays from empty array", () => { + const [left, right] = bifurcateArray([], () => true); + + expect(left).toStrictEqual([]); + expect(right).toStrictEqual([]); + }); + + it("should return all true condition returning items in the right array", () => { + const [left, right] = bifurcateArray([1, 2, 3], () => true); + + expect(left).toStrictEqual([]); + expect(right).toStrictEqual([1, 2, 3]); + }); + + it("should return all false condition returning items in the right array", () => { + const [left, right] = bifurcateArray([1, 2, 3], () => false); + + expect(left).toStrictEqual([1, 2, 3]); + expect(right).toStrictEqual([]); + }); + + it("should split array as specified", () => { + const [left, right] = bifurcateArray([1, 2, 3], (i) => Boolean(i % 2)); + + expect(left).toStrictEqual([2]); + expect(right).toStrictEqual([1, 3]); + }); +}); diff --git a/src/common/utils/splitArray.ts b/src/common/utils/splitArray.ts index 7be367ebe6..7d623a55bc 100644 --- a/src/common/utils/splitArray.ts +++ b/src/common/utils/splitArray.ts @@ -1,4 +1,3 @@ -// Moved from dashboard/client/utils/arrays.ts /** * This function splits an array into two sub arrays on the first instance of * element (from the left). If the array does not contain the element. The @@ -19,3 +18,20 @@ export function splitArray(array: T[], element: T): [T[], T[], boolean] { return [array.slice(0, index), array.slice(index + 1, array.length), true]; } + +/** + * Splits an array into two parts based on the outcome of `condition`. If `true` + * the value will be returned as part of the right array. If `false` then part of + * the left array. + * @param src the full array to bifurcate + * @param condition the function to determine which set each is in + */ +export function bifurcateArray(src: T[], condition: (item: T) => boolean): [falses: T[], trues: T[]] { + const res: [T[], T[]] = [[], []]; + + for (const item of src) { + res[+condition(item)].push(item); + } + + return res; +} diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 41fc63cf4d..23f6f2c508 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -50,6 +50,7 @@ import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale import { CommandContainer } from "./command-palette/command-container"; import { KubeObjectStore } from "../kube-object.store"; import { clusterContext } from "./context"; +import { namespaceStore } from "./+namespaces/namespace.store"; @observer export class App extends React.Component { @@ -84,7 +85,7 @@ export class App extends React.Component { componentDidMount() { disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([podsStore, nodesStore, eventStore], { + kubeWatchApi.subscribeStores([podsStore, nodesStore, eventStore, namespaceStore], { preload: true, }) ]); diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index 35f052d03f..445caf4cc6 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -1,7 +1,7 @@ import type { ClusterContext } from "./components/context"; import { action, computed, observable, reaction, when } from "mobx"; -import { autobind, noop, rejectPromiseBy } from "./utils"; +import { autobind, bifurcateArray, noop, rejectPromiseBy } from "./utils"; import { KubeObject, KubeStatus } from "./api/kube-object"; import { IKubeWatchEvent } from "./api/kube-watch-api"; import { ItemStore } from "./item.store"; @@ -281,21 +281,36 @@ export abstract class KubeObjectStore extends ItemSt subscribe(apis = this.getSubscribeApis()) { const abortController = new AbortController(); + const [clusterScopedApis, namespaceScopedApis] = bifurcateArray(apis, api => api.isNamespaced); - // This waits for the context and namespaces to be ready or fails fast if the disposer is called - Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])]) - .then(() => { - if (this.context.cluster.isGlobalWatchEnabled && this.loadedNamespaces.length === 0) { - apis.forEach(api => this.watchNamespace(api, "", abortController)); - } else { - apis.forEach(api => { - this.loadedNamespaces.forEach((namespace) => { - this.watchNamespace(api, namespace, abortController); - }); - }); - } - }) - .catch(noop); // ignore DOMExceptions + for (const api of namespaceScopedApis) { + const store = apiManager.getStore(api); + + // This waits for the context and namespaces to be ready or fails fast if the disposer is called + Promise.race([rejectPromiseBy(abortController.signal), Promise.all([store.contextReady, store.namespacesReady])]) + .then(() => { + if ( + store.context.cluster.isGlobalWatchEnabled + && store.loadedNamespaces.length === 0 + ) { + return store.watchNamespace(api, "", abortController); + } + + for (const namespace of this.loadedNamespaces) { + store.watchNamespace(api, namespace, abortController); + } + }) + .catch(noop); // ignore DOMExceptions + } + + for (const api of clusterScopedApis) { + /** + * if the api is cluster scoped then we will never assign to `loadedNamespaces` + * and thus `store.namespacesReady` will never resolve. Futhermore, we don't care + * about watching namespaces. + */ + apiManager.getStore(api).watchNamespace(api, "", abortController); + } return () => { abortController.abort();