Fix allowed resources checks on GKE (#6657)

* Add check for incomplete SelfSubjectRulesReview to fix GKE

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

* Adding namespaced for KubeApiResource

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

* Refactoring of AuthorizationNamespaceReview

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

* Removing dead code

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

* Refactoring ListApiResources

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

* Extract ClusterContext into deps for KubeObjectStore to fix circular import

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

* Fix remaining type errors

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

* Fix crash in frame by consolidating setup into runnables

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

* Fix type errors and remove dead code

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

* Fix core resources not showing up

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

* Fix namespaces not being shown

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

* Simplify ClusterContext to remove something only NamespaceStore needs

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

* Make sure the public API doesn't change

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

* Fix lint

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

* Fixing namespace-select-filter tests

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

* Fix other tests requiring overrides

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

* Fix kludge in cluster-frame tests

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

* Fix remaining test failures

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

* Fix integration test due to incorrect casting

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

* Fix integration test and kube watches not working at all

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

* Fix secret details test

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

* Fix lint

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

* Fix non-ApplicationBuilder tests by adding overrides

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

* Fix crash due to trying to read hostedCluster too soon

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

* Fix crash due to timing issues
- Make injectable phases more explicit for renderer

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

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-12-20 17:20:27 +02:00
1 parent e36f3d2d70
commit 443081493b
196 files changed
+2379 -2146

No files matched your search

@@ -13,6 +13,7 @@ import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-cre
import assert from "assert";
import nodeStoreInjectable from "../../+nodes/store.injectable";
import requestClusterMetricsByNodeNamesInjectable from "../../../../common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable";
const clusterOverviewStoreInjectable = getInjectable({
id: "cluster-overview-store",
@@ -32,6 +33,7 @@ const clusterOverviewStoreInjectable = getInjectable({
),
nodeStore: di.inject(nodeStoreInjectable),
requestClusterMetricsByNodeNames: di.inject(requestClusterMetricsByNodeNamesInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, clusterApi);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -4,6 +4,7 @@
*/
import { action, observable, reaction, when, makeObservable } from "mobx";
import type { KubeObjectStoreDependencies } from "../../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
import type { Cluster, ClusterApi } from "../../../../common/k8s-api/endpoints";
import type { StorageLayer } from "../../../utils";
@@ -28,7 +29,7 @@ export interface ClusterOverviewStorageState {
metricNodeRole: MetricNodeRole;
}
interface ClusterOverviewStoreDependencies {
interface ClusterOverviewStoreDependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer<ClusterOverviewStorageState>;
readonly nodeStore: NodeStore;
requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames;
@@ -58,7 +59,7 @@ export class ClusterOverviewStore extends KubeObjectStore<Cluster, ClusterApi> i
}
constructor(protected readonly dependencies: ClusterOverviewStoreDependencies, api: ClusterApi) {
super(api);
super(dependencies, api);
makeObservable(this);
autoBind(this);
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import horizontalPodAutoscalerApiInjectable from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { HorizontalPodAutoscalerStore } from "./store";
@@ -16,7 +17,9 @@ const horizontalPodAutoscalerStoreInjectable = getInjectable({
const api = di.inject(horizontalPodAutoscalerApiInjectable);
return new HorizontalPodAutoscalerStore(api);
return new HorizontalPodAutoscalerStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import leaseApiInjectable from "../../../common/k8s-api/endpoints/lease.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { LeaseStore } from "./store";
@@ -16,7 +17,9 @@ const leaseStoreInjectable = getInjectable({
const api = di.inject(leaseApiInjectable);
return new LeaseStore(api);
return new LeaseStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import limitRangeApiInjectable from "../../../common/k8s-api/endpoints/limit-range.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { LimitRangeStore } from "./store";
@@ -16,7 +17,9 @@ const limitRangeStoreInjectable = getInjectable({
const api = di.inject(limitRangeApiInjectable);
return new LimitRangeStore(api);
return new LimitRangeStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import configMapApiInjectable from "../../../common/k8s-api/endpoints/config-map.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { ConfigMapStore } from "./store";
@@ -16,7 +17,9 @@ const configMapStoreInjectable = getInjectable({
const api = di.inject(configMapApiInjectable);
return new ConfigMapStore(api);
return new ConfigMapStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import podDisruptionBudgetApiInjectable from "../../../common/k8s-api/endpoints/pod-disruption-budget.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PodDisruptionBudgetStore } from "./store";
@@ -16,7 +17,9 @@ const podDisruptionBudgetStoreInjectable = getInjectable({
const api = di.inject(podDisruptionBudgetApiInjectable);
return new PodDisruptionBudgetStore(api);
return new PodDisruptionBudgetStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import priorityClassApiInjectable from "../../../common/k8s-api/endpoints/priority-class.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PriorityClassStore } from "./store";
@@ -16,7 +17,9 @@ const priorityClassStoreInjectable = getInjectable({
const api = di.inject(priorityClassApiInjectable);
return new PriorityClassStore(api);
return new PriorityClassStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import resourceQuotaApiInjectable from "../../../common/k8s-api/endpoints/resource-quota.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { ResourceQuotaStore } from "./store";
@@ -16,7 +17,9 @@ const resourceQuotaStoreInjectable = getInjectable({
const api = di.inject(resourceQuotaApiInjectable);
return new ResourceQuotaStore(api);
return new ResourceQuotaStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import runtimeClassApiInjectable from "../../../common/k8s-api/endpoints/runtime-class.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { RuntimeClassStore } from "./store";
@@ -16,7 +17,9 @@ const runtimeClassStoreInjectable = getInjectable({
const api = di.inject(runtimeClassApiInjectable);
return new RuntimeClassStore(api);
return new RuntimeClassStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -9,6 +9,10 @@ import { Secret, SecretType } from "../../../../common/k8s-api/endpoints";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import { renderFor } from "../../test-utils/renderFor";
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../../cluster/create-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
jest.mock("../../kube-object-meta/kube-object-meta", () => ({
KubeObjectMeta: () => null,
@@ -19,8 +23,20 @@ describe("SecretDetails tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const render = renderFor(di);
di.override(directoryForUserDataInjectable, () => "/some-user-data");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
const secret = new Secret({
apiVersion: "v1",
kind: "secret",
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import secretApiInjectable from "../../../common/k8s-api/endpoints/secret.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { SecretStore } from "./store";
@@ -16,7 +17,9 @@ const secretStoreInjectable = getInjectable({
const api = di.inject(secretApiInjectable);
return new SecretStore(api);
return new SecretStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -7,6 +7,7 @@ import assert from "assert";
import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { CustomResourceDefinitionStore } from "./definition.store";
@@ -19,6 +20,7 @@ const customResourceDefinitionStoreInjectable = getInjectable({
return new CustomResourceDefinitionStore({
autoRegistration: di.inject(autoRegistrationEmitterInjectable),
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -4,7 +4,7 @@
*/
import { computed, reaction, makeObservable } from "mobx";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { autoBind } from "../../utils";
import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api";
@@ -12,7 +12,7 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type TypedEventEmitter from "typed-emitter";
import type { LegacyAutoRegistration } from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
export interface CustomResourceDefinitionStoreDependencies {
export interface CustomResourceDefinitionStoreDependencies extends KubeObjectStoreDependencies {
readonly autoRegistration: TypedEventEmitter<LegacyAutoRegistration>;
}
@@ -22,7 +22,7 @@ export class CustomResourceDefinitionStore extends KubeObjectStore<CustomResourc
api: CustomResourceDefinitionApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
makeObservable(this);
autoBind(this);
@@ -7,6 +7,7 @@ import assert from "assert";
import getPodByIdInjectable from "../+workloads-pods/get-pod-by-id.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import kubeEventApiInjectable from "../../../common/k8s-api/endpoints/events.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { EventStore } from "./store";
@@ -19,6 +20,7 @@ const eventStoreInjectable = getInjectable({
return new EventStore({
getPodById: di.inject(getPodByIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
+4 -4
View File
@@ -5,7 +5,7 @@
import groupBy from "lodash/groupBy";
import compact from "lodash/compact";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { autoBind } from "../../utils";
import type { KubeEvent, KubeEventApi } from "../../../common/k8s-api/endpoints/events.api";
@@ -13,17 +13,17 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { Pod } from "../../../common/k8s-api/endpoints/pod.api";
import type { GetPodById } from "../+workloads-pods/get-pod-by-id.injectable";
interface Dependencies {
export interface EventStoreDependencies extends KubeObjectStoreDependencies {
getPodById: GetPodById;
}
export class EventStore extends KubeObjectStore<KubeEvent, KubeEventApi> {
constructor(
protected readonly dependencies: Dependencies,
protected readonly dependencies: EventStoreDependencies,
api: KubeEventApi,
opts: KubeObjectStoreOptions = {},
) {
super(api, { limit: 1000, ...opts });
super(dependencies, api, { limit: 1000, ...opts });
autoBind(this);
}
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import releaseSecretsInjectable from "./release-secrets.injectable";
import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable";
import toHelmReleaseInjectable from "./to-helm-release.injectable";
@@ -13,7 +13,7 @@ const releasesInjectable = getInjectable({
id: "releases",
instantiate: (di) => {
const clusterContext = di.inject(clusterFrameContextInjectable);
const clusterContext = di.inject(clusterFrameContextForNamespacedResourcesInjectable);
const releaseSecrets = di.inject(releaseSecretsInjectable);
const requestHelmReleases = di.inject(requestHelmReleasesInjectable);
const toHelmRelease = di.inject(toHelmReleaseInjectable);
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NamespaceSelectFilter /> renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves renders 1`] = `
<body>
<div>
<div
@@ -86,7 +86,7 @@ exports[`<NamespaceSelectFilter /> renders 1`] = `
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked renders 1`] = `
<body>
<div>
<div
@@ -229,286 +229,6 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-2"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-2
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-2-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-3
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-3-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-4
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-4-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-5
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-5-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-6
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-6-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-7
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-7-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-8
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-8-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-9
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-9-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
@@ -542,7 +262,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-11"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
@@ -577,7 +297,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-12"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
@@ -612,7 +332,7 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-13"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
@@ -644,13 +364,293 @@ exports[`<NamespaceSelectFilter /> when clicked renders 1`] = `
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-2
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-2-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-3
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-3-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-4
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-4-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-5
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-5-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-6
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-6-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-7
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-7-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-8
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-8-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-9
</span>
<i
class="Icon box right material focusable small"
data-testid="namespace-select-filter-option-test-9-selected"
>
<span
class="icon"
data-icon-name="check"
>
check
</span>
</i>
</div>
</div>
</div>
</div>
</div>
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked renders 1`] = `
<body>
<div>
<div
@@ -736,7 +736,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked renders
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when clicked again renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked when clicked again renders 1`] = `
<body>
<div>
<div
@@ -918,7 +918,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-3
test-10
</span>
</div>
</div>
@@ -942,7 +942,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-4
test-11
</span>
</div>
</div>
@@ -966,7 +966,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-5
test-12
</span>
</div>
</div>
@@ -990,7 +990,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-6
test-13
</span>
</div>
</div>
@@ -1014,7 +1014,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-7
test-3
</span>
</div>
</div>
@@ -1038,7 +1038,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-8
test-4
</span>
</div>
</div>
@@ -1062,7 +1062,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-9
test-5
</span>
</div>
</div>
@@ -1086,7 +1086,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-10
test-6
</span>
</div>
</div>
@@ -1110,7 +1110,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-11
test-7
</span>
</div>
</div>
@@ -1134,7 +1134,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-12
test-8
</span>
</div>
</div>
@@ -1158,7 +1158,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</i>
<span>
test-13
test-9
</span>
</div>
</div>
@@ -1168,7 +1168,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
<body>
<div>
<div
@@ -1254,7 +1254,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</body>
`;
exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = `
exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = `
<body>
<div>
<div
@@ -1397,6 +1397,102 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-2"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-10
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-3"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-11
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-4"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-12
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-5"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-13
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-6"
tabindex="-1"
>
<div
class="flex gaps align-center"
@@ -1419,7 +1515,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option Select__option--is-selected css-tr4s17-option"
id="react-select-namespace-select-filter-option-3"
id="react-select-namespace-select-filter-option-7"
tabindex="-1"
>
<div
@@ -1454,7 +1550,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-4"
id="react-select-namespace-select-filter-option-8"
tabindex="-1"
>
<div
@@ -1478,7 +1574,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-5"
id="react-select-namespace-select-filter-option-9"
tabindex="-1"
>
<div
@@ -1502,7 +1598,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-6"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
@@ -1526,7 +1622,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-7"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
@@ -1550,7 +1646,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-8"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
@@ -1574,7 +1670,7 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-9"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
@@ -1595,102 +1691,6 @@ exports[`<NamespaceSelectFilter /> when clicked when 'test-2' is clicked when cl
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-10"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-10
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-11"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-11
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-12"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-12
</span>
</div>
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-namespace-select-filter-option-13"
tabindex="-1"
>
<div
class="flex gaps align-center"
>
<i
class="Icon material focusable small"
>
<span
class="icon"
data-icon-name="layers"
>
layers
</span>
</i>
<span>
test-13
</span>
</div>
</div>
</div>
</div>
</div>
@@ -3,14 +3,25 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { DiContainer } from "@ogre-tools/injectable";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import React from "react";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
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 hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import type { Disposer } from "../../utils";
import { disposer } from "../../utils";
import { renderFor } from "../test-utils/renderFor";
import { NamespaceSelectFilter } from "./namespace-select-filter";
import type { NamespaceStore } from "./store";
@@ -32,147 +43,197 @@ function createNamespace(name: string): Namespace {
describe("<NamespaceSelectFilter />", () => {
let di: DiContainer;
let namespaceStore: NamespaceStore;
let fetchMock: AsyncFnMock<Fetch>;
let result: RenderResult;
let cleanup: Disposer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-directory");
di.unoverride(subscribeStoresInjectable);
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
namespaceStore = di.inject(namespaceStoreInjectable);
const render = renderFor(di);
const subscribeStores = di.inject(subscribeStoresInjectable);
namespaceStore.items.replace([
createNamespace("test-1"),
createNamespace("test-2"),
createNamespace("test-3"),
createNamespace("test-4"),
createNamespace("test-5"),
createNamespace("test-6"),
createNamespace("test-7"),
createNamespace("test-8"),
createNamespace("test-9"),
createNamespace("test-10"),
createNamespace("test-11"),
createNamespace("test-12"),
createNamespace("test-13"),
]);
cleanup = disposer(subscribeStores([namespaceStore]));
fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock);
const render = renderFor(di);
result = render((
<NamespaceSelectFilter id="namespace-select-filter" />
));
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
afterEach(() => {
cleanup();
});
describe("when clicked", () => {
beforeEach(() => {
result.getByTestId("namespace-select-filter").click();
describe("once the subscribe resolves", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific([
"http://127.0.0.1:12345/api-kube/api/v1/namespaces",
], createMockResponseFromString("http://127.0.0.1:12345/api-kube/api/v1/namespaces", JSON.stringify({
apiVersion: "v1",
kind: "NamespaceList",
metadata: {},
items: [
createNamespace("test-1"),
createNamespace("test-2"),
createNamespace("test-3"),
createNamespace("test-4"),
createNamespace("test-5"),
createNamespace("test-6"),
createNamespace("test-7"),
createNamespace("test-8"),
createNamespace("test-9"),
createNamespace("test-10"),
createNamespace("test-11"),
createNamespace("test-12"),
createNamespace("test-13"),
],
})));
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("opens menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
describe("when 'test-2' is clicked", () => {
describe("when clicked", () => {
beforeEach(() => {
result.getByText("test-2").click();
result.getByTestId("namespace-select-filter").click();
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("has only 'test-2' is selected in the store", () => {
expect(namespaceStore.contextNamespaces).toEqual(["test-2"]);
it("opens menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
});
describe("when clicked again", () => {
describe("when 'test-2' is clicked", () => {
beforeEach(() => {
result.getByTestId("namespace-select-filter").click();
result.getByText("test-2").click();
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("shows 'test-2' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull();
it("has only 'test-2' is selected in the store", () => {
expect(namespaceStore.contextNamespaces).toEqual(["test-2"]);
});
it("does not show 'test-1' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull();
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
});
describe("when 'test-1' is clicked", () => {
describe("when clicked again", () => {
beforeEach(() => {
result.getByText("test-1").click();
result.getByTestId("namespace-select-filter").click();
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("has only 'test-1' is selected in the store", () => {
expect(namespaceStore.contextNamespaces).toEqual(["test-1"]);
it("shows 'test-2' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull();
});
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
it("does not show 'test-1' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull();
});
describe("when clicked again, then holding down multi select key", () => {
describe("when 'test-1' is clicked", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
filter.click();
fireEvent.keyDown(filter, { key: "Meta" });
result.getByText("test-1").click();
});
describe("when 'test-3' is clicked", () => {
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("has only 'test-1' is selected in the store", () => {
expect(namespaceStore.contextNamespaces).toEqual(["test-1"]);
});
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
});
describe("when clicked again, then holding down multi select key", () => {
beforeEach(() => {
result.getByText("test-3").click();
const filter = result.getByTestId("namespace-select-filter");
filter.click();
fireEvent.keyDown(filter, { key: "Meta" });
});
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
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"]));
});
it("keeps menu open", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
it("does not show 'kube-system' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull();
});
describe("when 'test-13' is clicked", () => {
describe("when 'test-3' is clicked", () => {
beforeEach(() => {
result.getByText("test-13").click();
result.getByText("test-3").click();
});
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"]));
it("renders", () => {
expect(result.baseElement).toMatchSnapshot();
});
it("'test-13' is not sorted to the top of the list", () => {
const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement;
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(topLevelElement.nextSibling).toBe(null);
it("keeps menu open", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
it("does not show 'kube-system' as selected", () => {
expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull();
});
describe("when 'test-13' is clicked", () => {
beforeEach(() => {
result.getByText("test-13").click();
});
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"]));
});
it("'test-13' is not sorted to the top of the list", () => {
const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement;
expect(topLevelElement.previousSibling).not.toBe(null);
});
});
describe("when releasing multi select key", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyUp(filter, { key: "Meta" });
});
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
});
});
});
@@ -183,46 +244,24 @@ describe("<NamespaceSelectFilter />", () => {
fireEvent.keyUp(filter, { key: "Meta" });
});
it("closes menu", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull();
it("keeps menu open", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
});
});
describe("when releasing multi select key", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyUp(filter, { key: "Meta" });
});
it("keeps menu open", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull();
});
});
});
});
});
});
describe("when multi-selection key is pressed", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyDown(filter, { key: "Meta" });
});
it("should show placeholder text as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces");
});
describe("when 'test-2' is clicked", () => {
describe("when multi-selection key is pressed", () => {
beforeEach(() => {
result.getByText("test-2").click();
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyDown(filter, { key: "Meta" });
});
it("should not show placeholder text as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces");
it("should show placeholder text as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces");
});
describe("when 'test-2' is clicked", () => {
@@ -230,20 +269,30 @@ describe("<NamespaceSelectFilter />", () => {
result.getByText("test-2").click();
});
it("should not show placeholder as 'All namespaces'", () => {
it("should not show placeholder text as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces");
});
describe("when multi-selection key is raised", () => {
describe("when 'test-2' is clicked", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyUp(filter, { key: "Meta" });
result.getByText("test-2").click();
});
it("should show placeholder text as 'All namespaces'", () => {
it("should not show placeholder as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces");
});
describe("when multi-selection key is raised", () => {
beforeEach(() => {
const filter = result.getByTestId("namespace-select-filter");
fireEvent.keyUp(filter, { key: "Meta" });
});
it("should show placeholder text as 'All namespaces'", () => {
expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces");
});
});
});
});
});
@@ -9,6 +9,8 @@ import createStorageInjectable from "../../utils/create-storage/create-storage.i
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
import assert from "assert";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import clusterConfiguredAccessibleNamespacesInjectable from "../../cluster/accessible-namespaces.injectable";
const namespaceStoreInjectable = getInjectable({
id: "namespace-store",
@@ -20,7 +22,9 @@ const namespaceStoreInjectable = getInjectable({
const api = di.inject(namespaceApiInjectable);
return new NamespaceStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
storage: createStorage<string[] | undefined>("selected_namespaces", undefined),
clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
+27 -47
View File
@@ -3,22 +3,23 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IReactionDisposer } from "mobx";
import type { IComputedValue, IReactionDisposer } from "mobx";
import { action, comparer, computed, makeObservable, reaction } from "mobx";
import type { StorageLayer } from "../../utils";
import { autoBind, noop, toggle } from "../../utils";
import type { KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api";
import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api";
interface Dependencies {
storage: StorageLayer<string[] | undefined>;
interface Dependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer<string[] | undefined>;
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
}
export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
constructor(protected readonly dependencies: Dependencies, api: NamespaceApi) {
super(api);
super(dependencies, api);
makeObservable(this);
autoBind(this);
@@ -26,11 +27,21 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
}
private async init() {
await this.contextReady;
await this.dependencies.storage.whenReady;
this.selectNamespaces(this.initialNamespaces);
this.autoLoadAllowedNamespaces();
const { allowedNamespaces } = this;
const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load
// return previously saved namespaces from local-storage (if any)
if (Array.isArray(selectedNamespaces)) {
this.selectNamespaces(selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace)));
} else if (allowedNamespaces.includes("default")) {
this.selectNamespaces(["default"]);
} else if (allowedNamespaces.length) {
this.selectNamespaces([allowedNamespaces[0]]);
} else {
this.selectNamespaces([]);
}
}
public onContextChange(callback: (namespaces: string[]) => void, opts: { fireImmediately?: boolean } = {}): IReactionDisposer {
@@ -40,32 +51,6 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
});
}
private autoLoadAllowedNamespaces(): IReactionDisposer {
return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), {
fireImmediately: true,
equals: comparer.shallow,
});
}
private get initialNamespaces(): string[] {
const { allowedNamespaces } = this;
const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load
// return previously saved namespaces from local-storage (if any)
if (Array.isArray(selectedNamespaces)) {
return selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace));
}
// otherwise select "default" or first allowed namespace
if (allowedNamespaces.includes("default")) {
return ["default"];
} else if (allowedNamespaces.length) {
return [allowedNamespaces[0]];
}
return [];
}
/**
* @private
* The current value (list of namespaces names) in the storage layer
@@ -75,10 +60,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
}
@computed get allowedNamespaces(): string[] {
return Array.from(new Set([
...(this.context?.allNamespaces ?? []), // allowed namespaces from cluster (main), updating every 30s
...this.items.map(item => item.getName()), // loaded namespaces from k8s api
].flat()));
return this.items.map(item => item.getName());
}
/**
@@ -110,11 +92,13 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
}
subscribe() {
const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get();
/**
* if user has given static list of namespaces let's not start watches
* because watch adds stuff that's not wanted or will just fail
*/
if ((this.context?.cluster.accessibleNamespaces.length ?? 0) > 0) {
if (clusterConfiguredAccessibleNamespaces.length > 0) {
return noop;
}
@@ -122,17 +106,13 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
}
protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<Namespace[]> {
const { allowedNamespaces } = this;
const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get();
let namespaces = await super.loadItems(params).catch(() => []);
namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName()));
if (!namespaces.length && allowedNamespaces.length > 0) {
return allowedNamespaces.map(getDummyNamespace);
if (clusterConfiguredAccessibleNamespaces.length > 0) {
return clusterConfiguredAccessibleNamespaces.map(getDummyNamespace);
}
return namespaces;
return super.loadItems(params);
}
@action selectNamespaces = (namespace: string | string[]) => {
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import endpointsApiInjectable from "../../../common/k8s-api/endpoints/endpoint.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { EndpointsStore } from "./store";
@@ -16,7 +17,9 @@ const endpointsStoreInjectable = getInjectable({
const api = di.inject(endpointsApiInjectable);
return new EndpointsStore(api);
return new EndpointsStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import ingressApiInjectable from "../../../common/k8s-api/endpoints/ingress.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { IngressStore } from "./store";
@@ -16,7 +17,9 @@ const ingressStoreInjectable = getInjectable({
const api = di.inject(ingressApiInjectable);
return new IngressStore(api);
return new IngressStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import networkPolicyApiInjectable from "../../../common/k8s-api/endpoints/network-policy.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { NetworkPolicyStore } from "./store";
@@ -16,7 +17,9 @@ const networkPolicyStoreInjectable = getInjectable({
const api = di.inject(networkPolicyApiInjectable);
return new NetworkPolicyStore(api);
return new NetworkPolicyStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import serviceApiInjectable from "../../../common/k8s-api/endpoints/service.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { ServiceStore } from "./store";
@@ -16,7 +17,9 @@ const serviceStoreInjectable = getInjectable({
const api = di.inject(serviceApiInjectable);
return new ServiceStore(api);
return new ServiceStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import nodeApiInjectable from "../../../common/k8s-api/endpoints/node.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { NodeStore } from "./store";
@@ -16,7 +17,9 @@ const nodeStoreInjectable = getInjectable({
const api = di.inject(nodeApiInjectable);
return new NodeStore(api);
return new NodeStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
+3 -3
View File
@@ -6,13 +6,13 @@ import { sum } from "lodash";
import { computed, makeObservable } from "mobx";
import type { Node, NodeApi } from "../../../common/k8s-api/endpoints";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { autoBind } from "../../utils";
export class NodeStore extends KubeObjectStore<Node, NodeApi> {
constructor(api: NodeApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
constructor(dependencies: KubeObjectStoreDependencies, api: NodeApi, opts?: KubeObjectStoreOptions) {
super(dependencies, api, opts);
makeObservable(this);
autoBind(this);
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import podSecurityPolicyApiInjectable from "../../../common/k8s-api/endpoints/pod-security-policy.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PodSecurityPolicyStore } from "./store";
@@ -16,7 +17,9 @@ const podSecurityPolicyStoreInjectable = getInjectable({
const api = di.inject(podSecurityPolicyApiInjectable);
return new PodSecurityPolicyStore(api);
return new PodSecurityPolicyStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -7,6 +7,7 @@ import assert from "assert";
import getPersistentVolumesByStorageClassInjectable from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import storageClassApiInjectable from "../../../common/k8s-api/endpoints/storage-class.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { StorageClassStore } from "./store";
@@ -19,6 +20,7 @@ const storageClassStoreInjectable = getInjectable({
return new StorageClassStore({
getPersistentVolumesByStorageClass: di.inject(getPersistentVolumesByStorageClassInjectable),
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -3,12 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { StorageClass, StorageClassApi, StorageClassData } from "../../../common/k8s-api/endpoints/storage-class.api";
import type { GetPersistentVolumesByStorageClass } from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable";
export interface StorageClassStoreDependencies {
export interface StorageClassStoreDependencies extends KubeObjectStoreDependencies {
getPersistentVolumesByStorageClass: GetPersistentVolumesByStorageClass;
}
@@ -18,7 +18,7 @@ export class StorageClassStore extends KubeObjectStore<StorageClass, StorageClas
api: StorageClassApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
}
getPersistentVolumes(storageClass: StorageClass) {
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import persistentVolumeClaimApiInjectable from "../../../common/k8s-api/endpoints/persistent-volume-claim.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PersistentVolumeClaimStore } from "./store";
@@ -16,7 +17,9 @@ const persistentVolumeClaimStoreInjectable = getInjectable({
const api = di.inject(persistentVolumeClaimApiInjectable);
return new PersistentVolumeClaimStore(api);
return new PersistentVolumeClaimStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import persistentVolumeApiInjectable from "../../../common/k8s-api/endpoints/persistent-volume.api.injectable";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { PersistentVolumeStore } from "./store";
@@ -16,7 +17,9 @@ const persistentVolumeStoreInjectable = getInjectable({
const api = di.inject(persistentVolumeApiInjectable);
return new PersistentVolumeStore(api);
return new PersistentVolumeStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -13,6 +13,9 @@ import { renderFor } from "../../../test-utils/renderFor";
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../../../cluster/create-cluster.injectable";
describe("ClusterRoleBindingDialog tests", () => {
let render: DiRender;
@@ -20,8 +23,19 @@ describe("ClusterRoleBindingDialog tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(directoryForUserDataInjectable, () => "/some-path-for-user-data");
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
render = renderFor(di);
@@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s-
import clusterRoleBindingApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role-binding.api.injectable";
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
import { ClusterRoleBindingStore } from "./store";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable";
const clusterRoleBindingStoreInjectable = getInjectable({
id: "cluster-role-binding-store",
@@ -16,7 +17,9 @@ const clusterRoleBindingStoreInjectable = getInjectable({
const api = di.inject(clusterRoleBindingApiInjectable);
return new ClusterRoleBindingStore(api);
return new ClusterRoleBindingStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s-
import clusterRoleApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role.api.injectable";
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
import { ClusterRoleStore } from "./store";
import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable";
const clusterRoleStoreInjectable = getInjectable({
id: "cluster-role-store",
@@ -16,7 +17,9 @@ const clusterRoleStoreInjectable = getInjectable({
const api = di.inject(clusterRoleApiInjectable);
return new ClusterRoleStore(api);
return new ClusterRoleStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -13,6 +13,9 @@ import { renderFor } from "../../../test-utils/renderFor";
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
import directoryForKubeConfigsInjectable from "../../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../../../cluster/create-cluster.injectable";
describe("RoleBindingDialog tests", () => {
let render: DiRender;
@@ -20,8 +23,19 @@ describe("RoleBindingDialog tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
render = renderFor(di);
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
import roleBindingApiInjectable from "../../../../common/k8s-api/endpoints/role-binding.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import { RoleBindingStore } from "./store";
@@ -16,7 +17,9 @@ const roleBindingStoreInjectable = getInjectable({
const api = di.inject(roleBindingApiInjectable);
return new RoleBindingStore(api);
return new RoleBindingStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -8,6 +8,7 @@ import roleApiInjectable from "../../../../common/k8s-api/endpoints/role.api.inj
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
import { RoleStore } from "./store";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable";
const roleStoreInjectable = getInjectable({
id: "role-store",
@@ -16,7 +17,9 @@ const roleStoreInjectable = getInjectable({
const api = di.inject(roleApiInjectable);
return new RoleStore(api);
return new RoleStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -8,6 +8,7 @@ import serviceAccountApiInjectable from "../../../../common/k8s-api/endpoints/se
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
import { ServiceAccountStore } from "./store";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable";
const serviceAccountStoreInjectable = getInjectable({
id: "service-account-store",
@@ -16,7 +17,9 @@ const serviceAccountStoreInjectable = getInjectable({
const api = di.inject(serviceAccountApiInjectable);
return new ServiceAccountStore(api);
return new ServiceAccountStore({
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
});
@@ -7,6 +7,7 @@ import assert from "assert";
import getJobsByOwnerInjectable from "../+workloads-jobs/get-jobs-by-owner.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import cronJobApiInjectable from "../../../common/k8s-api/endpoints/cron-job.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { CronJobStore } from "./store";
@@ -19,6 +20,7 @@ const cronJobStoreInjectable = getInjectable({
return new CronJobStore({
getJobsByOwner: di.inject(getJobsByOwnerInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -3,18 +3,18 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { CronJob, CronJobApi } from "../../../common/k8s-api/endpoints/cron-job.api";
import type { GetJobsByOwner } from "../+workloads-jobs/get-jobs-by-owner.injectable";
interface Dependencies {
interface Dependencies extends KubeObjectStoreDependencies {
getJobsByOwner: GetJobsByOwner;
}
export class CronJobStore extends KubeObjectStore<CronJob, CronJobApi> {
constructor(protected readonly dependencies: Dependencies, api: CronJobApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getStatuses(cronJobs?: CronJob[]) {
@@ -9,6 +9,7 @@ import daemonSetApiInjectable from "../../../common/k8s-api/endpoints/daemon-set
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import { DaemonSetStore } from "./store";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
const daemonSetStoreInjectable = getInjectable({
id: "daemon-set-store",
@@ -19,6 +20,7 @@ const daemonSetStoreInjectable = getInjectable({
return new DaemonSetStore({
getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -6,16 +6,16 @@
import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable";
import type { DaemonSet, DaemonSetApi, Pod } from "../../../common/k8s-api/endpoints";
import { PodStatusPhase } from "../../../common/k8s-api/endpoints";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
export interface DaemonSetStoreDependencies {
export interface DaemonSetStoreDependencies extends KubeObjectStoreDependencies {
readonly getPodsByOwnerId: GetPodsByOwnerId;
}
export class DaemonSetStore extends KubeObjectStore<DaemonSet, DaemonSetApi> {
constructor(protected readonly dependencies: DaemonSetStoreDependencies, api: DaemonSetApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(daemonSet: DaemonSet): Pod[] {
@@ -9,6 +9,7 @@ import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manag
import { storesAndApisCanBeCreatedInjectionToken } from "../../../common/k8s-api/stores-apis-can-be-created.token";
import deploymentApiInjectable from "../../../common/k8s-api/endpoints/deployment.api.injectable";
import { DeploymentStore } from "./store";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
const deploymentStoreInjectable = getInjectable({
id: "deployment-store",
@@ -19,6 +20,7 @@ const deploymentStoreInjectable = getInjectable({
return new DeploymentStore({
podStore: di.inject(podStoreInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -6,7 +6,7 @@
import type { PodStore } from "../+workloads-pods/store";
import type { Deployment, DeploymentApi } from "../../../common/k8s-api/endpoints";
import { PodStatusPhase } from "../../../common/k8s-api/endpoints";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
// This needs to be disables because of https://github.com/microsoft/TypeScript/issues/15300
@@ -17,13 +17,13 @@ export type DeploymentStatuses = {
pending: number;
};
export interface DeploymentStoreDependencies {
export interface DeploymentStoreDependencies extends KubeObjectStoreDependencies {
readonly podStore: PodStore;
}
export class DeploymentStore extends KubeObjectStore<Deployment, DeploymentApi> {
constructor(protected readonly dependencies: DeploymentStoreDependencies, api: DeploymentApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
protected sortItems(items: Deployment[]) {
@@ -7,6 +7,7 @@ import assert from "assert";
import getPodsByOwnerIdInjectable from "../+workloads-pods/get-pods-by-owner-id.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import jobApiInjectable from "../../../common/k8s-api/endpoints/job.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { JobStore } from "./store";
@@ -19,6 +20,7 @@ const jobStoreInjectable = getInjectable({
return new JobStore({
getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -3,20 +3,20 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { Job, JobApi } from "../../../common/k8s-api/endpoints/job.api";
import type { CronJob, Pod } from "../../../common/k8s-api/endpoints";
import { PodStatusPhase } from "../../../common/k8s-api/endpoints";
import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable";
interface Dependencies {
interface Dependencies extends KubeObjectStoreDependencies {
getPodsByOwnerId: GetPodsByOwnerId;
}
export class JobStore extends KubeObjectStore<Job, JobApi> {
constructor(protected readonly dependencies: Dependencies, api: JobApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(job: Job): Pod[] {
@@ -12,6 +12,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import workloadsInjectable from "./workloads/workloads.injectable";
import type { Workload } from "./workloads/workload-injection-token";
import { formatKubeApiResource } from "../../../common/rbac";
export interface OverviewStatusesProps {}
@@ -24,7 +25,7 @@ const NonInjectedOverviewStatuses = observer(
<div className="OverviewStatuses">
<div className="workloads">
{workloads.get().map((workload) => (
<div className="workload" key={workload.resourceName}>
<div className="workload" key={formatKubeApiResource(workload.resource)}>
<div className="title">
<a onClick={workload.open}>
{`${workload.title} (${workload.amountOfItems.get()})`}
@@ -17,8 +17,7 @@ import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip";
import { withInjectables } from "@ogre-tools/injectable-react";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import workloadOverviewDetailsInjectable from "./workload-overview-details/workload-overview-details.injectable";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
@@ -35,10 +34,11 @@ import jobStoreInjectable from "../+workloads-jobs/store.injectable";
import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injectable";
import type { EventStore } from "../+events/store";
import eventStoreInjectable from "../+events/store.injectable";
import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context";
interface Dependencies {
detailComponents: IComputedValue<React.ElementType<{}>[]>;
clusterFrameContext: ClusterFrameContext;
clusterFrameContext: ClusterContext;
subscribeStores: SubscribeStores;
podStore: PodStore;
daemonSetStore: DaemonSetStore;
@@ -123,7 +123,7 @@ class NonInjectedWorkloadsOverview extends React.Component<Dependencies> {
export const WorkloadsOverview = withInjectables<Dependencies>(NonInjectedWorkloadsOverview, {
getProps: (di) => ({
detailComponents: di.inject(workloadOverviewDetailsInjectable),
clusterFrameContext: di.inject(clusterFrameContextInjectable),
clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
subscribeStores: di.inject(subscribeStoresInjectable),
daemonSetStore: di.inject(daemonSetStoreInjectable),
podStore: di.inject(podStoreInjectable),
@@ -19,17 +19,17 @@ const cronJobsWorkloadInjectable = getInjectable({
const store = di.inject(cronJobsStoreInjectable);
return {
resourceName: "cronjobs",
resource: {
apiName: "cronjobs",
group: "batch",
},
open: navigate,
amountOfItems: computed(
() => store.getAllByNs(namespaceStore.contextNamespaces).length,
),
status: computed(() =>
store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)),
),
title: ResourceNames.cronjobs,
orderNumber: 70,
};
@@ -19,7 +19,10 @@ const daemonsetsWorkloadInjectable = getInjectable({
const store = di.inject(daemonsetsStoreInjectable);
return {
resourceName: "daemonsets",
resource: {
apiName: "daemonsets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const deploymentsWorkloadInjectable = getInjectable({
const store = di.inject(deploymentsStoreInjectable);
return {
resourceName: "deployments",
resource: {
apiName: "deployments",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const jobsWorkloadInjectable = getInjectable({
const store = di.inject(jobStoreInjectable);
return {
resourceName: "jobs",
resource: {
apiName: "jobs",
group: "batch",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const podsWorkloadInjectable = getInjectable({
const store = di.inject(podStoreInjectable);
return {
resourceName: "pods",
resource: {
apiName: "pods",
group: "v1",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const replicasetsWorkloadInjectable = getInjectable({
const store = di.inject(replicasetsStoreInjectable);
return {
resourceName: "replicasets",
resource: {
apiName: "replicasets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -19,7 +19,10 @@ const statefulsetsWorkloadInjectable = getInjectable({
const store = di.inject(statefulsetsStoreInjectable);
return {
resourceName: "statefulsets",
resource: {
apiName: "statefulsets",
group: "apps",
},
open: navigate,
amountOfItems: computed(
@@ -4,10 +4,11 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import type { KubeApiResourceDescriptor } from "../../../../common/rbac";
import type { WorkloadStatus } from "../overview-workload-status";
export interface Workload {
resourceName: string;
resource: KubeApiResourceDescriptor;
open: () => void;
amountOfItems: IComputedValue<number>;
status: IComputedValue<WorkloadStatus>;
@@ -2,18 +2,11 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import { getInjectable } from "@ogre-tools/injectable";
import { filter, sortBy as sortByWithBadTyping } from "lodash/fp";
import { computed } from "mobx";
import type { Workload } from "./workload-injection-token";
import { shouldShowResourceInjectionToken } from "../../../../common/cluster-store/allowed-resources-injection-token";
import { byOrderNumber } from "../../../../common/utils/composable-responsibilities/orderable/orderable";
import { workloadInjectionToken } from "./workload-injection-token";
import isAllowedResourceInjectable from "../../../../common/utils/is-allowed-resource.injectable";
const sortBy =
(propertyPath: string) =>
<Collection extends object>(collection: Collection[]) =>
sortByWithBadTyping(propertyPath, collection);
const workloadsInjectable = getInjectable({
id: "workloads",
@@ -21,22 +14,11 @@ const workloadsInjectable = getInjectable({
instantiate: (di) => {
const workloads = di.injectMany(workloadInjectionToken);
const isAllowedResource = (resourceName: string) =>
di.inject(isAllowedResourceInjectable, resourceName);
return computed(() =>
pipeline(
workloads,
filter((workload: Workload) => {
const isAllowed = isAllowedResource(workload.resourceName);
return isAllowed.get();
}),
sortBy("orderNumber"),
),
);
return computed(() => (
workloads
.filter(w => di.inject(shouldShowResourceInjectionToken, w.resource).get())
.sort(byOrderNumber)
));
},
});
@@ -9,6 +9,7 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import { PodStore } from "./store";
import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
const podStoreInjectable = getInjectable({
id: "pod-store",
@@ -19,6 +20,7 @@ const podStoreInjectable = getInjectable({
return new PodStore({
podMetricsApi: di.inject(podMetricsApiInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -5,13 +5,13 @@
import countBy from "lodash/countBy";
import { observable } from "mobx";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { cpuUnitsToNumber, unitsToBytes } from "../../utils";
import type { Pod, PodMetrics, PodApi, PodMetricsApi } from "../../../common/k8s-api/endpoints";
import type { KubeObject, NamespaceScopedMetadata } from "../../../common/k8s-api/kube-object";
export interface PodStoreDependencies {
export interface PodStoreDependencies extends KubeObjectStoreDependencies {
readonly podMetricsApi: PodMetricsApi;
}
@@ -21,7 +21,7 @@ export class PodStore extends KubeObjectStore<Pod, PodApi> {
api: PodApi,
opts?: KubeObjectStoreOptions,
) {
super(api, opts);
super(dependencies, api, opts);
}
readonly kubeMetrics = observable.array<PodMetrics>([]);
@@ -9,6 +9,7 @@ import replicaSetApiInjectable from "../../../common/k8s-api/endpoints/replica-s
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import { ReplicaSetStore } from "./store";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
const replicaSetStoreInjectable = getInjectable({
id: "replica-set-store",
@@ -19,6 +20,7 @@ const replicaSetStoreInjectable = getInjectable({
return new ReplicaSetStore({
getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -6,16 +6,16 @@
import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable";
import type { Deployment, ReplicaSet, ReplicaSetApi } from "../../../common/k8s-api/endpoints";
import { PodStatusPhase } from "../../../common/k8s-api/endpoints/pod.api";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
interface Dependencies {
export interface ReplicaSetStoreDependencies extends KubeObjectStoreDependencies {
getPodsByOwnerId: GetPodsByOwnerId;
}
export class ReplicaSetStore extends KubeObjectStore<ReplicaSet, ReplicaSetApi> {
constructor(protected readonly dependencies: Dependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
constructor(protected readonly dependencies: ReplicaSetStoreDependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) {
super(dependencies, api, opts);
}
getChildPods(replicaSet: ReplicaSet) {
@@ -7,6 +7,7 @@ import assert from "assert";
import getPodsByOwnerIdInjectable from "../+workloads-pods/get-pods-by-owner-id.injectable";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable";
import statefulSetApiInjectable from "../../../common/k8s-api/endpoints/stateful-set.api.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { StatefulSetStore } from "./store";
@@ -19,6 +20,7 @@ const statefulSetStoreInjectable = getInjectable({
return new StatefulSetStore({
getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}, api);
},
injectionToken: kubeObjectStoreInjectionToken,
@@ -6,16 +6,16 @@
import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable";
import type { StatefulSet, StatefulSetApi } from "../../../common/k8s-api/endpoints";
import { PodStatusPhase } from "../../../common/k8s-api/endpoints";
import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
interface Dependencies {
interface Dependencies extends KubeObjectStoreDependencies {
getPodsByOwnerId: GetPodsByOwnerId;
}
export class StatefulSetStore extends KubeObjectStore<StatefulSet, StatefulSetApi> {
constructor(protected readonly dependencies: Dependencies, api: StatefulSetApi, opts?: KubeObjectStoreOptions) {
super(api, opts);
super(dependencies, api, opts);
}
getChildPods(statefulSet: StatefulSet) {
@@ -7,6 +7,10 @@ import cronJobStoreInjectable from "../+workloads-cronjobs/store.injectable";
import { CronJob } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const scheduledCronJob = new CronJob({
apiVersion: "foo",
@@ -119,8 +123,20 @@ describe("CronJob Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
cronJobStore = di.inject(cronJobStoreInjectable);
});
@@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable";
import { DaemonSet, Pod } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const runningDaemonSet = new DaemonSet({
apiVersion: "foo",
@@ -136,8 +140,20 @@ describe("DaemonSet Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
const podStore = di.inject(podStoreInjectable);
daemonSetStore = di.inject(daemonSetStoreInjectable);
@@ -11,6 +11,10 @@ import type { PodSpec } from "../../../common/k8s-api/endpoints";
import { Deployment, Pod } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const spec: PodSpec = {
containers: [{
@@ -208,8 +212,20 @@ describe("Deployment Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
const podStore = di.inject(podStoreInjectable);
// Add pods to pod store
@@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable";
import { Job, Pod } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const runningJob = new Job({
apiVersion: "foo",
@@ -173,8 +177,20 @@ describe("Job Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
jobStore = di.inject(jobStoreInjectable);
const podStore = di.inject(podStoreInjectable);
@@ -8,6 +8,10 @@ import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const runningPod = new Pod({
apiVersion: "foo",
@@ -119,8 +123,20 @@ describe("Pod Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
podStore = di.inject(podStoreInjectable);
});
@@ -10,6 +10,10 @@ import type { ReplicaSetStore } from "../+workloads-replicasets/store";
import { ReplicaSet, Pod } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const runningReplicaSet = new ReplicaSet({
apiVersion: "foo",
@@ -136,8 +140,20 @@ describe("ReplicaSet Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
const podStore = di.inject(podStoreInjectable);
replicaSetStore = di.inject(replicasetsStoreInjectable);
@@ -10,6 +10,10 @@ import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injecta
import { StatefulSet, Pod } from "../../../common/k8s-api/endpoints";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../cluster/create-cluster.injectable";
const runningStatefulSet = new StatefulSet({
apiVersion: "foo",
@@ -136,8 +140,20 @@ describe("StatefulSet Store tests", () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
statefulSetStore = di.inject(statefulSetStoreInjectable);
const podStore = di.inject(podStoreInjectable);
@@ -32,13 +32,13 @@ export class ClusterAccessibleNamespaces extends React.Component<ClusterAccessib
placeholder="Add new namespace..."
add={(newNamespace) => {
this.namespaces.add(newNamespace);
this.props.cluster.accessibleNamespaces = Array.from(this.namespaces);
this.props.cluster.accessibleNamespaces.replace([...this.namespaces]);
}}
validators={systemName}
items={Array.from(this.namespaces)}
remove={({ oldItem: oldNamespace }) => {
this.namespaces.delete(oldNamespace);
this.props.cluster.accessibleNamespaces = Array.from(this.namespaces);
this.props.cluster.accessibleNamespaces.replace([...this.namespaces]);
}}
inputTheme="round-black"
/>
@@ -13,8 +13,8 @@ import { observer } from "mobx-react";
import type { CreateResourceTabStore } from "./store";
import { EditorPanel } from "../editor-panel";
import { InfoPanel } from "../info-panel";
import { Notifications } from "../../notifications";
import logger from "../../../../common/logger";
import type { ShowNotification } from "../../notifications";
import type { Logger } from "../../../../common/logger";
import type { ApiManager } from "../../../../common/k8s-api/api-manager";
import { isObject, prevDefault } from "../../../utils";
import { withInjectables } from "@ogre-tools/injectable-react";
@@ -29,6 +29,10 @@ import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.in
import navigateInjectable from "../../../navigation/navigate.injectable";
import type { RequestKubeObjectCreation } from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable";
import requestKubeObjectCreationInjectable from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import type { ShowCheckedErrorNotification } from "../../notifications/show-checked-error.injectable";
import showSuccessNotificationInjectable from "../../notifications/show-success-notification.injectable";
import showCheckedErrorNotificationInjectable from "../../notifications/show-checked-error.injectable";
export interface CreateResourceProps {
tabId: string;
@@ -38,9 +42,12 @@ interface Dependencies {
createResourceTemplates: IComputedValue<GroupBase<{ label: string; value: string }>[]>;
createResourceTabStore: CreateResourceTabStore;
apiManager: ApiManager;
logger: Logger;
navigate: Navigate;
getDetailsUrl: GetDetailsUrl;
requestKubeObjectCreation: RequestKubeObjectCreation;
showSuccessNotification: ShowNotification;
showCheckedErrorNotification: ShowCheckedErrorNotification;
}
@observer
@@ -81,34 +88,38 @@ class NonInjectedCreateResource extends React.Component<CreateResourceProps & De
const resources = yaml.loadAll(this.data).filter(isObject);
if (resources.length === 0) {
return void logger.info("Nothing to create");
return this.props.logger.info("Nothing to create");
}
const creatingResources = resources.map(async (resource) => {
try {
const data = await requestKubeObjectCreation(dump(resource));
const { kind, apiVersion, metadata: { name, namespace }} = data;
const result = await requestKubeObjectCreation(dump(resource));
const showDetails = () => {
const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace });
if (!result.callWasSuccessful) {
this.props.logger.warn("Failed to create resource", { resource }, result.error);
this.props.showCheckedErrorNotification(result.error, "Unknown error occured while creating resources");
navigate(getDetailsUrl(resourceLink));
close();
};
const close = Notifications.ok(
<p>
{kind}
{" "}
<a onClick={prevDefault(showDetails)}>
{name}
</a>
{" successfully created."}
</p>,
);
} catch (error) {
Notifications.checkedError(error, "Unknown error occured while creating resources");
return;
}
const { kind, apiVersion, metadata: { name, namespace }} = result.response;
const close = this.props.showSuccessNotification((
<p>
{kind}
{" "}
<a
onClick={prevDefault(() => {
const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace });
navigate(getDetailsUrl(resourceLink));
close();
})}
>
{name}
</a>
{" successfully created."}
</p>
));
});
await Promise.allSettled(creatingResources);
@@ -168,8 +179,11 @@ export const CreateResource = withInjectables<Dependencies, CreateResourceProps>
createResourceTabStore: di.inject(createResourceTabStoreInjectable),
createResourceTemplates: await di.inject(createResourceTemplatesInjectable),
apiManager: di.inject(apiManagerInjectable),
logger: di.inject(loggerInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable),
navigate: di.inject(navigateInjectable),
requestKubeObjectCreation: di.inject(requestKubeObjectCreationInjectable),
showSuccessNotification: di.inject(showSuccessNotificationInjectable),
showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable),
}),
});
+17 -5
View File
@@ -11,7 +11,8 @@ import { cssNames, autoBind } from "../../utils";
import { Icon } from "../icon";
import type { InputProps } from "./input";
import { Input } from "./input";
import { isMac } from "../../../common/vars";
import { withInjectables } from "@ogre-tools/injectable-react";
import isMacInjectable from "../../../common/vars/is-mac.injectable";
export interface SearchInputProps extends InputProps {
compact?: boolean; // show only search-icon when not focused
@@ -27,13 +28,17 @@ const defaultProps: Partial<SearchInputProps> = {
placeholder: "Search...",
};
interface Dependencies {
isMac: boolean;
}
@observer
export class SearchInput extends React.Component<SearchInputProps> {
class NonInjectedSearchInput extends React.Component<SearchInputProps & Dependencies> {
static defaultProps = defaultProps as object;
private inputRef = createRef<Input>();
constructor(props: SearchInputProps) {
constructor(props: SearchInputProps & Dependencies) {
super(props);
autoBind(this);
}
@@ -48,7 +53,7 @@ export class SearchInput extends React.Component<SearchInputProps> {
}
onGlobalKey(evt: KeyboardEvent) {
if (evt.key === "f" && (isMac ? evt.metaKey : evt.ctrlKey)) {
if (evt.key === "f" && (this.props.isMac ? evt.metaKey : evt.ctrlKey)) {
this.inputRef.current?.focus();
}
}
@@ -71,7 +76,7 @@ export class SearchInput extends React.Component<SearchInputProps> {
}
render() {
const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, ...inputProps } = this.props;
const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, isMac, ...inputProps } = this.props;
let rightIcon = <Icon small material="search"/>;
if (showClearIcon && value) {
@@ -97,3 +102,10 @@ export class SearchInput extends React.Component<SearchInputProps> {
);
}
}
export const SearchInput = withInjectables<Dependencies, SearchInputProps>(NonInjectedSearchInput, {
getProps: (di, props) => ({
...props,
isMac: di.inject(isMacInjectable),
}),
});
@@ -20,8 +20,7 @@ import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import type { SubscribableStore, SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import type { KubeApi } from "../../../common/k8s-api/kube-api";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
@@ -29,6 +28,7 @@ import type { PageParam } from "../../navigation";
import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details.injectable";
import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable";
import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable";
import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context";
export interface KubeObjectListLayoutProps<
K extends KubeObject,
@@ -43,7 +43,7 @@ export interface KubeObjectListLayoutProps<
}
interface Dependencies {
clusterFrameContext: ClusterFrameContext;
clusterFrameContext: ClusterContext;
subscribeToStores: SubscribeStores;
kubeSelectedUrlParam: PageParam<string>;
toggleKubeDetailsPane: ToggleKubeDetailsPane;
@@ -177,7 +177,7 @@ export const KubeObjectListLayout = withInjectables<
>(NonInjectedKubeObjectListLayout, {
getProps: (di, props) => ({
...props,
clusterFrameContext: di.inject(clusterFrameContextInjectable),
clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
subscribeToStores: di.inject(subscribeStoresInjectable),
kubeSelectedUrlParam: di.inject(kubeSelectedUrlParamInjectable),
toggleKubeDetailsPane: di.inject(toggleKubeDetailsPaneInjectable),
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens";
import { beforeFrameStartsSecondInjectionToken } from "../../../before-frame-starts/tokens";
import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable";
import topBarStateInjectable from "./state.injectable";
@@ -26,7 +26,7 @@ const startTopbarStateSyncInjectable = getInjectable({
}));
},
}),
injectionToken: beforeFrameStartsInjectionToken,
injectionToken: beforeFrameStartsSecondInjectionToken,
causesSideEffects: true,
});
@@ -1,128 +0,0 @@
{
"name": "clouds-midnight",
"base": "vs-dark",
"inherit": true,
"rules": [
{
"background": "191919",
"token": ""
},
{
"foreground": "3c403b",
"token": "comment"
},
{
"foreground": "5d90cd",
"token": "string"
},
{
"foreground": "46a609",
"token": "constant.numeric"
},
{
"foreground": "39946a",
"token": "constant.language"
},
{
"foreground": "927c5d",
"token": "keyword"
},
{
"foreground": "927c5d",
"token": "support.constant.property-value"
},
{
"foreground": "927c5d",
"token": "constant.other.color"
},
{
"foreground": "366f1a",
"token": "keyword.other.unit"
},
{
"foreground": "a46763",
"token": "entity.other.attribute-name.html"
},
{
"foreground": "4b4b4b",
"token": "keyword.operator"
},
{
"foreground": "e92e2e",
"token": "storage"
},
{
"foreground": "858585",
"token": "entity.other.inherited-class"
},
{
"foreground": "606060",
"token": "entity.name.tag"
},
{
"foreground": "a165ac",
"token": "constant.character.entity"
},
{
"foreground": "a165ac",
"token": "support.class.js"
},
{
"foreground": "606060",
"token": "entity.other.attribute-name"
},
{
"foreground": "e92e2e",
"token": "meta.selector.css"
},
{
"foreground": "e92e2e",
"token": "entity.name.tag.css"
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.id.css"
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.class.css"
},
{
"foreground": "616161",
"token": "meta.property-name.css"
},
{
"foreground": "e92e2e",
"token": "support.function"
},
{
"foreground": "ffffff",
"background": "e92e2e",
"token": "invalid"
},
{
"foreground": "e92e2e",
"token": "punctuation.section.embedded"
},
{
"foreground": "606060",
"token": "punctuation.definition.tag"
},
{
"foreground": "a165ac",
"token": "constant.other.color.rgb-value.css"
},
{
"foreground": "a165ac",
"token": "support.constant.property-value.css"
}
],
"colors": {
"editor.foreground": "#929292",
"editor.background": "#191919",
"editor.selectionBackground": "#000000",
"editor.lineHighlightBackground": "#D7D7D708",
"editorCursor.foreground": "#7DA5DC",
"editorWhitespace.foreground": "#BFBFBF"
}
}
@@ -9,10 +9,10 @@ import type { IComputedValue, ObservableMap } from "mobx";
import { action, computed, observable, runInAction } from "mobx";
import React from "react";
import { Router } from "react-router";
import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable";
import type { RenderResult } from "@testing-library/react";
import { fireEvent, queryByText } from "@testing-library/react";
import type { KubeResource } from "../../../common/rbac";
import type { KubeApiResourceDescriptor } from "../../../common/rbac";
import { formatKubeApiResource } from "../../../common/rbac";
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
@@ -22,10 +22,7 @@ import navigateToPreferencesInjectable from "../../../features/preferences/commo
import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context";
import type { Cluster } from "../../../common/cluster/cluster";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable";
import startFrameInjectable from "../../start-frame/start-frame.injectable";
import type { NamespaceStore } from "../+namespaces/store";
@@ -52,7 +49,6 @@ import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluste
import activeKubernetesClusterInjectable from "../../cluster-frame-context/active-kubernetes-cluster.injectable";
import { catalogEntityFromCluster } from "../../../main/cluster/manager";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import { isAllowedResource } from "../../../common/cluster/is-allowed-resource";
import createApplicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-application-window.injectable";
import type { CreateElectronWindow } from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
import createElectronWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
@@ -115,7 +111,7 @@ export interface ApplicationBuilder {
create: (id: string) => LensWindowWithHelpers;
};
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
allowKubeResource: (resource: KubeApiResourceDescriptor) => ApplicationBuilder;
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
afterApplicationStart: (callback: Callback) => ApplicationBuilder;
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
@@ -214,7 +210,7 @@ export const getApplicationBuilder = () => {
},
}));
const allowedResourcesState = observable.array<KubeResource>();
const allowedResourcesState = observable.set<string>();
const windowHelpers = new Map<string, { di: DiContainer; getRendered: () => RenderResult }>();
@@ -502,15 +498,11 @@ export const getApplicationBuilder = () => {
environment = environments.clusterFrame;
builder.beforeWindowStart((windowDi) => {
windowDi.override(allowedResourcesInjectable, () =>
computed(() => new Set([...allowedResourcesState])),
);
const clusterStub = {
id: "some-cluster-id",
accessibleNamespaces: [],
isAllowedResource: isAllowedResource(allowedResourcesState),
} as unknown as Cluster;
accessibleNamespaces: observable.array(),
shouldShowResource: (kind) => allowedResourcesState.has(formatKubeApiResource(kind)),
} as Partial<Cluster> as Cluster;
windowDi.override(activeKubernetesClusterInjectable, () =>
computed(() => catalogEntityFromCluster(clusterStub)),
@@ -538,20 +530,8 @@ export const getApplicationBuilder = () => {
getTotalCount: () => namespaceItems.length,
} as Partial<NamespaceStore> as NamespaceStore;
const clusterFrameContextFake = new ClusterFrameContext(
clusterStub,
{
namespaceStore: namespaceStoreStub,
},
);
windowDi.override(namespaceStoreInjectable, () => namespaceStoreStub);
windowDi.override(hostedClusterInjectable, () => clusterStub);
windowDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake);
// Todo: get rid of global state.
KubeObjectStore.defaultContext.set(clusterFrameContextFake);
});
return builder;
@@ -635,11 +615,11 @@ export const getApplicationBuilder = () => {
},
},
allowKubeResource: (resourceName) => {
allowKubeResource: (resource) => {
environment.onAllowKubeResource();
runInAction(() => {
allowedResourcesState.push(resourceName);
allowedResourcesState.add(formatKubeApiResource(resource));
});
return builder;