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

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

No files matched your search

+7 -5
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",
@@ -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");
@@ -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;
@@ -43,7 +43,7 @@ describe("HotbarStore", () => {
let loggerMock: jest.Mocked<Logger>;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
testCluster = getMockCatalogEntity({
apiVersion: "v1",
@@ -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");
@@ -12,7 +12,7 @@ describe("kubernetesClusterCategory", () => {
let kubernetesClusterCategory: KubernetesClusterCategory;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
});
@@ -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:
@@ -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);
}
@@ -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));
}
@@ -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 });
@@ -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();
});
});
});
-17
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;
@@ -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,
@@ -15,7 +15,7 @@ describe("InitializableState tests", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
});
describe("when created", () => {
@@ -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");
@@ -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 = {
@@ -30,7 +30,7 @@ describe("KubeApi", () => {
let di: DiContainer;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting();
fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock);
@@ -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");
@@ -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 = {
@@ -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]);
},
});
@@ -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;
@@ -26,6 +26,8 @@ const loggerInjectable = getInjectable({
silly: (message, ...data) => baseLogger.silly(message, ...data),
};
},
decorable: false,
});
export default loggerInjectable;
+8
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[];
}
@@ -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();
@@ -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();
@@ -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",
@@ -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",
});
@@ -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",
@@ -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);
@@ -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());
@@ -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();
@@ -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);
@@ -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",
@@ -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",
@@ -23,6 +23,8 @@ describe("check-for-platform-updates", () => {
beforeEach(() => {
const di = getDiForUnitTesting();
di.unoverride(checkForPlatformUpdatesInjectable);
checkForUpdatesMock = asyncFn();
electronUpdaterFake = {
@@ -26,6 +26,8 @@ describe("download-platform-update", () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.unoverride(downloadPlatformUpdateInjectable);
downloadUpdateMock = asyncFn();
electronUpdaterOnMock = jest.fn();
electronUpdaterOffMock = jest.fn();
@@ -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";
@@ -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
@@ -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>
`;
@@ -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",
},
});
});
});
});
});
@@ -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();
});
});
});
@@ -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";
@@ -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({
@@ -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",
@@ -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", () => {
@@ -78,9 +78,12 @@ describe("showing details for helm release", () => {
});
builder.namespaces.add("some-namespace");
builder.namespaces.select("some-namespace");
builder.namespaces.add("some-namespace");
builder.namespaces.select("some-other-namespace");
builder.afterWindowStart(() => {
builder.namespaces.select("some-namespace");
builder.namespaces.select("some-other-namespace");
});
});
describe("given application is started", () => {
@@ -106,10 +109,9 @@ describe("showing details for helm release", () => {
});
it("calls for releases for each selected namespace", () => {
expect(requestHelmReleasesMock.mock.calls).toEqual([
["some-namespace"],
["some-other-namespace"],
]);
expect(requestHelmReleasesMock).toBeCalledTimes(2);
expect(requestHelmReleasesMock).toBeCalledWith("some-namespace");
expect(requestHelmReleasesMock).toBeCalledWith("some-other-namespace");
});
it("shows spinner", () => {
@@ -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({
@@ -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;
@@ -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();
@@ -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";
@@ -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", () => {
it("doesn't log errors, at least yet", () => {
expect(logErrorMock).not.toHaveBeenCalled();
});
describe("when a normal white-listed function is called with arguments", () => {
beforeEach(() => {
injectedWhiteListedFunction("some-arg", "some-other-arg");
whiteListedFunction("some-arg", "some-other-arg");
});
it("telemetry is emitted in event bus", () => {
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: [
{
someStaticProperty: "some-static-value",
someComputedProperty: "some-computed-value",
},
],
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"] },
});
});
});
});
});
});
@@ -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)) } : {}),
});
};
},
@@ -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,
}));
@@ -2,58 +2,81 @@
* 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);
instantiate: (diForDecorator) => ({
decorate:
(instantiateToBeDecorated: any) =>
(di: DiContainerForInjection, instantiationParameter: any) => {
const instance = instantiateToBeDecorated(di, instantiationParameter);
const whiteList = diForDecorator.inject(
telemetryWhiteListForFunctionsInjectable,
);
if (isFunction(instance)) {
return (...args: any[]) => {
const currentContext = di.context.at(-1);
const shouldEmitTelemetry = shouldEmitTelemetryFor(whiteList);
assert(currentContext);
return {
decorate:
(instantiateToBeDecorated: any) =>
(di: DiContainerForInjection, instantiationParameter: any) => {
const instance = instantiateToBeDecorated(di, instantiationParameter);
const emitTelemetry = diForDecorator.inject(
emitTelemetryInjectable,
);
if (isFunction(instance)) {
return (...args: any[]) => {
const currentContext = di.context.at(-1);
const logError = diForDecorator.inject(logErrorInjectable);
assert(currentContext);
const whiteList = diForDecorator.inject(
telemetryWhiteListForFunctionsInjectable,
);
if (shouldEmitTelemetry(currentContext.injectable)) {
emitTelemetry({ action: currentContext.injectable.id, args });
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,
);
}
return instance(...args);
};
}
emitTelemetry({
action: currentContext.injectable.id,
params,
});
}
return instance;
},
};
},
return instance(...args);
};
}
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;
@@ -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,
@@ -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";
@@ -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");
@@ -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);
@@ -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");
@@ -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");
@@ -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);
});
@@ -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",
@@ -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;
@@ -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");
@@ -65,7 +65,7 @@ describe("CatalogEntityRegistry", () => {
});
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
entityRegistry = di.inject(catalogEntityRegistryInjectable);
});
@@ -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 }));
@@ -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({
@@ -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),
}),
});
+6 -37
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);
}
@@ -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",
@@ -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) {
@@ -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",
},
],
});
});
});
});
});
@@ -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;
@@ -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");
});
});
@@ -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;
@@ -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");
});
});
-32
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,
};
};
@@ -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;
@@ -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";
@@ -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({
@@ -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({
@@ -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",
@@ -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",
@@ -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";
@@ -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({
@@ -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";
@@ -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";
-17
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;
};
+32 -32
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,34 +52,32 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
}
});
if (doGeneralOverrides) {
for (const globalOverridePath of global.injectablePaths.main.globalOverridePaths) {
const globalOverride = require(globalOverridePath).default as GlobalOverride;
for (const globalOverridePath of global.injectablePaths.main.globalOverridePaths) {
const globalOverride = require(globalOverridePath).default as GlobalOverride;
di.override(globalOverride.injectable, globalOverride.overridingInstantiate);
}
di.override(electronInjectable, () => ({}));
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
overrideRunnablesHavingSideEffects(di);
overrideElectronFeatures(di);
getOverrideFsWithFakes()(di);
di.override(clusterFramesInjectable, () => observable.map<string, ClusterFrameInfo>());
di.override(broadcastMessageInjectable, () => (channel) => {
throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`);
});
di.override(spawnInjectable, () => () => {
return {
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
on: jest.fn(),
} as never;
});
di.override(globalOverride.injectable, globalOverride.overridingInstantiate);
}
di.override(electronInjectable, () => ({}));
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
overrideRunnablesHavingSideEffects(di);
overrideElectronFeatures(di);
getOverrideFsWithFakes()(di);
di.override(clusterFramesInjectable, () => observable.map<string, ClusterFrameInfo>());
di.override(broadcastMessageInjectable, () => (channel) => {
throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`);
});
di.override(spawnInjectable, () => () => {
return {
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
on: jest.fn(),
} as never;
});
return di;
}
@@ -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();
@@ -21,7 +21,7 @@ describe("exec-file-with-input", () => {
};
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.unoverride(execFileWithInputInjectable);
@@ -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();
@@ -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",
+6 -6
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";
@@ -30,6 +30,7 @@ const consoleLoggerTransportInjectable = getInjectable({
),
}),
injectionToken: loggerTransportInjectionToken,
decorable: false,
});
export default consoleLoggerTransportInjectable;
@@ -23,6 +23,7 @@ const fileLoggerTranportInjectable = getInjectable({
tailable: true,
}),
injectionToken: loggerTransportInjectionToken,
decorable: false,
});
export default fileLoggerTranportInjectable;
@@ -37,7 +37,7 @@ describe("protocol router tests", () => {
let broadcastMessageMock: jest.Mock;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
@@ -2,20 +2,26 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { runInAction } from "mobx";
import type {
Environments } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import {
setLegacyGlobalDiForExtensionApi,
} from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
export function registerLensCore(di: DiContainer, environment: Environments) {
setLegacyGlobalDiForExtensionApi(di, environment);
export function registerInjectables(di: DiContainer) {
runInAction(() => {
autoRegister({
di,
targetModule: module,
getRequireContexts: () => [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES),
],
});
+1 -1
View File
@@ -28,7 +28,7 @@ describe("router", () => {
beforeEach(async () => {
routeHandlerMock = asyncFn();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const di = getDiForUnitTesting();
di.override(parseRequestInjectable, () => () => Promise.resolve({
payload: "some-payload",
Loaded 100 of 268 files, more files were not shown because too many files have changed in this diff. Show more