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

Merge branch 'master' into enhancement-ability-to-remove-subnamespaces

This commit is contained in:
Alex Andreev 2023-03-09 17:01:31 +03:00
commit 55f7bebced
268 changed files with 6468 additions and 2679 deletions

View File

@ -27,7 +27,7 @@ See [Development](https://docs.k8slens.dev/contributing/development/) page.
## Contributing
See [Contributing](https://docs.k8slens.dev/contributing/) page.
See [Contributing](https://docs.k8slens.dev/contributing/contribute-to-lens/) page.
## License

View File

@ -1,7 +1,7 @@
# Release Guide
Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`.
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relavent commits from master.
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relevant commits from master.
## Prerequisites
@ -11,9 +11,13 @@ All releases will be made by creating a PR which bumps the version field in the
## Steps
1. If you are making a minor or major release (or prereleases for one) make sure you are on the `master` branch.
1. If you are making a minor or major release (or prereleases of one) make sure you are on the `master` branch.
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
1. Run `npm run create-release-pr <release-type>`. If you are making a subsequent prerelease release, provide the `--check-commits` flag.
1. If you are checking the commits, type `y<ENTER>` to pick a commit, and `n<ENTER>` to skip it. You will want to skip the commits that were part of previous prerelease releases.
1. Run `npm run create-release-pr`.
1. Pick the PRs that you want to include in this release using the keys listed.
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
1. If you released a patch version, create a new patch milestone for the next patch version and move all the issues and PRs (open or closed) that weren't included in the current release to that milestone.
1. Close the milestone related to the release that was just made (if not a prerelease release).
1. If you released a patch version and it contains PRs that targeted `release/v<MAJOR>.<MINOR>` make a new PR targeting master and include all the relevant PRs as cherry-picks. This PR should have the `skip-changelog` label and have a milestone of the next minor.

2927
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,6 @@
"adr": "^1.4.3",
"cross-env": "^7.0.3",
"lerna": "^6.5.1",
"rimraf": "^4.1.3"
"rimraf": "^4.3.1"
}
}

View File

@ -130,11 +130,12 @@
"@k8slens/node-fetch": "^6.5.0-alpha.0",
"@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.1",
"@ogre-tools/injectable-react": "^15.1.1",
"@ogre-tools/fp": "^15.1.2",
"@ogre-tools/injectable": "^15.1.2",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
"@ogre-tools/injectable-react": "^15.1.2",
"@ogre-tools/injectable-utils": "^15.1.2",
"@sentry/electron": "^3.0.8",
"@sentry/integrations": "^6.19.3",
"@side/jest-runtime": "^1.1.0",
@ -330,6 +331,7 @@
},
"peerDependencies": {
"@k8slens/application": "^6.5.0-alpha.0",
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
"@types/byline": "^4.2.33",
"@types/chart.js": "^2.9.36",
"@types/color": "^3.0.3",

View File

@ -67,7 +67,7 @@ describe("cluster-store", () => {
let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-temp-directory");

View File

@ -17,7 +17,7 @@ describe("create resource stack tests", () => {
let cluster: KubernetesCluster;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
cluster = {
getId: () => "test-cluster",
} as any;

View File

@ -43,7 +43,7 @@ describe("HotbarStore", () => {
let loggerMock: jest.Mocked<Logger>;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
testCluster = getMockCatalogEntity({
apiVersion: "v1",

View File

@ -21,7 +21,7 @@ describe("user store tests", () => {
let di: DiContainer;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(writeFileInjectable, () => () => Promise.resolve());
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");

View File

@ -12,7 +12,7 @@ describe("kubernetesClusterCategory", () => {
let kubernetesClusterCategory: KubernetesClusterCategory;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
});

View File

@ -127,7 +127,13 @@ export class KubernetesCluster<
context.menuItems.push({
title: "Disconnect",
icon: "link_off",
onClick: () => requestClusterDisconnection(this.getId()),
onClick: () => {
requestClusterDisconnection(this.getId());
broadcastMessage(
IpcRendererNavigationEvents.NAVIGATE_IN_APP,
"/catalog",
);
},
});
break;
case LensKubernetesClusterStatus.DISCONNECTED:

View File

@ -34,6 +34,10 @@ export class CatalogCategoryRegistry {
};
}
getById(id: string) {
return iter.find(this.categories.values(), (category) => category.getId() === id);
}
@computed get items() {
return Array.from(this.categories);
}

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());
}
/**
@ -699,6 +700,11 @@ export class Cluster implements ClusterModel {
}
shouldShowResource(resource: KubeApiResourceDescriptor): boolean {
if (this.allowedResources.size === 0) {
// better to show than hide everything
return true;
}
return this.allowedResources.has(formatKubeApiResource(resource));
}

View File

@ -47,9 +47,9 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
const { resourceRules } = status;
return (resource) => {
const resourceRule = resourceRules.find(({
apiGroups = [],
resources = [],
const rules = resourceRules.filter(({
apiGroups = ["*"],
resources = ["*"],
}) => {
const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group);
const isAboutResource = resources.includes("*") || resources.includes(resource.apiName);
@ -57,13 +57,7 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
return isAboutRelevantApiGroup && isAboutResource;
});
if (!resourceRule) {
return false;
}
const { verbs } = resourceRule;
return verbs.includes("*") || verbs.includes("list");
return rules.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"));
};
} catch (error) {
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });

View File

@ -0,0 +1,336 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import type { RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
import requestNamespaceListPermissionsForInjectable from "./request-namespace-list-permissions.injectable";
const createStubProxyConfig = (statusResponse: Promise<{ body: { status: V1SubjectRulesReviewStatus }}>) => ({
makeApiClient: () => ({
createSelfSubjectRulesReview: (): Promise<{ body: { status: V1SubjectRulesReviewStatus }}> => statusResponse,
}),
});
describe("requestNamespaceListPermissions", () => {
let di: DiContainer;
let requestNamespaceListPermissions: RequestNamespaceListPermissionsFor;
beforeEach(() => {
di = getDiForUnitTesting();
requestNamespaceListPermissions = di.inject(requestNamespaceListPermissionsForInjectable);
});
describe("when api returns incomplete data", () => {
it("returns truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: true,
resourceRules: [],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when api rejects", () => {
it("returns truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve, reject) => reject("unknown error")),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has all permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["*"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has list permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["list"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has list permissions for asked resource", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has all permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: ["*"],
verbs: ["*"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has list permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: ["*"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has list permissions for asked resource", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when resourceRules has matching resource without list verb", () => {
it("return falsy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["pods"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeFalsy();
});
});
describe("when resourceRules has no matching resource with list verb", () => {
it("return falsy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["services"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeFalsy();
});
});
});

View File

@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainerForInjection } from "@ogre-tools/injectable";
export interface ApplicationConfig {
mode: string;
}
export interface Application {
start: () => Promise<void>;
readonly di: DiContainerForInjection;
}
export type CreateApplication = (config: ApplicationConfig) => Application;

View File

@ -12,7 +12,7 @@ import { pipeline } from "@ogre-tools/fp";
describe("verify-that-all-routes-have-component", () => {
it("verify that routes have route component", () => {
const rendererDi = getDiForUnitTesting({ doGeneralOverrides: true });
const rendererDi = getDiForUnitTesting();
rendererDi.override(clusterStoreInjectable, () => ({
getById: () => null,

View File

@ -15,7 +15,7 @@ describe("InitializableState tests", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
});
describe("when created", () => {

View File

@ -37,7 +37,7 @@ describe("ApiManager", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");

View File

@ -15,7 +15,7 @@ describe("DeploymentApi", () => {
let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = {

View File

@ -30,7 +30,7 @@ describe("KubeApi", () => {
let di: DiContainer;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock);

View File

@ -42,7 +42,7 @@ describe("createKubeApiForRemoteCluster", () => {
let fetchMock: AsyncFnMock<Fetch>;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
@ -145,7 +145,7 @@ describe("KubeApi", () => {
let di: DiContainer;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");

View File

@ -15,7 +15,7 @@ describe("StatefulSetApi", () => {
let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = {

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

@ -8,6 +8,7 @@ import loggerInjectable from "./logger.injectable";
const logErrorInjectable = getInjectable({
id: "log-error",
instantiate: (di) => di.inject(loggerInjectable).error,
decorable: false,
});
export default logErrorInjectable;

View File

@ -26,6 +26,8 @@ const loggerInjectable = getInjectable({
silly: (message, ...data) => baseLogger.silly(message, ...data),
};
},
decorable: false,
});
export default loggerInjectable;

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[];
}

View File

@ -18,7 +18,7 @@ describe("with-error-logging", () => {
let logErrorMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
logErrorMock = jest.fn();
@ -116,7 +116,7 @@ describe("with-error-logging", () => {
let logErrorMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
logErrorMock = jest.fn();

View File

@ -14,7 +14,7 @@ describe("with orphan promise, when called", () => {
let logErrorMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
logErrorMock = jest.fn();

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import nodeEnvInjectionToken from "./node-env-injection-token";
import { nodeEnvInjectionToken } from "./node-env-injection-token";
const isProductionInjectable = getInjectable({
id: "is-production",

View File

@ -4,7 +4,7 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
const nodeEnvInjectionToken = getInjectionToken<string | undefined>({
export const nodeEnvInjectionToken = getInjectionToken<string | undefined>({
id: "node-env-injection-token",
});

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import nodeEnvInjectionToken from "./node-env-injection-token";
import { nodeEnvInjectionToken } from "./node-env-injection-token";
const nodeEnvFakeInjectable = getInjectable({
id: "node-env-fake",

View File

@ -23,7 +23,7 @@ describe("ExtensionLoader", () => {
let updateExtensionStateMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(currentlyInClusterFrameInjectable, () => false);

View File

@ -32,7 +32,7 @@ describe("ExtensionDiscovery", () => {
let homeDirectoryPath: string;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(installExtensionInjectable, () => () => Promise.resolve());

View File

@ -18,7 +18,7 @@ describe("ensure-hashed-directory-for-extension", () => {
let registeredExtensions: ObservableMap<string, string>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
ensureDirMock = jest.fn();

View File

@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import hideEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/hide.injectable";
import showEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/show.injectable";
import getDetailsUrlInjectable from "../../renderer/components/kube-detail-params/get-details-url.injectable";
import hideDetailsInjectable from "../../renderer/components/kube-detail-params/hide-details.injectable";
import showDetailsInjectable from "../../renderer/components/kube-detail-params/show-details.injectable";
@ -20,3 +22,6 @@ export const hideDetails = asLegacyGlobalFunctionForExtensionApi(hideDetailsInje
export const createPageParam = asLegacyGlobalFunctionForExtensionApi(createPageParamInjectable);
export const isActiveRoute = asLegacyGlobalFunctionForExtensionApi(isActiveRouteInjectable);
export const navigate = asLegacyGlobalFunctionForExtensionApi(navigateInjectable);
export const showEntityDetails = asLegacyGlobalFunctionForExtensionApi(showEntityDetailsInjectable);
export const hideEntityDetails = asLegacyGlobalFunctionForExtensionApi(hideEntityDetailsInjectable);

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import applicationMenuReactivityInjectable from "./application-menu-reactivity.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
const startApplicationMenuInjectable = getInjectable({
id: "start-application-menu",

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
import updatingIsEnabledInjectable from "../../../main/updating-is-enabled/updating-is-enabled.injectable";
import { afterApplicationIsLoadedInjectionToken } from "../../../../../main/start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
import { afterApplicationIsLoadedInjectionToken } from "@k8slens/application";
const startCheckingForUpdatesInjectable = getInjectable({
id: "start-checking-for-updates",

View File

@ -23,6 +23,8 @@ describe("check-for-platform-updates", () => {
beforeEach(() => {
const di = getDiForUnitTesting();
di.unoverride(checkForPlatformUpdatesInjectable);
checkForUpdatesMock = asyncFn();
electronUpdaterFake = {

View File

@ -26,6 +26,8 @@ describe("download-platform-update", () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.unoverride(downloadPlatformUpdateInjectable);
downloadUpdateMock = asyncFn();
electronUpdaterOnMock = jest.fn();
electronUpdaterOffMock = jest.fn();

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { afterApplicationIsLoadedInjectionToken } from "../../../main/start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
import { afterApplicationIsLoadedInjectionToken } from "@k8slens/application";
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time";
import buildVersionInjectable from "../../../main/vars/build-version/build-version.injectable";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { onLoadOfApplicationInjectionToken } from "../../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable";
const startWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({

File diff suppressed because it is too large Load Diff

View File

@ -1925,10 +1925,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
/>
</div>
<ul
class="Animate opacity Menu MenuActions flex right bottom portal enter"
class="Animate opacity Menu MenuActions flex bottom right portal enter"
data-testid="menu-actions-for-catalog-for-some-entity-id"
id="menu-actions-for-catalog-for-some-entity-id"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
style="--enter-duration: 100ms; --leave-duration: 100ms; left: 0px; top: 8px;"
>
<li
class="MenuItem"
@ -2778,10 +2778,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
/>
</div>
<ul
class="Animate opacity Menu MenuActions flex right bottom portal enter"
class="Animate opacity Menu MenuActions flex bottom right portal enter"
data-testid="menu-actions-for-catalog-for-some-entity-id"
id="menu-actions-for-catalog-for-some-entity-id"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
style="--enter-duration: 100ms; --leave-duration: 100ms; left: 0px; top: 8px;"
>
<li
class="MenuItem"
@ -6609,3 +6609,459 @@ exports[`opening catalog entity details panel when navigated to the catalog when
</div>
</body>
`;
exports[`opening catalog entity details panel when not navigated to the catalog and showEntityDetails is called from someplace renders 1`] = `
<body>
<div>
<div
class="ClusterManager"
>
<div
class="topBar"
>
<div
class="items"
>
<div
class="preventedDragging"
>
<i
class="Icon material interactive disabled focusable"
data-testid="home-button"
>
<span
class="icon"
data-icon-name="home"
>
home
</span>
</i>
</div>
<div
class="size-sm"
/>
<div
class="preventedDragging"
>
<i
class="Icon material interactive disabled focusable"
data-testid="history-back"
>
<span
class="icon"
data-icon-name="arrow_back"
>
arrow_back
</span>
</i>
</div>
<div
class="size-sm"
/>
<div
class="preventedDragging"
>
<i
class="Icon material interactive disabled focusable"
data-testid="history-forward"
>
<span
class="icon"
data-icon-name="arrow_forward"
>
arrow_forward
</span>
</i>
</div>
<div
class="separator"
/>
</div>
</div>
<main>
<div
id="lens-views"
/>
<div
class="flex justify-center Welcome align-center"
data-testid="welcome-page"
>
<div
data-testid="welcome-banner-container"
style="width: 320px;"
>
<i
class="Icon logo svg focusable"
>
<span
class="icon"
/>
</i>
<div
class="flex justify-center"
>
<div
data-testid="welcome-text-container"
style="width: 320px;"
>
<h2>
Welcome to some-product-name!
</h2>
<p>
To get you started we have auto-detected your clusters in your
kubeconfig file and added them to the catalog, your centralized
view for managing all your cloud-native resources.
<br />
<br />
If you have any questions or feedback, please join our
<a
class="link"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Forums
</a>
.
</p>
<ul
class="block"
data-testid="welcome-menu-container"
style="width: 320px;"
>
<li
class="flex grid-12"
>
<i
class="Icon box col-1 material focusable"
>
<span
class="icon"
data-icon-name="view_list"
>
view_list
</span>
</i>
<a
class="box col-10"
>
Browse Clusters in Catalog
</a>
<i
class="Icon box col-1 material focusable"
>
<span
class="icon"
data-icon-name="navigate_next"
>
navigate_next
</span>
</i>
</li>
</ul>
</div>
</div>
</div>
</div>
</main>
<div
class="HotbarMenu flex column"
>
<div
class="HotbarItems flex column gaps"
>
<div
class="HotbarCell isDraggingOwner animateDown"
index="0"
>
<div
style="z-index: 12; position: absolute;"
>
<div
class="HotbarIcon contextMenuAvailable"
>
<div
class="Avatar rounded disabled avatar"
id="hotbarIcon-hotbar-icon-catalog-entity"
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
>
Ca
</div>
</div>
</div>
</div>
<div
class="HotbarCell isDraggingOwner animateDown"
index="1"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="2"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="3"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="4"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="5"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="6"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="7"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="8"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="9"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="10"
/>
<div
class="HotbarCell isDraggingOwner animateDown"
index="11"
/>
</div>
<div
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="arrow_left"
>
arrow_left
</span>
</i>
<div
class="HotbarIndex"
>
<div
class="badge Badge small clickable"
id="hotbarIndex"
>
1
</div>
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="arrow_right"
>
arrow_right
</span>
</i>
</div>
</div>
<div
class="StatusBar"
data-testid="status-bar"
>
<div
class="leftSide"
data-testid="status-bar-left"
/>
<div
class="rightSide"
data-testid="status-bar-right"
/>
</div>
</div>
<div
class="Notifications flex column align-flex-end"
/>
</div>
<div
class="Animate slide-right Drawer entityDetails right enter"
data-testid="catalog-entity-details-drawer"
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="drawer-wrapper flex column"
>
<div
class="drawer-title flex align-center"
>
<div
class="drawer-title-text flex gaps align-center"
>
WebLink: some-weblink
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="content_copy"
>
content_copy
</span>
</i>
<div>
Copy
</div>
</div>
<i
class="Icon material interactive focusable"
tabindex="0"
>
<span
class="icon"
data-icon-name="close"
>
close
</span>
</i>
<div>
Close
</div>
</div>
<div
class="drawer-content flex column box grow"
>
<div
class="flex"
data-testid="catalog-entity-details-content-for-some-weblink-id"
>
<div
class="entityIcon"
>
<div
class="Avatar rounded avatar"
data-testid="detail-panel-hot-bar-icon"
style="width: 128px; height: 128px; background: rgb(77, 163, 16);"
>
sw
</div>
<div
class="hint"
>
Click to open
</div>
</div>
<div
class="box grow metadata"
>
<div
class="DrawerItem"
>
<span
class="name"
>
Name
</span>
<span
class="value"
>
some-weblink
</span>
</div>
<div
class="DrawerItem"
>
<span
class="name"
>
Kind
</span>
<span
class="value"
>
WebLink
</span>
</div>
<div
class="DrawerItem"
>
<span
class="name"
>
Source
</span>
<span
class="value"
>
unknown
</span>
</div>
<div
class="DrawerItem"
>
<span
class="name"
>
Status
</span>
<span
class="value"
>
available
</span>
</div>
<div
class="DrawerItem"
>
<span
class="name"
>
Labels
</span>
<span
class="value"
/>
</div>
</div>
</div>
<div
class="box grow"
>
<div
class="DrawerTitle title"
>
More Information
</div>
<div
class="DrawerItem"
data-testid="weblink-url-for-some-weblink-id"
>
<span
class="name"
>
URL
</span>
<span
class="value"
>
https://my-websome.com
</span>
</div>
</div>
</div>
</div>
<div
class="ResizingAnchor horizontal leading"
/>
</div>
</body>
`;

View File

@ -0,0 +1,234 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import asyncFn, { type AsyncFnMock } from "@async-fn/jest";
import type { DiContainer } from "@ogre-tools/injectable";
import type { RenderResult } from "@testing-library/react";
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
import type { AppEvent } from "../../common/app-event-bus/event-bus";
import type { CatalogEntityActionContext } from "../../common/catalog";
import { CatalogCategory, categoryVersion, CatalogEntity } from "../../common/catalog";
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import { flushPromises } from "../../common/test-utils/flush-promises";
import { advanceFakeTime, testUsingFakeTime } from "../../common/test-utils/use-fake-time";
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../renderer/api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
class MockCatalogCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
kind = "CatalogCategory";
metadata = {
name: "mock",
icon: "gear",
};
spec = {
group: "entity.k8slens.dev",
versions: [
categoryVersion("v1alpha1", (() => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
return function (data: any) {
const entity = new MockCatalogEntity(data);
entity.onRun = self.onRun;
return entity;
} as any;
})()),
],
names: {
kind: "Mock",
},
};
constructor(private onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
super();
}
}
class MockCatalogEntity extends CatalogEntity {
public apiVersion = "entity.k8slens.dev/v1alpha1";
public kind = "Mock";
}
function createMockCatalogEntity() {
return new MockCatalogEntity({
metadata: {
uid: "a_catalogEntity_uid",
name: "a catalog entity",
labels: {
test: "label",
},
},
status: {
phase: "",
},
spec: {},
});
}
describe("entity running technical tests", () => {
let builder: ApplicationBuilder;
let windowDi: DiContainer;
let rendered: RenderResult;
let appEventListener: jest.MockedFunction<(event: AppEvent) => void>;
let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise<void>>;
let catalogEntityRegistry: CatalogEntityRegistry;
beforeEach(async () => {
builder = getApplicationBuilder();
builder.afterWindowStart((windowDi) => {
onRun = jest.fn();
const catalogCategoryRegistery = windowDi.inject(catalogCategoryRegistryInjectable);
catalogCategoryRegistery.add(new MockCatalogCategory(onRun));
catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable);
const catalogEntityItem = createMockCatalogEntity();
catalogEntityRegistry.updateItems([catalogEntityItem]);
appEventListener = jest.fn();
windowDi.inject(appEventBusInjectable).addListener(appEventListener);
});
testUsingFakeTime();
rendered = await builder.render();
windowDi = builder.applicationWindow.only.di;
});
describe("when navigated to catalog", () => {
beforeEach(() => {
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
navigateToCatalog();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when details panel is opened", () => {
beforeEach(() => {
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-a_catalogEntity_uid").click();
advanceFakeTime(500);
rendered.getByTestId("open-details-menu-item-for-a_catalogEntity_uid").click();
advanceFakeTime(500);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
beforeEach(() => {
onBeforeRunMock = asyncFn();
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
rendered.getByTestId("detail-panel-hot-bar-icon").click();
});
it("calls on before run event", () => {
const target = onBeforeRunMock.mock.calls[0][0].target;
const actual = { id: target.getId(), name: target.getName() };
expect(actual).toEqual({
id: "a_catalogEntity_uid",
name: "a catalog entity",
});
});
it("does not call onRun yet", () => {
expect(onRun).not.toHaveBeenCalled();
});
it("when before run event resolves, calls onRun", async () => {
await onBeforeRunMock.resolve();
expect(onRun).toHaveBeenCalled();
});
});
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
rendered.getByTestId("detail-panel-hot-bar-icon").click();
await flushPromises();
expect(onRun).not.toHaveBeenCalled();
});
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
catalogEntityRegistry.addOnBeforeRun(() => {
throw new Error("some error");
});
rendered.getByTestId("detail-panel-hot-bar-icon").click();
await flushPromises();
expect(onRun).toHaveBeenCalled();
});
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
onRun.mockImplementation(() => done());
catalogEntityRegistry.addOnBeforeRun(async () => {});
rendered.getByTestId("detail-panel-hot-bar-icon").click();
});
it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
catalogEntityRegistry.addOnBeforeRun(async (event) => event.preventDefault());
rendered.getByTestId("detail-panel-hot-bar-icon").click();
expect(onRun).not.toHaveBeenCalled();
});
it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
const onBeforeRunMock = asyncFn();
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
rendered.getByTestId("detail-panel-hot-bar-icon").click();
await onBeforeRunMock.reject();
expect(onRun).toHaveBeenCalled();
});
it("emits catalog open AppEvent", () => {
expect(appEventListener).toHaveBeenCalledWith( {
action: "open",
name: "catalog",
});
});
it("emits catalog change AppEvent when changing the category", () => {
rendered.getByText("Web Links").click();
expect(appEventListener).toHaveBeenCalledWith({
action: "change-category",
name: "catalog",
params: {
category: "Web Links",
},
});
});
});
});
});

View File

@ -9,18 +9,20 @@ import { KubernetesCluster, WebLink } from "../../common/catalog-entities";
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import { advanceFakeTime, testUsingFakeTime } from "../../common/test-utils/use-fake-time";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable";
import showEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/show.injectable";
import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
describe("opening catalog entity details panel", () => {
let builder: ApplicationBuilder;
let rendered: RenderResult;
let windowDi: DiContainer;
let cluster: Cluster;
let clusterEntity: KubernetesCluster;
let localClusterEntity: KubernetesCluster;
let otherEntity: WebLink;
let cluster: Cluster;
beforeEach(async () => {
builder = getApplicationBuilder();
@ -28,7 +30,7 @@ describe("opening catalog entity details panel", () => {
builder.beforeWindowStart((windowDi) => {
// TODO: remove once ClusterStore can be used without overriding it
windowDi.override(getClusterByIdInjectable, () => (clusterId) => {
if (clusterId === cluster.id) {
if (clusterId === cluster?.id) {
return cluster;
}
@ -36,6 +38,8 @@ describe("opening catalog entity details panel", () => {
});
});
testUsingFakeTime();
builder.afterWindowStart((windowDi) => {
const createCluster = windowDi.inject(createClusterInjectable);
@ -129,6 +133,7 @@ describe("opening catalog entity details panel", () => {
describe("when opening the menu 'some-kubernetes-cluster'", () => {
beforeEach(() => {
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-some-entity-id").click();
advanceFakeTime(1000);
});
it("renders", () => {
@ -154,6 +159,7 @@ describe("opening catalog entity details panel", () => {
describe("when the panel opens", () => {
beforeEach(async () => {
advanceFakeTime(1000);
await rendered.findAllByTestId("catalog-entity-details-drawer");
});
@ -222,4 +228,21 @@ describe("opening catalog entity details panel", () => {
});
});
});
describe("when not navigated to the catalog and showEntityDetails is called from someplace", () => {
beforeEach(async () => {
const showEntityDetails = windowDi.inject(showEntityDetailsInjectable);
showEntityDetails("some-weblink-id");
advanceFakeTime(1000);
});
it("renders", async () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("opens the detail panel for the correct item", () => {
expect(rendered.queryByTestId("catalog-entity-details-content-for-some-weblink-id")).toBeInTheDocument();
});
});
});

View File

@ -7,7 +7,7 @@ import { isEqual } from "lodash";
import { autorun } from "mobx";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import type { ClusterId, ClusterState } from "../../../../common/cluster-types";
import { beforeApplicationIsLoadingInjectionToken } from "../../../../main/start-main-application/runnable-tokens/before-application-is-loading-injection-token";
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import initClusterStoreInjectable from "../../store/main/init.injectable";
import emitClusterStateUpdateInjectable from "./emit-update.injectable";

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { beforeApplicationIsLoadingInjectionToken } from "../../../../main/start-main-application/runnable-tokens/before-application-is-loading-injection-token";
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import initUserStoreInjectable from "../../../../main/stores/init-user-store.injectable";
const initClusterStoreInjectable = getInjectable({

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
const initFileSystemProvisionerStoreInjectable = getInjectable({
id: "init-file-system-provisioner-store",

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,10 +78,13 @@ describe("showing details for helm release", () => {
});
builder.namespaces.add("some-namespace");
builder.namespaces.select("some-namespace");
builder.namespaces.add("some-namespace");
builder.afterWindowStart(() => {
builder.namespaces.select("some-namespace");
builder.namespaces.select("some-other-namespace");
});
});
describe("given application is started", () => {
let rendered: RenderResult;
@ -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

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
const initHotbarStoreInjectable = getInjectable({

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

@ -37,9 +37,7 @@ describe("computeUnixShellEnvironment technical tests", () => {
let unixShellEnv: ReturnType<ComputeUnixShellEnvironment>;
beforeEach(() => {
di = getDiForUnitTesting({
doGeneralOverrides: true,
});
di = getDiForUnitTesting();
spawnMock = jest.fn().mockImplementation((spawnfile, spawnargs) => {
shellStdin = new MemoryStream();

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import { unionPATHs } from "../../../common/utils/union-env-path";
import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable";
import electronAppInjectable from "../../../main/electron-app/electron-app.injectable";

View File

@ -8,12 +8,16 @@ import { getInjectable } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
import telemetryWhiteListForFunctionsInjectable from "./renderer/telemetry-white-list-for-functions.injectable";
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
import logErrorInjectable from "../../common/log-error.injectable";
import telemetryDecoratorInjectable from "./renderer/telemetry-decorator.injectable";
describe("emit-telemetry-from-specific-function-calls", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.unoverride(telemetryDecoratorInjectable);
});
describe("given a telemetry white-list for injectables which instantiate a function", () => {
@ -22,72 +26,158 @@ describe("emit-telemetry-from-specific-function-calls", () => {
beforeEach(() => {
di.override(telemetryWhiteListForFunctionsInjectable, () => [
"some-white-listed-function",
{
id: "some-white-listed-function-with-white-listed-argument",
getParams: (irrelevantArg, arg) => ({ someParam: arg }),
},
{
id: "some-white-listed-function-with-bad-config",
getParams: () => {
throw new Error("some-error-from-bad-configuration");
},
},
]);
emitEventMock = jest.fn();
di.override(emitEventInjectable, () => emitEventMock);
});
describe("given instances of white-listed, non-white-listed and tagged functions", () => {
describe("given instances of white-listed and non-white-listed functions", () => {
let whiteListedFunctionMock: jest.Mock;
let nonWhiteListedFunctionMock: jest.Mock;
let taggedFunctionMock: jest.Mock;
let injectedWhiteListedFunction: jest.Mock;
let injectedNonWhiteListedFunction: jest.Mock;
let injectedTaggedFunction: jest.Mock;
let whiteListedFunction: jest.Mock;
let whiteListedFunctionWithArgument: jest.Mock;
let whiteListedFunctionWithFaultyConfig: jest.Mock;
let nonWhiteListedFunction: jest.Mock;
let logErrorMock: jest.Mock;
beforeEach(() => {
whiteListedFunctionMock = jest.fn();
nonWhiteListedFunctionMock = jest.fn();
taggedFunctionMock = jest.fn();
logErrorMock = jest.fn();
const whiteListedInjectable = getInjectable({
id: "some-white-listed-function",
instantiate: () => whiteListedFunctionMock,
});
const whiteListedInjectableWithArgument = getInjectable({
id: "some-white-listed-function-with-white-listed-argument",
instantiate: () => whiteListedFunctionMock,
});
const whiteListedInjectableWithBadConfig = getInjectable({
id: "some-white-listed-function-with-bad-config",
instantiate: () => whiteListedFunctionMock,
});
const nonWhiteListedInjectable = getInjectable({
id: "some-non-white-listed-function",
instantiate: () => nonWhiteListedFunctionMock,
});
const taggedInjectable = getInjectable({
id: "some-tagged-function",
instantiate: () => taggedFunctionMock,
tags: ["emit-telemetry"],
});
runInAction(() => {
di.register(whiteListedInjectable);
di.register(nonWhiteListedInjectable);
di.register(taggedInjectable);
di.register(
whiteListedInjectable,
whiteListedInjectableWithArgument,
whiteListedInjectableWithBadConfig,
nonWhiteListedInjectable,
);
});
injectedWhiteListedFunction = di.inject(whiteListedInjectable);
injectedNonWhiteListedFunction = di.inject(nonWhiteListedInjectable);
injectedTaggedFunction = di.inject(taggedInjectable);
di.override(logErrorInjectable, () => logErrorMock);
whiteListedFunction = di.inject(whiteListedInjectable);
whiteListedFunctionWithArgument = di.inject(
whiteListedInjectableWithArgument,
);
whiteListedFunctionWithFaultyConfig = di.inject(
whiteListedInjectableWithBadConfig,
);
nonWhiteListedFunction = di.inject(nonWhiteListedInjectable);
});
it("telemetry is not emitted yet", () => {
expect(emitEventMock).not.toHaveBeenCalled();
});
describe("when the white-listed function is called", () => {
beforeEach(() => {
injectedWhiteListedFunction("some-arg", "some-other-arg");
it("doesn't log errors, at least yet", () => {
expect(logErrorMock).not.toHaveBeenCalled();
});
it("telemetry is emitted in event bus", () => {
describe("when a normal white-listed function is called with arguments", () => {
beforeEach(() => {
whiteListedFunction("some-arg", "some-other-arg");
});
it("telemetry is emitted in event bus without the arguments", () => {
expect(emitEventMock).toHaveBeenCalledWith({
destination: "auto-capture",
action: "telemetry-from-business-action",
name: "some-white-listed-function",
params: { args: ["some-arg", "some-other-arg"] },
});
});
});
describe("when the white-listed function is called with MobX reactive content", () => {
describe("when a white-listed function with a white-listed argument is called with arguments", () => {
beforeEach(() => {
whiteListedFunctionWithArgument("some-arg", "some-other-arg");
});
it("telemetry is emitted in event bus with the arguments as params", () => {
expect(emitEventMock).toHaveBeenCalledWith({
action: "telemetry-from-business-action",
destination: "auto-capture",
name: "some-white-listed-function-with-white-listed-argument",
params: { someParam: "some-other-arg" },
});
});
});
describe("when a white-listed function with a white-listed argument is called without arguments", () => {
beforeEach(() => {
whiteListedFunctionWithArgument();
});
it("telemetry is emitted in event bus without params", () => {
expect(emitEventMock).toHaveBeenCalledWith({
action: "telemetry-from-business-action",
destination: "auto-capture",
name: "some-white-listed-function-with-white-listed-argument",
params: { someParam: undefined },
});
});
});
describe("given a faulty configuration, when a white-listed function is called", () => {
beforeEach(() => {
whiteListedFunctionWithFaultyConfig();
});
it("telemetry is still emitted in event bus, but with params indicating bad configuration, ", () => {
expect(emitEventMock).toHaveBeenCalledWith({
action: "telemetry-from-business-action",
destination: "auto-capture",
name: "some-white-listed-function-with-bad-config",
params: { error: "Tried to produce params for telemetry, but getParams() threw an error" },
});
});
it("logs error", () => {
expect(logErrorMock).toHaveBeenCalledWith(
'Tried to produce params for telemetry of "some-white-listed-function-with-bad-config", but getParams() threw an error',
expect.objectContaining({ message: "some-error-from-bad-configuration" }),
);
});
});
describe("when a white-listed function with a white-listed argument is called with MobX reactive content", () => {
beforeEach(() => {
const someComputedProperty = computed(() => "some-computed-value");
@ -96,22 +186,23 @@ describe("emit-telemetry-from-specific-function-calls", () => {
someComputedProperty,
};
injectedWhiteListedFunction(someObservable);
whiteListedFunctionWithArgument(
"irrelevant-argument",
someObservable,
);
});
it("telemetry is emitted in event bus without MobX internals or computeds", () => {
expect(emitEventMock).toHaveBeenCalledWith({
destination: "auto-capture",
action: "telemetry-from-business-action",
name: "some-white-listed-function",
name: "some-white-listed-function-with-white-listed-argument",
params: {
args: [
{
someParam: {
someStaticProperty: "some-static-value",
someComputedProperty: "some-computed-value",
},
],
},
});
});
@ -119,28 +210,13 @@ describe("emit-telemetry-from-specific-function-calls", () => {
describe("when the non-white-listed function is called", () => {
beforeEach(() => {
injectedNonWhiteListedFunction();
nonWhiteListedFunction();
});
it("telemetry is not emitted", () => {
expect(emitEventMock).not.toHaveBeenCalled();
});
});
describe("when the tagged, but not white-listed function is called", () => {
beforeEach(() => {
injectedTaggedFunction("some-arg", "some-other-arg");
});
it("telemetry is emitted in event bus", () => {
expect(emitEventMock).toHaveBeenCalledWith({
destination: "auto-capture",
action: "telemetry-from-business-action",
name: "some-tagged-function",
params: { args: ["some-arg", "some-other-arg"] },
});
});
});
});
});
});

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import { toJS, observable } from "mobx";
import { observable, toJS } from "mobx";
const emitTelemetryInjectable = getInjectable({
id: "emit-telemetry",
@ -12,12 +12,12 @@ const emitTelemetryInjectable = getInjectable({
instantiate: (di) => {
const emitEvent = di.inject(emitEventInjectable);
return ({ action, args }: { action: string; args: any[] }) => {
return ({ action, params }: { action: string; params?: object }) => {
emitEvent({
destination: "auto-capture",
action: "telemetry-from-business-action",
name: action,
params: { args: toJS(observable(args)) },
...(params ? { params: toJS(observable(params)) } : {}),
});
};
},

View File

@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { identity } from "lodash/fp";
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
import telemetryDecoratorInjectable from "./telemetry-decorator.injectable";
export default getGlobalOverride(telemetryDecoratorInjectable, () => ({
decorate: identity,
}));

View File

@ -2,35 +2,26 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type {
DiContainerForInjection,
Injectable,
} from "@ogre-tools/injectable";
import type { DiContainerForInjection } from "@ogre-tools/injectable";
import {
lifecycleEnum,
getInjectable,
instantiationDecoratorToken,
lifecycleEnum,
} from "@ogre-tools/injectable";
import assert from "assert";
import assert from "assert";
import { isFunction } from "lodash/fp";
import emitTelemetryInjectable from "./emit-telemetry.injectable";
import type { WhiteListItem } from "./telemetry-white-list-for-functions.injectable";
import telemetryWhiteListForFunctionsInjectable from "./telemetry-white-list-for-functions.injectable";
import logErrorInjectable from "../../../common/log-error.injectable";
const telemetryDecoratorInjectable = getInjectable({
id: "telemetry-decorator",
instantiate: (diForDecorator) => {
const emitTelemetry = diForDecorator.inject(emitTelemetryInjectable);
const whiteList = diForDecorator.inject(
telemetryWhiteListForFunctionsInjectable,
);
const shouldEmitTelemetry = shouldEmitTelemetryFor(whiteList);
return {
instantiate: (diForDecorator) => ({
decorate:
(instantiateToBeDecorated: any) =>
(di: DiContainerForInjection, instantiationParameter: any) => {
@ -42,8 +33,41 @@ const telemetryDecoratorInjectable = getInjectable({
assert(currentContext);
if (shouldEmitTelemetry(currentContext.injectable)) {
emitTelemetry({ action: currentContext.injectable.id, args });
const emitTelemetry = diForDecorator.inject(
emitTelemetryInjectable,
);
const logError = diForDecorator.inject(logErrorInjectable);
const whiteList = diForDecorator.inject(
telemetryWhiteListForFunctionsInjectable,
);
const whiteListMap = getWhiteListMap(whiteList);
const whiteListed = whiteListMap.get(currentContext.injectable.id);
if (whiteListed) {
let params;
try {
params = whiteListed.getParams(...args);
} catch (e) {
params = {
error:
"Tried to produce params for telemetry, but getParams() threw an error",
};
logError(
`Tried to produce params for telemetry of "${currentContext.injectable.id}", but getParams() threw an error`,
e,
);
}
emitTelemetry({
action: currentContext.injectable.id,
params,
});
}
return instance(...args);
@ -52,8 +76,7 @@ const telemetryDecoratorInjectable = getInjectable({
return instance;
},
};
},
}),
decorable: false,
// Todo: this is required because of imperfect typing in injectable.
@ -61,9 +84,23 @@ const telemetryDecoratorInjectable = getInjectable({
injectionToken: instantiationDecoratorToken,
});
const shouldEmitTelemetryFor =
(whiteList: string[]) => (injectable: Injectable<any, any, any>) =>
injectable.tags?.includes("emit-telemetry") ||
whiteList.includes(injectable.id);
const getWhiteListMap = (whiteList: WhiteListItem[]) =>
new Map(
whiteList.map((item) =>
typeof item === "string"
? [
item,
{
getParams: () => undefined,
},
]
: [
item.id,
{
getParams: item.getParams,
},
],
),
);
export default telemetryDecoratorInjectable;

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
const navigateTo = [
"navigate-to-preference-tab-id",
@ -88,21 +89,19 @@ const extensions = [
"uninstall-extension",
];
const externalActions = [
"open-link-in-browser",
];
const externalActions = ["open-link-in-browser"];
const uiInteraction = [
"show-details",
];
const uiInteraction = ["show-details"];
const terminal = [
"create-terminal-tab",
];
const terminal = ["create-terminal-tab"];
export type WhiteListItem =
| string
| { id: string; getParams: (...args: unknown[]) => AppEvent["params"] };
const telemetryWhiteListForFunctionsInjectable = getInjectable({
id: "telemetry-white-list-for-functions",
instantiate: () => [
instantiate: (): WhiteListItem[] => [
...navigateTo,
...helmInjectableIds,
...kubeConfigActions,

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import { onLoadOfApplicationInjectionToken } from "../../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import operatingSystemThemeInjectable from "../../../../main/theme/operating-system-theme.injectable";
import emitSystemThemeTypeUpdateInjectable from "./emit-update.injectable";

View File

@ -31,7 +31,7 @@ describe("create clusters", () => {
beforeEach(() => {
jest.clearAllMocks();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
const clusterServerUrl = "https://192.168.64.3:8443";
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");

View File

@ -53,7 +53,7 @@ describe("ContextHandler", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(createKubeAuthProxyInjectable, () => ({} as any));
createContextHandler = di.inject(createContextHandlerInjectable);

View File

@ -39,7 +39,7 @@ describe("kube auth proxy tests", () => {
let getBasenameOfPath: GetBasenameOfPath;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");

View File

@ -46,7 +46,7 @@ describe("kubeconfig manager tests", () => {
let ensureServerMock: AsyncFnMock<() => Promise<void>>;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");

View File

@ -11,7 +11,7 @@ describe("static-file-route", () => {
let handleStaticFileRoute: Route<Buffer, "/{path*}">;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
handleStaticFileRoute = di.inject(staticFileRouteInjectable);
});

View File

@ -11,7 +11,9 @@ describe("get-electron-app-path", () => {
let getElectronAppPath: (name: string) => string;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: false });
const di = getDiForUnitTesting();
di.unoverride(getElectronAppPathInjectable);
const appStub = {
name: "some-app-name",

View File

@ -13,7 +13,7 @@ import { fromPairs, map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
import appNameInjectable from "../../common/vars/app-name.injectable";
import { appPathsRunnablePhaseInjectionToken } from "../start-main-application/runnable-tokens/phases";
import { beforeAnythingInjectionToken } from "@k8slens/application-for-electron-main";
const setupAppPathsInjectable = getInjectable({
id: "setup-app-paths",
@ -51,7 +51,7 @@ const setupAppPathsInjectable = getInjectable({
};
},
injectionToken: appPathsRunnablePhaseInjectionToken,
injectionToken: beforeAnythingInjectionToken,
});
export default setupAppPathsInjectable;

View File

@ -43,7 +43,7 @@ describe("kubeconfig-sync.source tests", () => {
let di: DiContainer;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");

View File

@ -65,7 +65,7 @@ describe("CatalogEntityRegistry", () => {
});
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
entityRegistry = di.inject(catalogEntityRegistryInjectable);
});

View File

@ -24,7 +24,7 @@ describe("detect-cluster-metadata", () => {
let cluster: Cluster;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
const lastSeenDetectMock = jest.fn().mockReturnValue(Promise.resolve({ value: "some-time-stamp", accuracy: 100 }));
const nodeCountDetectMock = jest.fn().mockReturnValue(Promise.resolve({ value: 42, accuracy: 100 }));

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import clusterManagerInjectable from "./manager.injectable";
const initializeClusterManagerInjectable = getInjectable({

View File

@ -8,6 +8,8 @@ import loggerInjectable from "../../common/logger.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable";
import { ClusterManager } from "./manager";
import updateEntityMetadataInjectable from "./update-entity-metadata.injectable";
import updateEntitySpecInjectable from "./update-entity-spec.injectable";
import visibleClusterInjectable from "./visible-cluster.injectable";
const clusterManagerInjectable = getInjectable({
@ -19,6 +21,8 @@ const clusterManagerInjectable = getInjectable({
clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable),
visibleCluster: di.inject(visibleClusterInjectable),
logger: di.inject(loggerInjectable),
updateEntityMetadata: di.inject(updateEntityMetadataInjectable),
updateEntitySpec: di.inject(updateEntitySpecInjectable),
}),
});

View File

@ -8,7 +8,6 @@ import type { IObservableValue, ObservableSet } from "mobx";
import { action, makeObservable, observe, reaction, toJS } from "mobx";
import type { Cluster } from "../../common/cluster/cluster";
import { isErrnoException } from "../../common/utils";
import type { KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities/kubernetes-cluster";
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
import { ipcMainOn } from "../../common/ipc";
import { once } from "lodash";
@ -16,6 +15,8 @@ import type { ClusterStore } from "../../common/cluster-store/cluster-store";
import type { ClusterId } from "../../common/cluster-types";
import type { CatalogEntityRegistry } from "../catalog";
import type { Logger } from "../../common/logger";
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
const logPrefix = "[CLUSTER-MANAGER]:";
@ -27,6 +28,8 @@ interface Dependencies {
readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
readonly visibleCluster: IObservableValue<ClusterId | null>;
readonly logger: Logger;
readonly updateEntityMetadata: UpdateEntityMetadata;
readonly updateEntitySpec: UpdateEntitySpec;
}
export class ClusterManager {
@ -97,42 +100,8 @@ export class ClusterManager {
this.updateEntityStatus(entity, cluster);
entity.metadata.labels = {
...entity.metadata.labels,
...cluster.labels,
};
entity.metadata.distro = cluster.distribution;
entity.metadata.kubeVersion = cluster.version;
if (cluster.preferences?.clusterName) {
/**
* Only set the name if the it is overriden in preferences. If it isn't
* set then the name of the entity has been explicitly set by its source
*/
entity.metadata.name = cluster.preferences.clusterName;
}
entity.spec.metrics ||= { source: "local" };
if (entity.spec.metrics.source === "local") {
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
prometheus.type = cluster.preferences.prometheusProvider?.type;
prometheus.address = cluster.preferences.prometheus;
entity.spec.metrics.prometheus = prometheus;
}
if (cluster.preferences.icon) {
entity.spec.icon ??= {};
entity.spec.icon.src = cluster.preferences.icon;
} else if (cluster.preferences.icon === null) {
/**
* NOTE: only clear the icon if set to `null` by ClusterIconSettings.
* We can then also clear that value too
*/
entity.spec.icon = undefined;
cluster.preferences.icon = undefined;
}
this.dependencies.updateEntityMetadata(entity, cluster);
this.dependencies.updateEntitySpec(entity, cluster);
this.dependencies.catalogEntityRegistry.items.splice(index, 1, entity);
}

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();
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

@ -0,0 +1,42 @@
/**
* 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 type { KubernetesCluster } from "../../common/catalog-entities";
import { ClusterMetadataKey } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster";
import { enumKeys } from "../../common/utils/enum";
export type UpdateEntityMetadata = (entity: KubernetesCluster, cluster: Cluster) => void;
const updateEntityMetadataInjectable = getInjectable({
id: "update-entity-metadata",
instantiate: (): UpdateEntityMetadata => {
return (entity, cluster) => {
entity.metadata.labels = {
...entity.metadata.labels,
...cluster.labels,
};
entity.metadata.distro = cluster.distribution;
entity.metadata.kubeVersion = cluster.version;
enumKeys(ClusterMetadataKey).forEach((key) => {
const metadataKey = ClusterMetadataKey[key];
entity.metadata[metadataKey] = cluster.metadata[metadataKey];
});
if (cluster.preferences?.clusterName) {
/**
* Only set the name if the it is overriden in preferences. If it isn't
* set then the name of the entity has been explicitly set by its source
*/
entity.metadata.name = cluster.preferences.clusterName;
}
};
},
});
export default updateEntityMetadataInjectable;

View File

@ -0,0 +1,160 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { KubernetesCluster } from "../../common/catalog-entities";
import { ClusterMetadataKey } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
import updateEntityMetadataInjectable from "./update-entity-metadata.injectable";
describe("update-entity-metadata", () => {
let cluster: Cluster;
let entity: KubernetesCluster;
let updateEntityMetadata: UpdateEntityMetadata;
let detectedMetadata: Record<ClusterMetadataKey, any>;
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(appPathsStateInjectable, () => ({
get: () => ({} as AppPaths),
set: () => {},
}));
const createCluster = di.inject(createClusterInjectionToken);
updateEntityMetadata = di.inject(updateEntityMetadataInjectable);
cluster = createCluster({
id: "some-id",
contextName: "some-context",
kubeConfigPath: "minikube-config.yml",
}, {
clusterServerUrl: "foo",
});
detectedMetadata = {
[ClusterMetadataKey.CLUSTER_ID]: "some-cluster-id",
[ClusterMetadataKey.DISTRIBUTION]: "some-distribution",
[ClusterMetadataKey.VERSION]: "some-version",
[ClusterMetadataKey.LAST_SEEN]: "some-date",
[ClusterMetadataKey.NODES_COUNT]: 42,
[ClusterMetadataKey.PROMETHEUS]: {
"some-parameter": "some-value",
},
};
cluster.metadata = {
...cluster.metadata,
};
entity = new KubernetesCluster({
metadata: {
uid: "some-uid",
name: "some-name",
labels: {},
},
spec: {
kubeconfigContext: "some-context",
kubeconfigPath: "/some/path/to/kubeconfig",
},
status: {
phase: "connecting",
},
});
});
it("given cluster metadata has no some last seen timestamp, does not update entity metadata with last seen timestamp", () => {
updateEntityMetadata(entity, cluster);
expect(entity.metadata.lastSeen).toEqual(undefined);
});
it("given cluster metadata has some last seen timestamp, updates entity metadata with last seen timestamp", () => {
cluster.metadata[ClusterMetadataKey.LAST_SEEN] = detectedMetadata[ClusterMetadataKey.LAST_SEEN];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.lastSeen).toEqual("some-date");
});
it("given cluster metadata has some version, updates entity metadata with version", () => {
cluster.metadata[ClusterMetadataKey.VERSION] = detectedMetadata[ClusterMetadataKey.VERSION];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.version).toEqual("some-version");
});
it("given cluster metadata has nodes count, updates entity metadata with node count", () => {
cluster.metadata[ClusterMetadataKey.NODES_COUNT] = detectedMetadata[ClusterMetadataKey.NODES_COUNT];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.nodes).toEqual(42);
});
it("given cluster metadata has prometheus data, updates entity metadata with prometheus data", () => {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = detectedMetadata[ClusterMetadataKey.PROMETHEUS];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.prometheus).toEqual({
"some-parameter": "some-value",
});
});
it("given cluster metadata has distribution, updates entity metadata with distribution", () => {
cluster.metadata[ClusterMetadataKey.DISTRIBUTION] = detectedMetadata[ClusterMetadataKey.DISTRIBUTION];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.distribution).toEqual("some-distribution");
});
it("given cluster metadata has cluster id, updates entity metadata with cluster id", () => {
cluster.metadata[ClusterMetadataKey.CLUSTER_ID] = detectedMetadata[ClusterMetadataKey.CLUSTER_ID];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.id).toEqual("some-cluster-id");
});
it("given cluster metadata has no kubernetes version, updates entity metadata with 'unknown' kubernetes version", () => {
updateEntityMetadata(entity, cluster);
expect(entity.metadata.kubeVersion).toEqual("unknown");
});
it("given cluster metadata has kubernetes version, updates entity metadata with kubernetes version", () => {
cluster.metadata.version = "some-kubernetes-version";
updateEntityMetadata(entity, cluster);
expect(entity.metadata.kubeVersion).toEqual("some-kubernetes-version");
});
it("given cluster has labels, updates entity metadata with labels", () => {
cluster.labels = {
"some-label": "some-value",
};
entity.metadata.labels = {
"some-other-label": "some-other-value",
};
updateEntityMetadata(entity, cluster);
expect(entity.metadata.labels).toEqual({
"some-label": "some-value",
"some-other-label": "some-other-value",
});
});
it("given cluster has labels, overwrites entity metadata with cluster labels", () => {
cluster.labels = {
"some-label": "some-cluster-value",
};
entity.metadata.labels = {
"some-label": "some-entity-value",
};
updateEntityMetadata(entity, cluster);
expect(entity.metadata.labels).toEqual({
"some-label": "some-cluster-value",
});
});
it("give cluster preferences has name, updates entity metadata with name", () => {
cluster.preferences.clusterName = "some-cluster-name";
updateEntityMetadata(entity, cluster);
expect(entity.metadata.name).toEqual("some-cluster-name");
});
});

View File

@ -0,0 +1,41 @@
/**
* 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 type { KubernetesCluster, KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities";
import type { Cluster } from "../../common/cluster/cluster";
export type UpdateEntitySpec = (entity: KubernetesCluster, cluster: Cluster) => void;
const updateEntitySpecInjectable = getInjectable({
id: "update-entity-spec",
instantiate: (): UpdateEntitySpec => {
return (entity, cluster) => {
entity.spec.metrics ||= { source: "local" };
if (entity.spec.metrics.source === "local") {
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
prometheus.type = cluster.preferences.prometheusProvider?.type;
prometheus.address = cluster.preferences.prometheus;
entity.spec.metrics.prometheus = prometheus;
}
if (cluster.preferences.icon) {
entity.spec.icon ??= {};
entity.spec.icon.src = cluster.preferences.icon;
} else if (cluster.preferences.icon === null) {
/**
* NOTE: only clear the icon if set to `null` by ClusterIconSettings.
* We can then also clear that value too
*/
entity.spec.icon = undefined;
cluster.preferences.icon = undefined;
}
};
},
});
export default updateEntitySpecInjectable;

View File

@ -0,0 +1,154 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { KubernetesCluster } from "../../common/catalog-entities";
import type { Cluster } from "../../common/cluster/cluster";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
import updateEntitySpecInjectable from "./update-entity-spec.injectable";
describe("update-entity-spec", () => {
let cluster: Cluster;
let entity: KubernetesCluster;
let updateEntitySpec: UpdateEntitySpec;
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(appPathsStateInjectable, () => ({
get: () => ({} as AppPaths),
set: () => {},
}));
const createCluster = di.inject(createClusterInjectionToken);
updateEntitySpec = di.inject(updateEntitySpecInjectable);
cluster = createCluster({
id: "some-id",
contextName: "some-context",
kubeConfigPath: "minikube-config.yml",
}, {
clusterServerUrl: "foo",
});
entity = new KubernetesCluster({
metadata: {
uid: "some-uid",
name: "some-name",
labels: {},
},
spec: {
kubeconfigContext: "some-context",
kubeconfigPath: "/some/path/to/kubeconfig",
},
status: {
phase: "connecting",
},
});
});
it("given cluster has icon, updates entity spec with icon", () => {
cluster.preferences.icon = "some-icon";
updateEntitySpec(entity, cluster);
expect(entity.spec.icon?.src).toEqual("some-icon");
});
it("given cluster icon is null, deletes icon from both", () => {
cluster.preferences.icon = null;
entity.spec.icon = { src : "some-icon" };
updateEntitySpec(entity, cluster);
expect(entity.spec.icon).toBeUndefined();
expect(cluster.preferences.icon).toBeUndefined();
});
it("given entity has no metrics, adds source as local", () => {
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.source).toEqual("local");
});
it("given entity has metrics, does not change source", () => {
entity.spec.metrics = { source: "some-source" };
entity.spec.metrics.prometheus = {
address: {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
},
};
cluster.preferences.prometheus = {
namespace: "some-other-namespace",
port: 666,
service: "some-other-service",
prefix: "some-other-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.source).toEqual("some-source");
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
});
it("given entity has local prometheus source, updates entity spec with prometheus provider", () => {
entity.spec.metrics = { source: "local" };
cluster.preferences.prometheusProvider = {
type: "some-prometheus-provider-type",
};
cluster.preferences.prometheus = {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
});
it("given entity has no metrics, updates entity spec with prometheus provider", () => {
expect(entity.spec.metrics).toBeUndefined();
cluster.preferences.prometheusProvider = {
type: "some-prometheus-provider-type",
};
cluster.preferences.prometheus = {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
});
});

View File

@ -1,32 +0,0 @@
/**
* 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 { runInAction } from "mobx";
import type { CreateApplication } from "../common/create-app";
import nodeEnvInjectionToken from "../common/vars/node-env-injection-token";
import { getDi } from "./getDi";
import { registerInjectables } from "./register-injectables";
import startMainApplicationInjectable from "./start-main-application/start-main-application.injectable";
export const createApplication: CreateApplication = (config) => {
const { mode } = config;
const di = getDi();
runInAction(() => {
di.register(getInjectable({
id: "node-env",
instantiate: () => mode,
injectionToken: nodeEnvInjectionToken,
}));
registerInjectables(di);
});
return {
start: di.inject(startMainApplicationInjectable),
di,
};
};

View File

@ -1,14 +0,0 @@
/**
* 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 electronAppInjectable from "../electron-app.injectable";
const waitForElectronToBeReadyInjectable = getInjectable({
id: "wait-for-electron-to-be-ready",
instantiate: (di) => () => di.inject(electronAppInjectable).whenReady(),
});
export default waitForElectronToBeReadyInjectable;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
import { beforeElectronIsReadyInjectionToken } from "@k8slens/application-for-electron-main";
import requestSingleInstanceLockInjectable from "../features/request-single-instance-lock.injectable";
import exitAppInjectable from "../features/exit-app.injectable";

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import appNameInjectable from "../../../common/vars/app-name.injectable";
import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
import { beforeElectronIsReadyInjectionToken } from "@k8slens/application-for-electron-main";
import electronAppInjectable from "../electron-app.injectable";
const setupApplicationNameInjectable = getInjectable({

View File

@ -9,7 +9,7 @@ import loggerInjectable from "../../../common/logger.injectable";
import commandLineArgumentsInjectable from "../../utils/command-line-arguments.injectable";
import { pipeline } from "@ogre-tools/fp";
import { find, startsWith, toLower, map } from "lodash/fp";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
const setupDeepLinkingInjectable = getInjectable({

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import nodeEnvInjectionToken from "../../../common/vars/node-env-injection-token";
import { nodeEnvInjectionToken } from "../../../common/vars/node-env-injection-token";
import loggerInjectable from "../../../common/logger.injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
const setupDeveloperToolsInDevelopmentEnvironmentInjectable = getInjectable({
id: "setup-developer-tools-in-development-environment",

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import powerMonitorInjectable from "../features/power-monitor.injectable";
import exitAppInjectable from "../features/exit-app.injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
const setupDeviceShutdownInjectable = getInjectable({
id: "setup-device-shutdown",

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
import loggerInjectable from "../../../../common/logger.injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import electronAppInjectable from "../electron-app.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
import { beforeElectronIsReadyInjectionToken } from "@k8slens/application-for-electron-main";
import electronAppInjectable from "../electron-app.injectable";
import { runManyFor } from "../../../common/runnable/run-many-for";
import { afterWindowIsOpenedInjectionToken } from "../../start-main-application/runnable-tokens/after-window-is-opened-injection-token";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
import { beforeElectronIsReadyInjectionToken } from "@k8slens/application-for-electron-main";
import { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-front-end-injection-token";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
import electronAppInjectable from "../electron-app.injectable";

View File

@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createContainer } from "@ogre-tools/injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
export const getDi = () => {
const environment = "main";
const di = createContainer(environment);
registerMobX(di);
setLegacyGlobalDiForExtensionApi(di, environment);
return di;
};

View File

@ -5,7 +5,7 @@
import { chunk } from "lodash/fp";
import type { DiContainer } from "@ogre-tools/injectable";
import { isInjectable } from "@ogre-tools/injectable";
import { createContainer, isInjectable } from "@ogre-tools/injectable";
import spawnInjectable from "./child-process/spawn.injectable";
import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable";
import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable";
@ -28,14 +28,16 @@ import electronInjectable from "./utils/resolve-system-proxy/electron.injectable
import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable";
import type { GlobalOverride } from "../common/test-utils/get-global-override";
import { getOverrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
import { getDi } from "./getDi";
import {
setLegacyGlobalDiForExtensionApi,
} from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
const {
doGeneralOverrides = false,
} = opts;
export function getDiForUnitTesting() {
const di = createContainer("main");
const di = getDi();
registerMobX(di);
setLegacyGlobalDiForExtensionApi(di, "main");
di.preventSideEffects();
@ -50,7 +52,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
}
});
if (doGeneralOverrides) {
for (const globalOverridePath of global.injectablePaths.main.globalOverridePaths) {
const globalOverride = require(globalOverridePath).default as GlobalOverride;
@ -76,7 +77,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
on: jest.fn(),
} as never;
});
}
return di;
}

View File

@ -16,7 +16,7 @@ describe("Helm Service tests", () => {
let getActiveHelmRepositoriesMock: jest.Mock<Promise<AsyncResult<HelmRepo[]>>>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
getActiveHelmRepositoriesMock = jest.fn();

View File

@ -21,7 +21,7 @@ describe("exec-file-with-input", () => {
};
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.unoverride(execFileWithInputInjectable);

View File

@ -21,7 +21,7 @@ describe("get helm release resources", () => {
let execFileWithStreamInputMock: AsyncFnMock<ExecFileWithInput>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
execHelmMock = asyncFn();
execFileWithStreamInputMock = asyncFn();

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

@ -4,12 +4,12 @@
*/
// @experimental
export { afterApplicationIsLoadedInjectionToken } from "./start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
export { beforeApplicationIsLoadingInjectionToken } from "./start-main-application/runnable-tokens/before-application-is-loading-injection-token";
export { beforeElectronIsReadyInjectionToken } from "./start-main-application/runnable-tokens/before-electron-is-ready-injection-token";
export { onLoadOfApplicationInjectionToken } from "./start-main-application/runnable-tokens/on-load-of-application-injection-token";
export { createApplication } from "./create-app";
export type { CreateApplication, Application, ApplicationConfig } from "../common/create-app";
export type {
Environments,
} from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
export { registerLensCore } from "./register-lens-core";
export { nodeEnvInjectionToken } from "../common/vars/node-env-injection-token";
export * as Mobx from "mobx";
export * as mainExtensionApi from "../extensions/main-api";
export * as commonExtensionApi from "../extensions/common-api";

View File

@ -30,6 +30,7 @@ const consoleLoggerTransportInjectable = getInjectable({
),
}),
injectionToken: loggerTransportInjectionToken,
decorable: false,
});
export default consoleLoggerTransportInjectable;

Some files were not shown because too many files have changed in this diff Show More