diff --git a/packages/core/src/common/cluster/cluster.ts b/packages/core/src/common/cluster/cluster.ts index 6829a6744a..329d6e2df7 100644 --- a/packages/core/src/common/cluster/cluster.ts +++ b/packages/core/src/common/cluster/cluster.ts @@ -498,6 +498,7 @@ export class Cluster implements ClusterModel { this.allowedResources.replace(await this.getAllowedResources(requestNamespaceListPermissions)); this.ready = this.knownResources.length > 0; + this.dependencies.logger.debug(`[CLUSTER]: refreshed accessibility data`, this.getState()); } /** diff --git a/packages/core/src/common/k8s-api/selected-filter-namespaces.injectable.ts b/packages/core/src/common/k8s-api/selected-filter-namespaces.injectable.ts index 6c70a665a4..41f664b895 100644 --- a/packages/core/src/common/k8s-api/selected-filter-namespaces.injectable.ts +++ b/packages/core/src/common/k8s-api/selected-filter-namespaces.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import namespaceStoreInjectable from "../../renderer/components/+namespaces/store.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; import { storesAndApisCanBeCreatedInjectionToken } from "./stores-apis-can-be-created.token"; const selectedFilterNamespacesInjectable = getInjectable({ @@ -15,9 +15,9 @@ const selectedFilterNamespacesInjectable = getInjectable({ return computed(() => []); } - const store = di.inject(namespaceStoreInjectable); + const context = di.inject(clusterFrameContextForNamespacedResourcesInjectable); - return computed(() => [...store.contextNamespaces]); + return computed(() => [...context.contextNamespaces]); }, }); diff --git a/packages/core/src/features/helm-charts/installing-chart/installing-helm-chart-from-new-tab.test.ts b/packages/core/src/features/helm-charts/installing-chart/installing-helm-chart-from-new-tab.test.ts index f7aef8dd88..b63d08f904 100644 --- a/packages/core/src/features/helm-charts/installing-chart/installing-helm-chart-from-new-tab.test.ts +++ b/packages/core/src/features/helm-charts/installing-chart/installing-helm-chart-from-new-tab.test.ts @@ -28,6 +28,9 @@ import requestHelmChartReadmeInjectable from "../../../common/k8s-api/endpoints/ import requestHelmChartValuesInjectable from "../../../common/k8s-api/endpoints/helm-charts.api/request-values.injectable"; import type { RequestDetailedHelmRelease } from "../../../renderer/components/+helm-releases/release-details/release-details-model/request-detailed-helm-release.injectable"; import requestDetailedHelmReleaseInjectable from "../../../renderer/components/+helm-releases/release-details/release-details-model/request-detailed-helm-release.injectable"; +import type { RequestHelmReleases } from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable"; +import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable"; +import { flushPromises } from "../../../common/test-utils/flush-promises"; describe("installing helm chart from new tab", () => { let builder: ApplicationBuilder; @@ -37,6 +40,7 @@ describe("installing helm chart from new tab", () => { let requestHelmChartReadmeMock: AsyncFnMock; let requestHelmChartValuesMock: AsyncFnMock; let requestCreateHelmReleaseMock: AsyncFnMock; + let requestHelmReleasesMock: AsyncFnMock; beforeEach(() => { builder = getApplicationBuilder(); @@ -49,6 +53,7 @@ describe("installing helm chart from new tab", () => { requestHelmChartReadmeMock = asyncFn(); requestHelmChartValuesMock = asyncFn(); requestCreateHelmReleaseMock = asyncFn(); + requestHelmReleasesMock = asyncFn(); builder.beforeWindowStart((windowDi) => { windowDi.override(directoryForLensLocalStorageInjectable, () => "/some-directory-for-lens-local-storage"); @@ -58,6 +63,7 @@ describe("installing helm chart from new tab", () => { windowDi.override(requestHelmChartReadmeInjectable, () => requestHelmChartReadmeMock); windowDi.override(requestHelmChartValuesInjectable, () => requestHelmChartValuesMock); windowDi.override(requestCreateHelmReleaseInjectable, () => requestCreateHelmReleaseMock); + windowDi.override(requestHelmReleasesInjectable, () => requestHelmReleasesMock); windowDi.override(getRandomInstallChartTabIdInjectable, () => jest @@ -386,12 +392,15 @@ describe("installing helm chart from new tab", () => { }); describe("when selected to see the installed release", () => { - beforeEach(() => { + beforeEach(async () => { const releaseButton = rendered.getByTestId( "show-release-some-release-for-some-first-tab-id", ); fireEvent.click(releaseButton); + + await flushPromises(); + await requestHelmReleasesMock.resolve([]); }); it("renders", () => { diff --git a/packages/core/src/features/helm-releases/showing-details-for-helm-release.test.ts b/packages/core/src/features/helm-releases/showing-details-for-helm-release.test.ts index ca947358ad..88ec114eb0 100644 --- a/packages/core/src/features/helm-releases/showing-details-for-helm-release.test.ts +++ b/packages/core/src/features/helm-releases/showing-details-for-helm-release.test.ts @@ -78,9 +78,12 @@ describe("showing details for helm release", () => { }); builder.namespaces.add("some-namespace"); - builder.namespaces.select("some-namespace"); builder.namespaces.add("some-namespace"); - builder.namespaces.select("some-other-namespace"); + + builder.afterWindowStart(() => { + builder.namespaces.select("some-namespace"); + builder.namespaces.select("some-other-namespace"); + }); }); describe("given application is started", () => { @@ -106,10 +109,9 @@ describe("showing details for helm release", () => { }); it("calls for releases for each selected namespace", () => { - expect(requestHelmReleasesMock.mock.calls).toEqual([ - ["some-namespace"], - ["some-other-namespace"], - ]); + expect(requestHelmReleasesMock).toBeCalledTimes(2); + expect(requestHelmReleasesMock).toBeCalledWith("some-namespace"); + expect(requestHelmReleasesMock).toBeCalledWith("some-other-namespace"); }); it("shows spinner", () => { diff --git a/packages/core/src/features/namespace-filtering/renderer/storage.injectable.ts b/packages/core/src/features/namespace-filtering/renderer/storage.injectable.ts new file mode 100644 index 0000000000..d69111a35e --- /dev/null +++ b/packages/core/src/features/namespace-filtering/renderer/storage.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import assert from "assert"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import createStorageInjectable from "../../../renderer/utils/create-storage/create-storage.injectable"; + +const selectedNamespacesStorageInjectable = getInjectable({ + id: "selected-namespaces-storage", + instantiate: (di) => { + const createStorage = di.inject(createStorageInjectable); + const cluster = di.inject(hostedClusterInjectable); + + assert(cluster, "selectedNamespacesStorage is only available in certain environments"); + + const defaultSelectedNamespaces = cluster.allowedNamespaces.includes("default") + ? ["default"] + : cluster.allowedNamespaces.slice(0, 1); + + return createStorage("selected_namespaces", defaultSelectedNamespaces); + }, +}); + +export default selectedNamespacesStorageInjectable; 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..f4795f8523 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 @@ -8,6 +8,7 @@ import namespaceStoreInjectable from "../components/+namespaces/store.injectable import hostedClusterInjectable from "./hosted-cluster.injectable"; import assert from "assert"; import { computed } from "mobx"; +import selectedNamespacesStorageInjectable from "../../features/namespace-filtering/renderer/storage.injectable"; const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ id: "cluster-frame-context-for-namespaced-resources", @@ -15,6 +16,7 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ instantiate: (di): ClusterContext => { const cluster = di.inject(hostedClusterInjectable); const namespaceStore = di.inject(namespaceStoreInjectable); + const selectedNamespacesStorage = di.inject(selectedNamespacesStorageInjectable); assert(cluster, "This can only be injected within a cluster frame"); @@ -32,13 +34,19 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ // fallback to cluster resolved namespaces because we could not load list return cluster.allowedNamespaces.slice(); }); - const contextNamespaces = computed(() => namespaceStore.contextNamespaces); + const contextNamespaces = computed(() => { + const selectedNamespaces = selectedNamespacesStorage.get(); + + return selectedNamespaces.length > 0 + ? selectedNamespaces + : allNamespaces.get(); + }); 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 { diff --git a/packages/core/src/renderer/components/+helm-releases/releases.injectable.ts b/packages/core/src/renderer/components/+helm-releases/releases.injectable.ts index 7f4d2d286b..ae90a67e18 100644 --- a/packages/core/src/renderer/components/+helm-releases/releases.injectable.ts +++ b/packages/core/src/renderer/components/+helm-releases/releases.injectable.ts @@ -22,13 +22,11 @@ const releasesInjectable = getInjectable({ getValueFromObservedPromise: async () => { void releaseSecrets.get(); - const releaseArrays = await (clusterContext.hasSelectedAll - ? requestHelmReleases() - : Promise.all( - clusterContext.contextNamespaces.map((namespace) => - requestHelmReleases(namespace), - ), - )); + const releaseArrays = await ( + clusterContext.hasSelectedAll + ? requestHelmReleases() + : Promise.all(clusterContext.contextNamespaces.map((namespace) => requestHelmReleases(namespace))) + ); return releaseArrays.flat().map(toHelmRelease); }, diff --git a/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap b/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap index 335cc1d159..a03fe634d7 100644 --- a/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap +++ b/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap @@ -1412,7 +1412,7 @@ exports[` once the subscribe resolves when clicked when - test-10 + test-2 @@ -1436,7 +1436,7 @@ exports[` once the subscribe resolves when clicked when - test-11 + test-10 @@ -1460,7 +1460,7 @@ exports[` once the subscribe resolves when clicked when - test-12 + test-11 @@ -1484,7 +1484,7 @@ exports[` once the subscribe resolves when clicked when - test-13 + test-12 @@ -1508,7 +1508,7 @@ exports[` once the subscribe resolves when clicked when - test-2 + test-13 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 5578767cf1..d84292410b 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 @@ -6,6 +6,7 @@ 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"; const namespaceSelectFilterModelInjectable = getInjectable({ id: "namespace-select-filter-model", @@ -13,6 +14,7 @@ const namespaceSelectFilterModelInjectable = getInjectable({ 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 a3d850cdcd..7f99db46f3 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 @@ -11,8 +11,10 @@ import { Icon } from "../../icon"; import type { SelectOption } from "../../select"; import { observableCrate } from "../../../utils"; import type { IsMultiSelectionKey } from "./is-selection-key.injectable"; +import type { ClusterContext } from "../../../cluster-frame-context/cluster-frame-context"; interface Dependencies { + context: ClusterContext; namespaceStore: NamespaceStore; isMultiSelectionKey: IsMultiSelectionKey; } @@ -44,7 +46,7 @@ enum SelectMenuState { } export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel { - const { isMultiSelectionKey, namespaceStore } = dependencies; + const { isMultiSelectionKey, namespaceStore, context } = dependencies; let didToggle = false; let isMultiSelection = false; @@ -56,7 +58,7 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names didToggle = false; }, }]); - const selectedNames = computed(() => new Set(namespaceStore.contextNamespaces), { + const selectedNames = computed(() => new Set(context.contextNamespaces), { equals: comparer.structural, }); const optionsSortingSelected = observable.set(selectedNames.get()); @@ -78,9 +80,8 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names label: "All Namespaces", id: "all-namespaces", }, - ...namespaceStore - .items - .map(ns => ns.getName()) + ...context + .allNamespaces .sort(sortNamespacesByIfTheyHaveBeenSelected) .map(namespace => ({ value: namespace, diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select.tsx b/packages/core/src/renderer/components/+namespaces/namespace-select.tsx index 6c50f3f921..baf2ba2d92 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select.tsx +++ b/packages/core/src/renderer/components/+namespaces/namespace-select.tsx @@ -12,9 +12,9 @@ import type { SelectProps } from "../select"; import { Select } from "../select"; import { cssNames } from "../../utils"; import { Icon } from "../icon"; -import type { NamespaceStore } from "./store"; import { withInjectables } from "@ogre-tools/injectable-react"; -import namespaceStoreInjectable from "./store.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; export type NamespaceSelectSort = (left: string, right: string) => number; @@ -25,12 +25,12 @@ export interface NamespaceSelectProps extends Omit { - const baseOptions = namespaceStore.items.map(ns => ns.getName()); + const baseOptions = context.allNamespaces; if (sort) { baseOptions.sort(sort); @@ -44,16 +44,16 @@ function getOptions(namespaceStore: NamespaceStore, sort: NamespaceSelectSort | } const NonInjectedNamespaceSelect = observer(({ - namespaceStore, + context, showIcons, formatOptionLabel, sort, className, ...selectProps }: Dependencies & NamespaceSelectProps) => { - const [baseOptions, setBaseOptions] = useState(getOptions(namespaceStore, sort)); + const [baseOptions, setBaseOptions] = useState(getOptions(context, sort)); - useEffect(() => setBaseOptions(getOptions(namespaceStore, sort)), [sort]); + useEffect(() => setBaseOptions(getOptions(context, sort)), [sort]); return (