mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into enhancement-ability-to-remove-subnamespaces
This commit is contained in:
commit
55f7bebced
@ -27,7 +27,7 @@ See [Development](https://docs.k8slens.dev/contributing/development/) page.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](https://docs.k8slens.dev/contributing/) page.
|
||||
See [Contributing](https://docs.k8slens.dev/contributing/contribute-to-lens/) page.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Release Guide
|
||||
|
||||
Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`.
|
||||
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relavent commits from master.
|
||||
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relevant commits from master.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@ -11,9 +11,13 @@ All releases will be made by creating a PR which bumps the version field in the
|
||||
|
||||
## Steps
|
||||
|
||||
1. If you are making a minor or major release (or prereleases for one) make sure you are on the `master` branch.
|
||||
1. If you are making a minor or major release (or prereleases of one) make sure you are on the `master` branch.
|
||||
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
|
||||
1. Run `npm run create-release-pr <release-type>`. If you are making a subsequent prerelease release, provide the `--check-commits` flag.
|
||||
1. If you are checking the commits, type `y<ENTER>` to pick a commit, and `n<ENTER>` to skip it. You will want to skip the commits that were part of previous prerelease releases.
|
||||
1. Run `npm run create-release-pr`.
|
||||
1. Pick the PRs that you want to include in this release using the keys listed.
|
||||
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
|
||||
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
|
||||
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
|
||||
1. If you released a patch version, create a new patch milestone for the next patch version and move all the issues and PRs (open or closed) that weren't included in the current release to that milestone.
|
||||
1. Close the milestone related to the release that was just made (if not a prerelease release).
|
||||
1. If you released a patch version and it contains PRs that targeted `release/v<MAJOR>.<MINOR>` make a new PR targeting master and include all the relevant PRs as cherry-picks. This PR should have the `skip-changelog` label and have a milestone of the next minor.
|
||||
|
||||
2927
package-lock.json
generated
2927
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,6 +32,6 @@
|
||||
"adr": "^1.4.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"lerna": "^6.5.1",
|
||||
"rimraf": "^4.1.3"
|
||||
"rimraf": "^4.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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
packages/core/src/common/utils/enum.ts
Normal file
8
packages/core/src/common/utils/enum.ts
Normal 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>
|
||||
`;
|
||||
|
||||
234
packages/core/src/features/catalog/entity-running.test.tsx
Normal file
234
packages/core/src/features/catalog/entity-running.test.tsx
Normal file
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import asyncFn, { type AsyncFnMock } from "@async-fn/jest";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||
import type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||
import type { CatalogEntityActionContext } from "../../common/catalog";
|
||||
import { CatalogCategory, categoryVersion, CatalogEntity } from "../../common/catalog";
|
||||
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
|
||||
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import { flushPromises } from "../../common/test-utils/flush-promises";
|
||||
import { advanceFakeTime, testUsingFakeTime } from "../../common/test-utils/use-fake-time";
|
||||
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../renderer/api/catalog/entity/registry";
|
||||
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
|
||||
class MockCatalogCategory extends CatalogCategory {
|
||||
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||
kind = "CatalogCategory";
|
||||
metadata = {
|
||||
name: "mock",
|
||||
icon: "gear",
|
||||
};
|
||||
spec = {
|
||||
group: "entity.k8slens.dev",
|
||||
versions: [
|
||||
categoryVersion("v1alpha1", (() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
|
||||
return function (data: any) {
|
||||
const entity = new MockCatalogEntity(data);
|
||||
|
||||
entity.onRun = self.onRun;
|
||||
|
||||
return entity;
|
||||
} as any;
|
||||
})()),
|
||||
],
|
||||
names: {
|
||||
kind: "Mock",
|
||||
},
|
||||
};
|
||||
|
||||
constructor(private onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class MockCatalogEntity extends CatalogEntity {
|
||||
public apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||
public kind = "Mock";
|
||||
}
|
||||
|
||||
function createMockCatalogEntity() {
|
||||
return new MockCatalogEntity({
|
||||
metadata: {
|
||||
uid: "a_catalogEntity_uid",
|
||||
name: "a catalog entity",
|
||||
labels: {
|
||||
test: "label",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "",
|
||||
},
|
||||
spec: {},
|
||||
});
|
||||
}
|
||||
|
||||
describe("entity running technical tests", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let windowDi: DiContainer;
|
||||
let rendered: RenderResult;
|
||||
let appEventListener: jest.MockedFunction<(event: AppEvent) => void>;
|
||||
let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise<void>>;
|
||||
let catalogEntityRegistry: CatalogEntityRegistry;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.afterWindowStart((windowDi) => {
|
||||
onRun = jest.fn();
|
||||
|
||||
const catalogCategoryRegistery = windowDi.inject(catalogCategoryRegistryInjectable);
|
||||
|
||||
catalogCategoryRegistery.add(new MockCatalogCategory(onRun));
|
||||
|
||||
catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable);
|
||||
|
||||
const catalogEntityItem = createMockCatalogEntity();
|
||||
|
||||
catalogEntityRegistry.updateItems([catalogEntityItem]);
|
||||
|
||||
appEventListener = jest.fn();
|
||||
windowDi.inject(appEventBusInjectable).addListener(appEventListener);
|
||||
});
|
||||
|
||||
testUsingFakeTime();
|
||||
rendered = await builder.render();
|
||||
windowDi = builder.applicationWindow.only.di;
|
||||
});
|
||||
|
||||
describe("when navigated to catalog", () => {
|
||||
beforeEach(() => {
|
||||
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
|
||||
|
||||
navigateToCatalog();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when details panel is opened", () => {
|
||||
beforeEach(() => {
|
||||
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-a_catalogEntity_uid").click();
|
||||
advanceFakeTime(500);
|
||||
rendered.getByTestId("open-details-menu-item-for-a_catalogEntity_uid").click();
|
||||
advanceFakeTime(500);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
|
||||
let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
|
||||
|
||||
beforeEach(() => {
|
||||
onBeforeRunMock = asyncFn();
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
});
|
||||
|
||||
it("calls on before run event", () => {
|
||||
const target = onBeforeRunMock.mock.calls[0][0].target;
|
||||
const actual = { id: target.getId(), name: target.getName() };
|
||||
|
||||
expect(actual).toEqual({
|
||||
id: "a_catalogEntity_uid",
|
||||
name: "a catalog entity",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not call onRun yet", () => {
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when before run event resolves, calls onRun", async () => {
|
||||
await onBeforeRunMock.resolve();
|
||||
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
|
||||
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
|
||||
catalogEntityRegistry.addOnBeforeRun(() => {
|
||||
throw new Error("some error");
|
||||
});
|
||||
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
|
||||
onRun.mockImplementation(() => done());
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(async () => {});
|
||||
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
|
||||
catalogEntityRegistry.addOnBeforeRun(async (event) => event.preventDefault());
|
||||
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
|
||||
const onBeforeRunMock = asyncFn();
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||
|
||||
await onBeforeRunMock.reject();
|
||||
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits catalog open AppEvent", () => {
|
||||
expect(appEventListener).toHaveBeenCalledWith( {
|
||||
action: "open",
|
||||
name: "catalog",
|
||||
});
|
||||
});
|
||||
|
||||
it("emits catalog change AppEvent when changing the category", () => {
|
||||
rendered.getByText("Web Links").click();
|
||||
|
||||
expect(appEventListener).toHaveBeenCalledWith({
|
||||
action: "change-category",
|
||||
name: "catalog",
|
||||
params: {
|
||||
category: "Web Links",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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,10 +78,13 @@ describe("showing details for helm release", () => {
|
||||
});
|
||||
|
||||
builder.namespaces.add("some-namespace");
|
||||
builder.namespaces.select("some-namespace");
|
||||
builder.namespaces.add("some-namespace");
|
||||
|
||||
builder.afterWindowStart(() => {
|
||||
builder.namespaces.select("some-namespace");
|
||||
builder.namespaces.select("some-other-namespace");
|
||||
});
|
||||
});
|
||||
|
||||
describe("given application is started", () => {
|
||||
let rendered: RenderResult;
|
||||
@ -106,10 +109,9 @@ describe("showing details for helm release", () => {
|
||||
});
|
||||
|
||||
it("calls for releases for each selected namespace", () => {
|
||||
expect(requestHelmReleasesMock.mock.calls).toEqual([
|
||||
["some-namespace"],
|
||||
["some-other-namespace"],
|
||||
]);
|
||||
expect(requestHelmReleasesMock).toBeCalledTimes(2);
|
||||
expect(requestHelmReleasesMock).toBeCalledWith("some-namespace");
|
||||
expect(requestHelmReleasesMock).toBeCalledWith("some-other-namespace");
|
||||
});
|
||||
|
||||
it("shows spinner", () => {
|
||||
|
||||
@ -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", () => {
|
||||
beforeEach(() => {
|
||||
injectedWhiteListedFunction("some-arg", "some-other-arg");
|
||||
it("doesn't log errors, at least yet", () => {
|
||||
expect(logErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus", () => {
|
||||
describe("when a normal white-listed function is called with arguments", () => {
|
||||
beforeEach(() => {
|
||||
whiteListedFunction("some-arg", "some-other-arg");
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus without the arguments", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-white-listed-function",
|
||||
params: { args: ["some-arg", "some-other-arg"] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the white-listed function is called with MobX reactive content", () => {
|
||||
describe("when a white-listed function with a white-listed argument is called with arguments", () => {
|
||||
beforeEach(() => {
|
||||
whiteListedFunctionWithArgument("some-arg", "some-other-arg");
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus with the arguments as params", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
action: "telemetry-from-business-action",
|
||||
destination: "auto-capture",
|
||||
name: "some-white-listed-function-with-white-listed-argument",
|
||||
params: { someParam: "some-other-arg" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a white-listed function with a white-listed argument is called without arguments", () => {
|
||||
beforeEach(() => {
|
||||
whiteListedFunctionWithArgument();
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus without params", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
action: "telemetry-from-business-action",
|
||||
destination: "auto-capture",
|
||||
name: "some-white-listed-function-with-white-listed-argument",
|
||||
params: { someParam: undefined },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a faulty configuration, when a white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
whiteListedFunctionWithFaultyConfig();
|
||||
});
|
||||
|
||||
it("telemetry is still emitted in event bus, but with params indicating bad configuration, ", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
action: "telemetry-from-business-action",
|
||||
destination: "auto-capture",
|
||||
name: "some-white-listed-function-with-bad-config",
|
||||
params: { error: "Tried to produce params for telemetry, but getParams() threw an error" },
|
||||
});
|
||||
});
|
||||
|
||||
it("logs error", () => {
|
||||
expect(logErrorMock).toHaveBeenCalledWith(
|
||||
'Tried to produce params for telemetry of "some-white-listed-function-with-bad-config", but getParams() threw an error',
|
||||
expect.objectContaining({ message: "some-error-from-bad-configuration" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a white-listed function with a white-listed argument is called with MobX reactive content", () => {
|
||||
beforeEach(() => {
|
||||
const someComputedProperty = computed(() => "some-computed-value");
|
||||
|
||||
@ -96,22 +186,23 @@ describe("emit-telemetry-from-specific-function-calls", () => {
|
||||
someComputedProperty,
|
||||
};
|
||||
|
||||
injectedWhiteListedFunction(someObservable);
|
||||
whiteListedFunctionWithArgument(
|
||||
"irrelevant-argument",
|
||||
someObservable,
|
||||
);
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus without MobX internals or computeds", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-white-listed-function",
|
||||
name: "some-white-listed-function-with-white-listed-argument",
|
||||
|
||||
params: {
|
||||
args: [
|
||||
{
|
||||
someParam: {
|
||||
someStaticProperty: "some-static-value",
|
||||
someComputedProperty: "some-computed-value",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -119,28 +210,13 @@ describe("emit-telemetry-from-specific-function-calls", () => {
|
||||
|
||||
describe("when the non-white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
injectedNonWhiteListedFunction();
|
||||
nonWhiteListedFunction();
|
||||
});
|
||||
|
||||
it("telemetry is not emitted", () => {
|
||||
expect(emitEventMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the tagged, but not white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
injectedTaggedFunction("some-arg", "some-other-arg");
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-tagged-function",
|
||||
params: { args: ["some-arg", "some-other-arg"] },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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,35 +2,26 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type {
|
||||
DiContainerForInjection,
|
||||
Injectable,
|
||||
} from "@ogre-tools/injectable";
|
||||
import type { DiContainerForInjection } from "@ogre-tools/injectable";
|
||||
|
||||
import {
|
||||
lifecycleEnum,
|
||||
getInjectable,
|
||||
instantiationDecoratorToken,
|
||||
lifecycleEnum,
|
||||
} from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
import assert from "assert";
|
||||
import { isFunction } from "lodash/fp";
|
||||
import emitTelemetryInjectable from "./emit-telemetry.injectable";
|
||||
|
||||
import type { WhiteListItem } from "./telemetry-white-list-for-functions.injectable";
|
||||
import telemetryWhiteListForFunctionsInjectable from "./telemetry-white-list-for-functions.injectable";
|
||||
import logErrorInjectable from "../../../common/log-error.injectable";
|
||||
|
||||
const telemetryDecoratorInjectable = getInjectable({
|
||||
id: "telemetry-decorator",
|
||||
|
||||
instantiate: (diForDecorator) => {
|
||||
const emitTelemetry = diForDecorator.inject(emitTelemetryInjectable);
|
||||
|
||||
const whiteList = diForDecorator.inject(
|
||||
telemetryWhiteListForFunctionsInjectable,
|
||||
);
|
||||
|
||||
const shouldEmitTelemetry = shouldEmitTelemetryFor(whiteList);
|
||||
|
||||
return {
|
||||
instantiate: (diForDecorator) => ({
|
||||
decorate:
|
||||
(instantiateToBeDecorated: any) =>
|
||||
(di: DiContainerForInjection, instantiationParameter: any) => {
|
||||
@ -42,8 +33,41 @@ const telemetryDecoratorInjectable = getInjectable({
|
||||
|
||||
assert(currentContext);
|
||||
|
||||
if (shouldEmitTelemetry(currentContext.injectable)) {
|
||||
emitTelemetry({ action: currentContext.injectable.id, args });
|
||||
const emitTelemetry = diForDecorator.inject(
|
||||
emitTelemetryInjectable,
|
||||
);
|
||||
|
||||
const logError = diForDecorator.inject(logErrorInjectable);
|
||||
|
||||
const whiteList = diForDecorator.inject(
|
||||
telemetryWhiteListForFunctionsInjectable,
|
||||
);
|
||||
|
||||
const whiteListMap = getWhiteListMap(whiteList);
|
||||
|
||||
const whiteListed = whiteListMap.get(currentContext.injectable.id);
|
||||
|
||||
if (whiteListed) {
|
||||
let params;
|
||||
|
||||
try {
|
||||
params = whiteListed.getParams(...args);
|
||||
} catch (e) {
|
||||
params = {
|
||||
error:
|
||||
"Tried to produce params for telemetry, but getParams() threw an error",
|
||||
};
|
||||
|
||||
logError(
|
||||
`Tried to produce params for telemetry of "${currentContext.injectable.id}", but getParams() threw an error`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
emitTelemetry({
|
||||
action: currentContext.injectable.id,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
return instance(...args);
|
||||
@ -52,8 +76,7 @@ const telemetryDecoratorInjectable = getInjectable({
|
||||
|
||||
return instance;
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
decorable: false,
|
||||
// Todo: this is required because of imperfect typing in injectable.
|
||||
@ -61,9 +84,23 @@ const telemetryDecoratorInjectable = getInjectable({
|
||||
injectionToken: instantiationDecoratorToken,
|
||||
});
|
||||
|
||||
const shouldEmitTelemetryFor =
|
||||
(whiteList: string[]) => (injectable: Injectable<any, any, any>) =>
|
||||
injectable.tags?.includes("emit-telemetry") ||
|
||||
whiteList.includes(injectable.id);
|
||||
const getWhiteListMap = (whiteList: WhiteListItem[]) =>
|
||||
new Map(
|
||||
whiteList.map((item) =>
|
||||
typeof item === "string"
|
||||
? [
|
||||
item,
|
||||
{
|
||||
getParams: () => undefined,
|
||||
},
|
||||
]
|
||||
: [
|
||||
item.id,
|
||||
{
|
||||
getParams: item.getParams,
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
export default telemetryDecoratorInjectable;
|
||||
|
||||
@ -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),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
160
packages/core/src/main/cluster/update-entity-metadata.test.ts
Normal file
160
packages/core/src/main/cluster/update-entity-metadata.test.ts
Normal file
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
|
||||
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { KubernetesCluster } from "../../common/catalog-entities";
|
||||
import { ClusterMetadataKey } from "../../common/cluster-types";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
|
||||
import updateEntityMetadataInjectable from "./update-entity-metadata.injectable";
|
||||
|
||||
describe("update-entity-metadata", () => {
|
||||
let cluster: Cluster;
|
||||
let entity: KubernetesCluster;
|
||||
let updateEntityMetadata: UpdateEntityMetadata;
|
||||
let detectedMetadata: Record<ClusterMetadataKey, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
||||
di.override(appPathsStateInjectable, () => ({
|
||||
get: () => ({} as AppPaths),
|
||||
set: () => {},
|
||||
}));
|
||||
const createCluster = di.inject(createClusterInjectionToken);
|
||||
|
||||
updateEntityMetadata = di.inject(updateEntityMetadataInjectable);
|
||||
|
||||
cluster = createCluster({
|
||||
id: "some-id",
|
||||
contextName: "some-context",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
}, {
|
||||
clusterServerUrl: "foo",
|
||||
});
|
||||
|
||||
detectedMetadata = {
|
||||
[ClusterMetadataKey.CLUSTER_ID]: "some-cluster-id",
|
||||
[ClusterMetadataKey.DISTRIBUTION]: "some-distribution",
|
||||
[ClusterMetadataKey.VERSION]: "some-version",
|
||||
[ClusterMetadataKey.LAST_SEEN]: "some-date",
|
||||
[ClusterMetadataKey.NODES_COUNT]: 42,
|
||||
[ClusterMetadataKey.PROMETHEUS]: {
|
||||
"some-parameter": "some-value",
|
||||
},
|
||||
};
|
||||
|
||||
cluster.metadata = {
|
||||
...cluster.metadata,
|
||||
};
|
||||
|
||||
entity = new KubernetesCluster({
|
||||
metadata: {
|
||||
uid: "some-uid",
|
||||
name: "some-name",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
kubeconfigContext: "some-context",
|
||||
kubeconfigPath: "/some/path/to/kubeconfig",
|
||||
},
|
||||
status: {
|
||||
phase: "connecting",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("given cluster metadata has no some last seen timestamp, does not update entity metadata with last seen timestamp", () => {
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.lastSeen).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("given cluster metadata has some last seen timestamp, updates entity metadata with last seen timestamp", () => {
|
||||
cluster.metadata[ClusterMetadataKey.LAST_SEEN] = detectedMetadata[ClusterMetadataKey.LAST_SEEN];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.lastSeen).toEqual("some-date");
|
||||
});
|
||||
|
||||
it("given cluster metadata has some version, updates entity metadata with version", () => {
|
||||
cluster.metadata[ClusterMetadataKey.VERSION] = detectedMetadata[ClusterMetadataKey.VERSION];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.version).toEqual("some-version");
|
||||
});
|
||||
|
||||
it("given cluster metadata has nodes count, updates entity metadata with node count", () => {
|
||||
cluster.metadata[ClusterMetadataKey.NODES_COUNT] = detectedMetadata[ClusterMetadataKey.NODES_COUNT];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.nodes).toEqual(42);
|
||||
});
|
||||
|
||||
it("given cluster metadata has prometheus data, updates entity metadata with prometheus data", () => {
|
||||
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = detectedMetadata[ClusterMetadataKey.PROMETHEUS];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.prometheus).toEqual({
|
||||
"some-parameter": "some-value",
|
||||
});
|
||||
});
|
||||
|
||||
it("given cluster metadata has distribution, updates entity metadata with distribution", () => {
|
||||
cluster.metadata[ClusterMetadataKey.DISTRIBUTION] = detectedMetadata[ClusterMetadataKey.DISTRIBUTION];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.distribution).toEqual("some-distribution");
|
||||
});
|
||||
|
||||
it("given cluster metadata has cluster id, updates entity metadata with cluster id", () => {
|
||||
cluster.metadata[ClusterMetadataKey.CLUSTER_ID] = detectedMetadata[ClusterMetadataKey.CLUSTER_ID];
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.id).toEqual("some-cluster-id");
|
||||
});
|
||||
|
||||
it("given cluster metadata has no kubernetes version, updates entity metadata with 'unknown' kubernetes version", () => {
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.kubeVersion).toEqual("unknown");
|
||||
});
|
||||
|
||||
it("given cluster metadata has kubernetes version, updates entity metadata with kubernetes version", () => {
|
||||
cluster.metadata.version = "some-kubernetes-version";
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.kubeVersion).toEqual("some-kubernetes-version");
|
||||
});
|
||||
|
||||
it("given cluster has labels, updates entity metadata with labels", () => {
|
||||
cluster.labels = {
|
||||
"some-label": "some-value",
|
||||
};
|
||||
entity.metadata.labels = {
|
||||
"some-other-label": "some-other-value",
|
||||
};
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.labels).toEqual({
|
||||
"some-label": "some-value",
|
||||
"some-other-label": "some-other-value",
|
||||
});
|
||||
});
|
||||
|
||||
it("given cluster has labels, overwrites entity metadata with cluster labels", () => {
|
||||
cluster.labels = {
|
||||
"some-label": "some-cluster-value",
|
||||
};
|
||||
entity.metadata.labels = {
|
||||
"some-label": "some-entity-value",
|
||||
};
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.labels).toEqual({
|
||||
"some-label": "some-cluster-value",
|
||||
});
|
||||
});
|
||||
|
||||
it("give cluster preferences has name, updates entity metadata with name", () => {
|
||||
cluster.preferences.clusterName = "some-cluster-name";
|
||||
|
||||
updateEntityMetadata(entity, cluster);
|
||||
expect(entity.metadata.name).toEqual("some-cluster-name");
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
154
packages/core/src/main/cluster/update-entity-spec.test.ts
Normal file
154
packages/core/src/main/cluster/update-entity-spec.test.ts
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
|
||||
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { KubernetesCluster } from "../../common/catalog-entities";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
|
||||
import updateEntitySpecInjectable from "./update-entity-spec.injectable";
|
||||
|
||||
describe("update-entity-spec", () => {
|
||||
let cluster: Cluster;
|
||||
let entity: KubernetesCluster;
|
||||
let updateEntitySpec: UpdateEntitySpec;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
||||
di.override(appPathsStateInjectable, () => ({
|
||||
get: () => ({} as AppPaths),
|
||||
set: () => {},
|
||||
}));
|
||||
const createCluster = di.inject(createClusterInjectionToken);
|
||||
|
||||
updateEntitySpec = di.inject(updateEntitySpecInjectable);
|
||||
|
||||
cluster = createCluster({
|
||||
id: "some-id",
|
||||
contextName: "some-context",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
}, {
|
||||
clusterServerUrl: "foo",
|
||||
});
|
||||
|
||||
entity = new KubernetesCluster({
|
||||
metadata: {
|
||||
uid: "some-uid",
|
||||
name: "some-name",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
kubeconfigContext: "some-context",
|
||||
kubeconfigPath: "/some/path/to/kubeconfig",
|
||||
},
|
||||
status: {
|
||||
phase: "connecting",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("given cluster has icon, updates entity spec with icon", () => {
|
||||
cluster.preferences.icon = "some-icon";
|
||||
updateEntitySpec(entity, cluster);
|
||||
expect(entity.spec.icon?.src).toEqual("some-icon");
|
||||
});
|
||||
|
||||
it("given cluster icon is null, deletes icon from both", () => {
|
||||
cluster.preferences.icon = null;
|
||||
entity.spec.icon = { src : "some-icon" };
|
||||
|
||||
updateEntitySpec(entity, cluster);
|
||||
expect(entity.spec.icon).toBeUndefined();
|
||||
expect(cluster.preferences.icon).toBeUndefined();
|
||||
});
|
||||
|
||||
it("given entity has no metrics, adds source as local", () => {
|
||||
updateEntitySpec(entity, cluster);
|
||||
expect(entity.spec.metrics?.source).toEqual("local");
|
||||
});
|
||||
|
||||
it("given entity has metrics, does not change source", () => {
|
||||
entity.spec.metrics = { source: "some-source" };
|
||||
entity.spec.metrics.prometheus = {
|
||||
address: {
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
},
|
||||
};
|
||||
|
||||
cluster.preferences.prometheus = {
|
||||
namespace: "some-other-namespace",
|
||||
port: 666,
|
||||
service: "some-other-service",
|
||||
prefix: "some-other-prefix",
|
||||
};
|
||||
|
||||
updateEntitySpec(entity, cluster);
|
||||
|
||||
expect(entity.spec.metrics?.source).toEqual("some-source");
|
||||
expect(entity.spec.metrics?.prometheus?.address).toEqual({
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
});
|
||||
});
|
||||
|
||||
it("given entity has local prometheus source, updates entity spec with prometheus provider", () => {
|
||||
entity.spec.metrics = { source: "local" };
|
||||
|
||||
cluster.preferences.prometheusProvider = {
|
||||
type: "some-prometheus-provider-type",
|
||||
};
|
||||
cluster.preferences.prometheus = {
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
};
|
||||
|
||||
updateEntitySpec(entity, cluster);
|
||||
expect(entity.spec.metrics?.prometheus?.address).toEqual({
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
});
|
||||
|
||||
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
|
||||
});
|
||||
|
||||
it("given entity has no metrics, updates entity spec with prometheus provider", () => {
|
||||
expect(entity.spec.metrics).toBeUndefined();
|
||||
|
||||
cluster.preferences.prometheusProvider = {
|
||||
type: "some-prometheus-provider-type",
|
||||
};
|
||||
cluster.preferences.prometheus = {
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
};
|
||||
|
||||
updateEntitySpec(entity, cluster);
|
||||
|
||||
expect(entity.spec.metrics?.prometheus?.address).toEqual({
|
||||
namespace: "some-namespace",
|
||||
port: 42,
|
||||
service: "some-service",
|
||||
prefix: "some-prefix",
|
||||
});
|
||||
|
||||
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
|
||||
});
|
||||
|
||||
});
|
||||
@ -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";
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import { chunk } from "lodash/fp";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { isInjectable } from "@ogre-tools/injectable";
|
||||
import { createContainer, isInjectable } from "@ogre-tools/injectable";
|
||||
import spawnInjectable from "./child-process/spawn.injectable";
|
||||
import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable";
|
||||
import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable";
|
||||
@ -28,14 +28,16 @@ import electronInjectable from "./utils/resolve-system-proxy/electron.injectable
|
||||
import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable";
|
||||
import type { GlobalOverride } from "../common/test-utils/get-global-override";
|
||||
import { getOverrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
|
||||
import { getDi } from "./getDi";
|
||||
import {
|
||||
setLegacyGlobalDiForExtensionApi,
|
||||
} from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
|
||||
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
||||
const {
|
||||
doGeneralOverrides = false,
|
||||
} = opts;
|
||||
export function getDiForUnitTesting() {
|
||||
const di = createContainer("main");
|
||||
|
||||
const di = getDi();
|
||||
registerMobX(di);
|
||||
setLegacyGlobalDiForExtensionApi(di, "main");
|
||||
|
||||
di.preventSideEffects();
|
||||
|
||||
@ -50,7 +52,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
}
|
||||
});
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
for (const globalOverridePath of global.injectablePaths.main.globalOverridePaths) {
|
||||
const globalOverride = require(globalOverridePath).default as GlobalOverride;
|
||||
|
||||
@ -76,7 +77,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
on: jest.fn(),
|
||||
} as never;
|
||||
});
|
||||
}
|
||||
|
||||
return di;
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user