From 3fd68bfcc1827833b4c7240885b6fc242349b06f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 3 Mar 2023 16:39:56 -0500 Subject: [PATCH] Move everything else related to selecting namespaces into context - Use only the context for the namespace-select-filter-model Signed-off-by: Sebastian Malton --- .../cluster-frame-context.ts | 19 ++++++- .../for-namespaced-resources.injectable.ts | 50 ++++++++++++++++--- .../filter-by-namespace.injectable.ts | 6 +-- ...amespace-select-filter-model.injectable.ts | 2 - .../namespace-select-filter-model.tsx | 13 +++-- .../namespace-select-filter.test.tsx | 12 +++-- 6 files changed, 78 insertions(+), 24 deletions(-) diff --git a/packages/core/src/renderer/cluster-frame-context/cluster-frame-context.ts b/packages/core/src/renderer/cluster-frame-context/cluster-frame-context.ts index e23bac5fd4..740be14e8e 100755 --- a/packages/core/src/renderer/cluster-frame-context/cluster-frame-context.ts +++ b/packages/core/src/renderer/cluster-frame-context/cluster-frame-context.ts @@ -7,10 +7,25 @@ * This type is used for KubeObjectStores */ export interface ClusterContext { - readonly allNamespaces: string[]; // available / allowed namespaces from cluster.ts - readonly contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) + /** + * A computed getter for all the namespaces that this cluster knows about + */ + readonly allNamespaces: string[]; + + /** + * The computed getter of namespaces that are currently selected + */ + readonly contextNamespaces: string[]; + readonly hasSelectedAll: boolean; isLoadingAll(namespaces: string[]): boolean; isGlobalWatchEnabled(): boolean; } + +export interface NamespaceScopedClusterContext extends ClusterContext { + selectAllNamespaces(): void; + toggleNamespace(namespace: string): void; + selectNamespace(namespace: string): void; + deselectNamespace(namespace: string): void; +} diff --git a/packages/core/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts b/packages/core/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts index 1eb85b3a43..bf8ff3bfc9 100644 --- a/packages/core/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts +++ b/packages/core/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts @@ -3,18 +3,21 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { ClusterContext } from "./cluster-frame-context"; +import type { NamespaceScopedClusterContext } from "./cluster-frame-context"; import namespaceStoreInjectable from "../components/+namespaces/store.injectable"; import hostedClusterInjectable from "./hosted-cluster.injectable"; import assert from "assert"; import { computed } from "mobx"; +import selectedNamespaceStorageInjectable from "../components/+namespaces/namespace-storage.injectable"; +import { toggle } from "../utils"; const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ id: "cluster-frame-context-for-namespaced-resources", - instantiate: (di): ClusterContext => { + instantiate: (di): NamespaceScopedClusterContext => { const cluster = di.inject(hostedClusterInjectable); const namespaceStore = di.inject(namespaceStoreInjectable); + const selectedNamespaceStorage = di.inject(selectedNamespaceStorageInjectable); assert(cluster, "This can only be injected within a cluster frame"); @@ -25,20 +28,37 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ } if (namespaceStore.items.length > 0) { - // namespaces from kubernetes api + // namespaces from kubernetes api return namespaceStore.items.map((namespace) => namespace.getName()); } // fallback to cluster resolved namespaces because we could not load list return cluster.allowedNamespaces.slice(); }); - const contextNamespaces = computed(() => namespaceStore.contextNamespaces); + const contextNamespaces = computed(() => { + const storedState = selectedNamespaceStorage.get(); + + if (!storedState || storedState.length === 0) { + return allNamespaces.get(); + } + + const state = new Set(storedState); + const currentlyKnownNamespaces = new Set(allNamespaces.get()); + + for (const namespace of storedState) { + if (!currentlyKnownNamespaces.has(namespace)) { + state.delete(namespace); + } + } + + return [...state]; + }); const hasSelectedAll = computed(() => { const namespaces = new Set(contextNamespaces.get()); return allNamespaces.get().length > 1 - && cluster.accessibleNamespaces.length === 0 - && allNamespaces.get().every(ns => namespaces.has(ns)); + && cluster.accessibleNamespaces.length === 0 + && allNamespaces.get().every(ns => namespaces.has(ns)); }); return { @@ -48,6 +68,24 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ && allNamespaces.get().every(ns => namespaces.includes(ns)) ), isGlobalWatchEnabled: () => cluster.isGlobalWatchEnabled, + selectAllNamespaces: () => { + selectedNamespaceStorage.set([]); + }, + selectNamespace: (namespace) => { + selectedNamespaceStorage.set([namespace]); + }, + toggleNamespace: (namespace) => { + const nextState = new Set(contextNamespaces.get()); + + toggle(nextState, namespace); + selectedNamespaceStorage.set([...nextState]); + }, + deselectNamespace: (namespace) => { + const nextState = new Set(contextNamespaces.get()); + + nextState.delete(namespace); + selectedNamespaceStorage.set([...nextState]); + }, get allNamespaces() { return allNamespaces.get(); }, diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts index fff1c37202..f099b109af 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts +++ b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts @@ -3,16 +3,16 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import namespaceStoreInjectable from "../store.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; export type FilterByNamespace = (namespace: string) => void; const filterByNamespaceInjectable = getInjectable({ id: "filter-by-namespace", instantiate: (di): FilterByNamespace => { - const namespaceStore = di.inject(namespaceStoreInjectable); + const context = di.inject(clusterFrameContextForNamespacedResourcesInjectable); - return (namespace) => namespaceStore.selectSingle(namespace); + return (namespace) => context.selectNamespace(namespace); }, }); diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts index d84292410b..61bdcf0118 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts +++ b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts @@ -4,7 +4,6 @@ */ import { namespaceSelectFilterModelFor } from "./namespace-select-filter-model"; import { getInjectable } from "@ogre-tools/injectable"; -import namespaceStoreInjectable from "../store.injectable"; import isMultiSelectionKeyInjectable from "./is-selection-key.injectable"; import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; @@ -12,7 +11,6 @@ const namespaceSelectFilterModelInjectable = getInjectable({ id: "namespace-select-filter-model", instantiate: (di) => namespaceSelectFilterModelFor({ - namespaceStore: di.inject(namespaceStoreInjectable), isMultiSelectionKey: di.inject(isMultiSelectionKeyInjectable), context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }), diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx index b9396f56a0..6819fc1361 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx +++ b/packages/core/src/renderer/components/+namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx @@ -5,16 +5,15 @@ import React from "react"; import type { IComputedValue } from "mobx"; import { observable, action, computed, comparer } from "mobx"; -import type { NamespaceStore } from "../store"; import type { ActionMeta, MultiValue } from "react-select"; import { Icon } from "../../icon"; import type { SelectOption } from "../../select"; import { observableCrate } from "../../../utils"; import type { IsMultiSelectionKey } from "./is-selection-key.injectable"; +import type { NamespaceScopedClusterContext } from "../../../cluster-frame-context/cluster-frame-context"; interface Dependencies { context: NamespaceScopedClusterContext; - namespaceStore: NamespaceStore; isMultiSelectionKey: IsMultiSelectionKey; } @@ -45,7 +44,7 @@ enum SelectMenuState { } export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel { - const { isMultiSelectionKey, namespaceStore, context } = dependencies; + const { isMultiSelectionKey, context } = dependencies; let didToggle = false; let isMultiSelection = false; @@ -111,7 +110,7 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names onChange: (_, action) => { switch (action.action) { case "clear": - namespaceStore.selectAll(); + context.selectAllNamespaces(); break; case "deselect-option": case "select-option": @@ -119,11 +118,11 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names didToggle = true; if (action.option.value === selectAllNamespaces) { - namespaceStore.selectAll(); + context.selectAllNamespaces(); } else if (isMultiSelection) { - namespaceStore.toggleSingle(action.option.value); + context.toggleNamespace(action.option.value); } else { - namespaceStore.selectSingle(action.option.value); + context.selectNamespace(action.option.value); } } break; diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx index 14751c13dd..064c6b6590 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx @@ -15,6 +15,8 @@ import type { Fetch } from "../../../common/fetch/fetch.injectable"; import fetchInjectable from "../../../common/fetch/fetch.injectable"; import { Namespace } from "../../../common/k8s-api/endpoints"; import { createMockResponseFromString } from "../../../test-utils/mock-responses"; +import type { NamespaceScopedClusterContext } from "../../cluster-frame-context/cluster-frame-context"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import createClusterInjectable from "../../cluster/create-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; @@ -43,6 +45,7 @@ function createNamespace(name: string): Namespace { describe("", () => { let di: DiContainer; let namespaceStore: NamespaceStore; + let context: NamespaceScopedClusterContext; let fetchMock: AsyncFnMock; let result: RenderResult; let cleanup: Disposer; @@ -69,6 +72,7 @@ describe("", () => { })); namespaceStore = di.inject(namespaceStoreInjectable); + context = di.inject(clusterFrameContextForNamespacedResourcesInjectable); const subscribeStores = di.inject(subscribeStoresInjectable); @@ -138,7 +142,7 @@ describe("", () => { }); it("has only 'test-2' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-2"]); + expect(context.contextNamespaces).toEqual(["test-2"]); }); it("closes menu", () => { @@ -172,7 +176,7 @@ describe("", () => { }); it("has only 'test-1' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-1"]); + expect(context.contextNamespaces).toEqual(["test-1"]); }); it("closes menu", () => { @@ -197,7 +201,7 @@ describe("", () => { }); it("has both 'test-1' and 'test-3' as selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); + expect(new Set(context.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); }); it("keeps menu open", () => { @@ -214,7 +218,7 @@ describe("", () => { }); it("has all of 'test-1', 'test-3', and 'test-13' selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); + expect(new Set(context.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); }); it("'test-13' is not sorted to the top of the list", () => {