1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Cherry pick PRs from 6.4.4 and 6.4.3 (#7309)

* Add support for using release branch targetted PRs (#7291)

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

* Fix namespaces not respecting accessible namespace config (#7279)

* Fix namespaces not respecting accessible namespace config

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

* Fix warning hover text formatting

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

* Log state after refresh accessibility

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

* Fix tests

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

* Fix NamespaceStore.allowedNamespaces

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

* Remove unnecessary '?'

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

* Add deprecation messages

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

* Move selected_namespaces storage to injectable

- And its initialization

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

* Implement contextNamespaces logic in forNamespacedResources

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

* Update snapshot

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

* Fix formatting

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

* Fix formatting

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

* Fix test

- This was a side effect of the previous bug fixes where
  the clusterContext.hasAllSelected was previously erroneously 'false'

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

* Change log level

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

* Fix another test suite

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

---------

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

* Fix not showing some non-core kinds (#7303)

* Fix not showing some non-core kinds

- Specifically if a Kind is not present within the preferredVersion of
  a group then we don't know that resource exists

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

* Add technical tests

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

---------

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

---------

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-03-07 09:46:00 -08:00 committed by GitHub
parent f69dd6fc8f
commit 0fe4daf8d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 311 additions and 77 deletions

View File

@ -498,6 +498,7 @@ export class Cluster implements ClusterModel {
this.allowedResources.replace(await this.getAllowedResources(requestNamespaceListPermissions));
this.ready = this.knownResources.length > 0;
this.dependencies.logger.debug(`[CLUSTER]: refreshed accessibility data`, this.getState());
}
/**

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import namespaceStoreInjectable from "../../renderer/components/+namespaces/store.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
import { storesAndApisCanBeCreatedInjectionToken } from "./stores-apis-can-be-created.token";
const selectedFilterNamespacesInjectable = getInjectable({
@ -15,9 +15,9 @@ const selectedFilterNamespacesInjectable = getInjectable({
return computed(() => []);
}
const store = di.inject(namespaceStoreInjectable);
const context = di.inject(clusterFrameContextForNamespacedResourcesInjectable);
return computed(() => [...store.contextNamespaces]);
return computed(() => [...context.contextNamespaces]);
},
});

View File

@ -28,6 +28,9 @@ import requestHelmChartReadmeInjectable from "../../../common/k8s-api/endpoints/
import requestHelmChartValuesInjectable from "../../../common/k8s-api/endpoints/helm-charts.api/request-values.injectable";
import type { RequestDetailedHelmRelease } from "../../../renderer/components/+helm-releases/release-details/release-details-model/request-detailed-helm-release.injectable";
import requestDetailedHelmReleaseInjectable from "../../../renderer/components/+helm-releases/release-details/release-details-model/request-detailed-helm-release.injectable";
import type { RequestHelmReleases } from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable";
import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable";
import { flushPromises } from "../../../common/test-utils/flush-promises";
describe("installing helm chart from new tab", () => {
let builder: ApplicationBuilder;
@ -37,6 +40,7 @@ describe("installing helm chart from new tab", () => {
let requestHelmChartReadmeMock: AsyncFnMock<RequestHelmChartReadme>;
let requestHelmChartValuesMock: AsyncFnMock<RequestHelmChartValues>;
let requestCreateHelmReleaseMock: AsyncFnMock<RequestCreateHelmRelease>;
let requestHelmReleasesMock: AsyncFnMock<RequestHelmReleases>;
beforeEach(() => {
builder = getApplicationBuilder();
@ -49,6 +53,7 @@ describe("installing helm chart from new tab", () => {
requestHelmChartReadmeMock = asyncFn();
requestHelmChartValuesMock = asyncFn();
requestCreateHelmReleaseMock = asyncFn();
requestHelmReleasesMock = asyncFn();
builder.beforeWindowStart((windowDi) => {
windowDi.override(directoryForLensLocalStorageInjectable, () => "/some-directory-for-lens-local-storage");
@ -58,6 +63,7 @@ describe("installing helm chart from new tab", () => {
windowDi.override(requestHelmChartReadmeInjectable, () => requestHelmChartReadmeMock);
windowDi.override(requestHelmChartValuesInjectable, () => requestHelmChartValuesMock);
windowDi.override(requestCreateHelmReleaseInjectable, () => requestCreateHelmReleaseMock);
windowDi.override(requestHelmReleasesInjectable, () => requestHelmReleasesMock);
windowDi.override(getRandomInstallChartTabIdInjectable, () =>
jest
@ -386,12 +392,15 @@ describe("installing helm chart from new tab", () => {
});
describe("when selected to see the installed release", () => {
beforeEach(() => {
beforeEach(async () => {
const releaseButton = rendered.getByTestId(
"show-release-some-release-for-some-first-tab-id",
);
fireEvent.click(releaseButton);
await flushPromises();
await requestHelmReleasesMock.resolve([]);
});
it("renders", () => {

View File

@ -78,9 +78,12 @@ describe("showing details for helm release", () => {
});
builder.namespaces.add("some-namespace");
builder.namespaces.select("some-namespace");
builder.namespaces.add("some-namespace");
builder.namespaces.select("some-other-namespace");
builder.afterWindowStart(() => {
builder.namespaces.select("some-namespace");
builder.namespaces.select("some-other-namespace");
});
});
describe("given application is started", () => {
@ -106,10 +109,9 @@ describe("showing details for helm release", () => {
});
it("calls for releases for each selected namespace", () => {
expect(requestHelmReleasesMock.mock.calls).toEqual([
["some-namespace"],
["some-other-namespace"],
]);
expect(requestHelmReleasesMock).toBeCalledTimes(2);
expect(requestHelmReleasesMock).toBeCalledWith("some-namespace");
expect(requestHelmReleasesMock).toBeCalledWith("some-other-namespace");
});
it("shows spinner", () => {

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import createStorageInjectable from "../../../renderer/utils/create-storage/create-storage.injectable";
const selectedNamespacesStorageInjectable = getInjectable({
id: "selected-namespaces-storage",
instantiate: (di) => {
const createStorage = di.inject(createStorageInjectable);
const cluster = di.inject(hostedClusterInjectable);
assert(cluster, "selectedNamespacesStorage is only available in certain environments");
const defaultSelectedNamespaces = cluster.allowedNamespaces.includes("default")
? ["default"]
: cluster.allowedNamespaces.slice(0, 1);
return createStorage("selected_namespaces", defaultSelectedNamespaces);
},
});
export default selectedNamespacesStorageInjectable;

View File

@ -4,7 +4,6 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { Cluster } from "../../common/cluster/cluster";
import type { AsyncResult } from "../../common/utils/async-result";
export interface KubeResourceListGroup {
@ -12,7 +11,11 @@ export interface KubeResourceListGroup {
path: string;
}
export type RequestApiVersions = (cluster: Cluster) => Promise<AsyncResult<KubeResourceListGroup[], Error>>;
export interface ClusterData {
readonly id: string;
}
export type RequestApiVersions = (cluster: ClusterData) => Promise<AsyncResult<KubeResourceListGroup[], Error>>;
export const requestApiVersionsInjectionToken = getInjectionToken<RequestApiVersions>({
id: "request-api-versions-token",

View File

@ -20,10 +20,10 @@ const requestNonCoreApiVersionsInjectable = getInjectable({
return {
callWasSuccessful: true,
response: chain(groups.values())
.filterMap(group => group.preferredVersion?.groupVersion && ({
.flatMap(group => group.versions.map(version => ({
group: group.name,
path: `/apis/${group.preferredVersion.groupVersion}`,
}))
path: `/apis/${version.groupVersion}`,
})))
.collect(v => [...v]),
};
} catch (error) {

View File

@ -0,0 +1,167 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* 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 { V1APIGroupList } from "@kubernetes/client-node";
import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { K8sRequest } from "../k8s-request.injectable";
import k8sRequestInjectable from "../k8s-request.injectable";
import type { RequestApiVersions } from "./request-api-versions";
import requestNonCoreApiVersionsInjectable from "./request-non-core-api-versions.injectable";
describe("requestNonCoreApiVersions", () => {
let di: DiContainer;
let k8sRequestMock: AsyncFnMock<K8sRequest>;
let requestNonCoreApiVersions: RequestApiVersions;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
k8sRequestMock = asyncFn();
di.override(k8sRequestInjectable, () => k8sRequestMock);
requestNonCoreApiVersions = di.inject(requestNonCoreApiVersionsInjectable);
});
describe("when called", () => {
let versionsRequest: ReturnType<RequestApiVersions>;
beforeEach(() => {
versionsRequest = requestNonCoreApiVersions({ id: "some-cluster-id" });
});
it("should request all api groups", () => {
expect(k8sRequestMock).toBeCalledWith({ id: "some-cluster-id" }, "/apis");
});
describe("when api groups request resolves to empty", () => {
beforeEach(async () => {
await k8sRequestMock.resolve({ groups: [] } as V1APIGroupList);
});
it("should return empty list", async () => {
expect(await versionsRequest).toEqual({
callWasSuccessful: true,
response: [],
});
});
});
describe("when api groups request resolves to single group", () => {
beforeEach(async () => {
await k8sRequestMock.resolve({ groups: [{
name: "some-name",
versions: [{
groupVersion: "some-name/v1",
version: "v1",
}],
}] } as V1APIGroupList);
});
it("should return single entry in list", async () => {
expect(await versionsRequest).toEqual({
callWasSuccessful: true,
response: [{
group: "some-name",
path: "/apis/some-name/v1",
}],
});
});
});
describe("when api groups request resolves to single group with multiple versions", () => {
beforeEach(async () => {
await k8sRequestMock.resolve({ groups: [{
name: "some-name",
versions: [
{
groupVersion: "some-name/v1",
version: "v1",
},
{
groupVersion: "some-name/v1beta1",
version: "v1beta1",
},
],
}] } as V1APIGroupList);
});
it("should return multiple entries in list", async () => {
expect(await versionsRequest).toEqual({
callWasSuccessful: true,
response: [
{
group: "some-name",
path: "/apis/some-name/v1",
},
{
group: "some-name",
path: "/apis/some-name/v1beta1",
},
],
});
});
});
describe("when api groups request resolves to multiple groups with multiple versions", () => {
beforeEach(async () => {
await k8sRequestMock.resolve({ groups: [
{
name: "some-name",
versions: [
{
groupVersion: "some-name/v1",
version: "v1",
},
{
groupVersion: "some-name/v1beta1",
version: "v1beta1",
},
],
},
{
name: "some-other-name.foo.com",
versions: [
{
groupVersion: "some-other-name.foo.com/v1",
version: "v1",
},
{
groupVersion: "some-other-name.foo.com/v1beta1",
version: "v1beta1",
},
],
},
] } as V1APIGroupList);
});
it("should return multiple entries in list", async () => {
expect(await versionsRequest).toEqual({
callWasSuccessful: true,
response: [
{
group: "some-name",
path: "/apis/some-name/v1",
},
{
group: "some-name",
path: "/apis/some-name/v1beta1",
},
{
group: "some-other-name.foo.com",
path: "/apis/some-other-name.foo.com/v1",
},
{
group: "some-other-name.foo.com",
path: "/apis/some-other-name.foo.com/v1beta1",
},
],
});
});
});
});
});

View File

@ -2,7 +2,6 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Cluster } from "../common/cluster/cluster";
import { getInjectable } from "@ogre-tools/injectable";
import type { LensRequestInit } from "../common/fetch/lens-fetch.injectable";
import lensFetchInjectable from "../common/fetch/lens-fetch.injectable";
@ -12,7 +11,11 @@ export interface K8sRequestInit extends LensRequestInit {
timeout?: number;
}
export type K8sRequest = (cluster: Cluster, pathnameAndQuery: string, init?: K8sRequestInit) => Promise<unknown>;
export interface ClusterData {
readonly id: string;
}
export type K8sRequest = (cluster: ClusterData, pathnameAndQuery: string, init?: K8sRequestInit) => Promise<unknown>;
const k8sRequestInjectable = getInjectable({
id: "k8s-request",

View File

@ -8,6 +8,7 @@ import namespaceStoreInjectable from "../components/+namespaces/store.injectable
import hostedClusterInjectable from "./hosted-cluster.injectable";
import assert from "assert";
import { computed } from "mobx";
import selectedNamespacesStorageInjectable from "../../features/namespace-filtering/renderer/storage.injectable";
const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({
id: "cluster-frame-context-for-namespaced-resources",
@ -15,6 +16,7 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({
instantiate: (di): ClusterContext => {
const cluster = di.inject(hostedClusterInjectable);
const namespaceStore = di.inject(namespaceStoreInjectable);
const selectedNamespacesStorage = di.inject(selectedNamespacesStorageInjectable);
assert(cluster, "This can only be injected within a cluster frame");
@ -32,13 +34,19 @@ const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({
// fallback to cluster resolved namespaces because we could not load list
return cluster.allowedNamespaces.slice();
});
const contextNamespaces = computed(() => namespaceStore.contextNamespaces);
const contextNamespaces = computed(() => {
const selectedNamespaces = selectedNamespacesStorage.get();
return selectedNamespaces.length > 0
? selectedNamespaces
: allNamespaces.get();
});
const hasSelectedAll = computed(() => {
const namespaces = new Set(contextNamespaces.get());
return allNamespaces.get().length > 1
&& cluster.accessibleNamespaces.length === 0
&& allNamespaces.get().every(ns => namespaces.has(ns));
&& cluster.accessibleNamespaces.length === 0
&& allNamespaces.get().every(ns => namespaces.has(ns));
});
return {

View File

@ -22,13 +22,11 @@ const releasesInjectable = getInjectable({
getValueFromObservedPromise: async () => {
void releaseSecrets.get();
const releaseArrays = await (clusterContext.hasSelectedAll
? requestHelmReleases()
: Promise.all(
clusterContext.contextNamespaces.map((namespace) =>
requestHelmReleases(namespace),
),
));
const releaseArrays = await (
clusterContext.hasSelectedAll
? requestHelmReleases()
: Promise.all(clusterContext.contextNamespaces.map((namespace) => requestHelmReleases(namespace)))
);
return releaseArrays.flat().map(toHelmRelease);
},

View File

@ -1412,7 +1412,7 @@ exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when
</span>
</i>
<span>
test-10
test-2
</span>
</div>
</div>
@ -1436,7 +1436,7 @@ exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when
</span>
</i>
<span>
test-11
test-10
</span>
</div>
</div>
@ -1460,7 +1460,7 @@ exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when
</span>
</i>
<span>
test-12
test-11
</span>
</div>
</div>
@ -1484,7 +1484,7 @@ exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when
</span>
</i>
<span>
test-13
test-12
</span>
</div>
</div>
@ -1508,7 +1508,7 @@ exports[`<NamespaceSelectFilter /> once the subscribe resolves when clicked when
</span>
</i>
<span>
test-2
test-13
</span>
</div>
</div>

View File

@ -6,6 +6,7 @@ import { namespaceSelectFilterModelFor } from "./namespace-select-filter-model";
import { getInjectable } from "@ogre-tools/injectable";
import namespaceStoreInjectable from "../store.injectable";
import isMultiSelectionKeyInjectable from "./is-selection-key.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable";
const namespaceSelectFilterModelInjectable = getInjectable({
id: "namespace-select-filter-model",
@ -13,6 +14,7 @@ const namespaceSelectFilterModelInjectable = getInjectable({
instantiate: (di) => namespaceSelectFilterModelFor({
namespaceStore: di.inject(namespaceStoreInjectable),
isMultiSelectionKey: di.inject(isMultiSelectionKeyInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}),
});

View File

@ -11,8 +11,10 @@ import { Icon } from "../../icon";
import type { SelectOption } from "../../select";
import { observableCrate } from "../../../utils";
import type { IsMultiSelectionKey } from "./is-selection-key.injectable";
import type { ClusterContext } from "../../../cluster-frame-context/cluster-frame-context";
interface Dependencies {
context: ClusterContext;
namespaceStore: NamespaceStore;
isMultiSelectionKey: IsMultiSelectionKey;
}
@ -44,7 +46,7 @@ enum SelectMenuState {
}
export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel {
const { isMultiSelectionKey, namespaceStore } = dependencies;
const { isMultiSelectionKey, namespaceStore, context } = dependencies;
let didToggle = false;
let isMultiSelection = false;
@ -56,7 +58,7 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names
didToggle = false;
},
}]);
const selectedNames = computed(() => new Set(namespaceStore.contextNamespaces), {
const selectedNames = computed(() => new Set(context.contextNamespaces), {
equals: comparer.structural,
});
const optionsSortingSelected = observable.set(selectedNames.get());
@ -78,9 +80,8 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names
label: "All Namespaces",
id: "all-namespaces",
},
...namespaceStore
.items
.map(ns => ns.getName())
...context
.allNamespaces
.sort(sortNamespacesByIfTheyHaveBeenSelected)
.map(namespace => ({
value: namespace,

View File

@ -12,9 +12,9 @@ import type { SelectProps } from "../select";
import { Select } from "../select";
import { cssNames } from "../../utils";
import { Icon } from "../icon";
import type { NamespaceStore } from "./store";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./store.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context";
export type NamespaceSelectSort = (left: string, right: string) => number;
@ -25,12 +25,12 @@ export interface NamespaceSelectProps<IsMulti extends boolean> extends Omit<Sele
}
interface Dependencies {
namespaceStore: NamespaceStore;
context: ClusterContext;
}
function getOptions(namespaceStore: NamespaceStore, sort: NamespaceSelectSort | undefined) {
function getOptions(context: ClusterContext, sort: NamespaceSelectSort | undefined) {
return computed(() => {
const baseOptions = namespaceStore.items.map(ns => ns.getName());
const baseOptions = context.allNamespaces;
if (sort) {
baseOptions.sort(sort);
@ -44,16 +44,16 @@ function getOptions(namespaceStore: NamespaceStore, sort: NamespaceSelectSort |
}
const NonInjectedNamespaceSelect = observer(({
namespaceStore,
context,
showIcons,
formatOptionLabel,
sort,
className,
...selectProps
}: Dependencies & NamespaceSelectProps<boolean>) => {
const [baseOptions, setBaseOptions] = useState(getOptions(namespaceStore, sort));
const [baseOptions, setBaseOptions] = useState(getOptions(context, sort));
useEffect(() => setBaseOptions(getOptions(namespaceStore, sort)), [sort]);
useEffect(() => setBaseOptions(getOptions(context, sort)), [sort]);
return (
<Select
@ -77,7 +77,7 @@ const NonInjectedNamespaceSelect = observer(({
const InjectedNamespaceSelect = withInjectables<Dependencies, NamespaceSelectProps<boolean>>(NonInjectedNamespaceSelect, {
getProps: (di, props) => ({
...props,
namespaceStore: di.inject(namespaceStoreInjectable),
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
}),
});

View File

@ -5,13 +5,13 @@
import { getInjectable } from "@ogre-tools/injectable";
import { NamespaceStore } from "./store";
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/kube-object-store-token";
import createStorageInjectable from "../../utils/create-storage/create-storage.injectable";
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";
import loggerInjectable from "../../../common/logger.injectable";
import selectedNamespacesStorageInjectable from "../../../features/namespace-filtering/renderer/storage.injectable";
const namespaceStoreInjectable = getInjectable({
id: "namespace-store",
@ -19,12 +19,11 @@ const namespaceStoreInjectable = getInjectable({
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectable), "namespaceStore is only available in certain environments");
const createStorage = di.inject(createStorageInjectable);
const api = di.inject(namespaceApiInjectable);
return new NamespaceStore({
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
storage: createStorage<string[] | undefined>("selected_namespaces", undefined),
storage: di.inject(selectedNamespacesStorageInjectable),
clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable),
logger: di.inject(loggerInjectable),
}, api);

View File

@ -19,7 +19,7 @@ export interface NamespaceTree {
}
interface Dependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer<string[] | undefined>;
readonly storage: StorageLayer<string[]>;
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
}
@ -28,21 +28,6 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
super(dependencies, api);
makeObservable(this);
autoBind(this);
// initialize allowed namespaces
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 {
@ -60,12 +45,16 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
return this.dependencies.storage.get() ?? [];
}
/**
* @deprecated This doesn't contain the namespaces from cluster settings or from cluster context
*/
@computed get allowedNamespaces(): string[] {
return this.items.map(item => item.getName());
}
/**
* The list of selected namespace names (for filtering)
* @deprecated This doesn't contain the namespaces from cluster settings or from cluster context
*/
@computed get contextNamespaces() {
if (!this.selectedNamespaces.length) {
@ -77,6 +66,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
/**
* The set of select namespace names (for filtering)
* @deprecated This doesn't contain the namespaces from cluster settings or from cluster context
*/
@computed get selectedNames(): Set<string> {
return new Set(this.contextNamespaces);

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
import podStoreInjectable from "./store.injectable";
@ -12,12 +12,12 @@ const loadPodsFromAllNamespacesInjectable = getInjectable({
id: "load-pods-from-all-namespaces",
instantiate: (di) => {
const podStore = di.inject(podStoreInjectable);
const namespaceStore = di.inject(namespaceStoreInjectable);
const context = di.inject(clusterFrameContextForNamespacedResourcesInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
return () => {
podStore.loadAll({
namespaces: [...namespaceStore.getItems().map(ns => ns.getName())],
namespaces: context.allNamespaces,
onLoadFailure: error =>
showErrorNotification(`Can not load Pods. ${String(error)}`),
});

View File

@ -9,7 +9,7 @@ import React from "react";
import { computed, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import type { Disposer } from "../../utils";
import { cssNames, isDefined } from "../../utils";
import { hasTypedProperty, isObject, isString, cssNames, isDefined } from "../../utils";
import type { KubeJsonApiDataFor, KubeObject } from "../../../common/k8s-api/kube-object";
import type { ItemListLayoutProps } from "../item-object-list/list-layout";
import { ItemListLayout } from "../item-object-list/list-layout";
@ -64,6 +64,10 @@ const getLoadErrorMessage = (error: unknown): string => {
return error.message;
}
if (isObject(error) && hasTypedProperty(error, "message", isString)) {
return error.message;
}
return `${error}`;
};

View File

@ -67,6 +67,7 @@ import fsInjectable from "../../../common/fs/fs.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
import { testUsingFakeTime } from "../../../common/test-utils/use-fake-time";
import selectedNamespacesStorageInjectable from "../../../features/namespace-filtering/renderer/storage.injectable";
import { registerFeature } from "@k8slens/feature-core";
import {
applicationFeatureForElectronMain,
@ -388,7 +389,12 @@ export const getApplicationBuilder = () => {
namespaces.add(namespace);
namespaceItems.replace(createNamespacesFor(namespaces));
}),
select: action((namespace) => selectedNamespaces.add(namespace)),
select: action((namespace) => {
const selectedNamespacesStorage = builder.applicationWindow.only.di.inject(selectedNamespacesStorageInjectable);
selectedNamespaces.add(namespace);
selectedNamespacesStorage.set([...selectedNamespaces]);
}),
},
applicationMenu: {
get items() {
@ -523,6 +529,7 @@ export const getApplicationBuilder = () => {
const clusterStub = {
id: "some-cluster-id",
accessibleNamespaces: observable.array(),
allowedNamespaces: observable.array(),
shouldShowResource: (kind) => allowedResourcesState.has(formatKubeApiResource(kind)),
} as Partial<Cluster> as Cluster;

View File

@ -103,7 +103,7 @@ export class KubeWatchApi {
return () => this.#watch.dec(store);
}
namespaces ??= this.dependencies.clusterContext?.contextNamespaces ?? [];
namespaces ??= this.dependencies.clusterContext.contextNamespaces ?? [];
let childController = new WrappedAbortController(parent);
const unsubscribe = disposer();

View File

@ -57,6 +57,7 @@ interface GithubPrData {
interface ExtendedGithubPrData extends Omit<GithubPrData, "mergedAt"> {
mergedAt: Date;
shouldAttemptCherryPick: boolean;
}
async function getCurrentBranch(): Promise<string> {
@ -190,7 +191,7 @@ function sortExtendedGithubPrData(left: ExtendedGithubPrData, right: ExtendedGit
return -1;
}
async function getRelevantPRs(previousReleasedVersion: string): Promise<ExtendedGithubPrData[]> {
async function getRelevantPRs(previousReleasedVersion: string, baseBranch: string): Promise<ExtendedGithubPrData[]> {
console.log("retrieving previous 200 PRs...");
const milestone = formatVersionForPickingPrs(await getCurrentVersionOfSubPackage("core"));
@ -200,7 +201,7 @@ async function getRelevantPRs(previousReleasedVersion: string): Promise<Extended
"list",
"--limit=500", // Should be big enough, if not we need to release more often ;)
"--state=merged",
"--base=master",
`--base=${baseBranch}`,
"--json mergeCommit,title,author,labels,number,milestone,mergedAt",
];
@ -217,7 +218,11 @@ async function getRelevantPRs(previousReleasedVersion: string): Promise<Extended
.filter(query => query.stdout)
.map(query => query.pr)
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"))
.map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) } as ExtendedGithubPrData))
.map(pr => ({
...pr,
mergedAt: new Date(pr.mergedAt),
shouldAttemptCherryPick: baseBranch === "master",
}))
.sort(sortExtendedGithubPrData);
}
@ -300,7 +305,11 @@ async function cherryPickCommits(prs: ExtendedGithubPrData[]): Promise<void> {
const cherryPickCommit = cherryPickCommitWith(rl);
for (const pr of prs) {
await cherryPickCommit(pr.mergeCommit.oid);
if (pr.shouldAttemptCherryPick) {
await cherryPickCommit(pr.mergeCommit.oid);
} else {
console.log(`Skipping cherry picking of #${pr.number} - ${pr.title}`);
}
}
rl.close();
@ -335,7 +344,12 @@ async function createRelease(): Promise<void> {
await bumpPackageVersions();
}
const relevantPrs = await getRelevantPRs(previousReleasedVersion);
const relevantPrs = await getRelevantPRs(previousReleasedVersion, "master");
if (prBase !== "master") {
relevantPrs.push(...await getRelevantPRs(previousReleasedVersion, prBase));
}
const selectedPrs = await pickRelevantPrs(relevantPrs, isMasterBranch);
const prBody = formatChangelog(previousReleasedVersion, selectedPrs);