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

Fix merge conflicts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-05-17 13:33:39 +03:00
parent 7a89c8f74c
commit c0cc56dba2
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
59 changed files with 358 additions and 469 deletions

View File

@ -340,7 +340,7 @@ exports[`helm-charts - navigation to Helm charts when navigating to Helm charts
class="items box grow flex column"
>
<div
class="Table flex column HelmCharts box grow some-active-theme-type selectable scrollable sortable autoSize virtual"
class="Table flex column HelmCharts box grow dark selectable scrollable sortable autoSize virtual"
>
<div
class="TableHead sticky nowrap topLine"
@ -430,7 +430,7 @@ exports[`helm-charts - navigation to Helm charts when navigating to Helm charts
>
<i
class="Icon material interactive focusable"
id="menu_actions_15"
id="menu_actions_17"
tabindex="0"
>
<span

View File

@ -10,8 +10,6 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import currentPathInjectable from "../../renderer/routes/current-path.injectable";
import { routeInjectionToken } from "../../common/front-end-routing/route-injection-token";
import { computed } from "mobx";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { preferenceNavigationItemInjectionToken } from "../../renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable";
import routeIsActiveInjectable from "../../renderer/routes/route-is-active.injectable";
import { Preferences } from "../../renderer/components/+preferences";
@ -24,7 +22,6 @@ import { createObservableHistory } from "mobx-observable-history";
import navigateToPreferenceTabInjectable from "../../renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable";
import navigateToFrontPageInjectable from "../../common/front-end-routing/navigate-to-front-page.injectable";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - closing-preferences", () => {
let applicationBuilder: ApplicationBuilder;
@ -39,16 +36,6 @@ describe("preferences - closing-preferences", () => {
rendererDi.register(testFrontPageRouteComponentInjectable);
rendererDi.register(testNavigationItemInjectable);
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
rendererDi.override(navigateToFrontPageInjectable, (di) => {
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const testFrontPage = di.inject(testFrontPageRouteInjectable);

View File

@ -5,28 +5,13 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import navigateToProxyPreferencesInjectable from "../../common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to application preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in some child page of preferences, when rendered", () => {

View File

@ -5,28 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to editor preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
editorConfiguration: { minimap: {}, tabSize: 42, fontSize: 42 },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,30 +5,15 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import React from "react";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to extension specific preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,29 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import { observable } from "mobx";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to kubernetes preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,27 +5,12 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to proxy preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -8,29 +8,14 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import navigateToTelemetryPreferencesInjectable from "../../common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable";
import sentryDnsUrlInjectable from "../../renderer/components/+preferences/sentry-dns-url.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to telemetry preferences", () => {
let applicationBuilder: ApplicationBuilder;
beforeEach(() => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
describe("given in preferences, when rendered", () => {

View File

@ -5,11 +5,7 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import { observable } from "mobx";
import defaultShellInjectable from "../../renderer/components/+preferences/default-shell.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to terminal preferences", () => {
let applicationBuilder: ApplicationBuilder;
@ -18,18 +14,7 @@ describe("preferences - navigation to terminal preferences", () => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
terminalConfig: { fontSize: 42 },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(defaultShellInjectable, () => "some-default-shell");
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});

View File

@ -6,10 +6,6 @@
import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
@ -17,19 +13,6 @@ describe("preferences - navigation using application menu", () => {
beforeEach(async () => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ rendererDi, mainDi }) => {
mainDi.override(isAutoUpdateEnabledInjectable, () => () => false);
const userStoreStub = {
extensionRegistryUrl: { customUrl: "some-custom-url" },
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
rendered = await applicationBuilder.render();
});

View File

@ -284,7 +284,7 @@ describe("HotbarStore", () => {
});
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(async () => {
beforeEach(() => {
const configurationToBeMigrated = {
"some-directory-for-user-data": {
"lens-hotbar-store.json": JSON.stringify({
@ -351,8 +351,6 @@ describe("HotbarStore", () => {
di.override(appVersionInjectable, () => "5.0.0-beta.10");
await di.runSetups();
hotbarStore = di.inject(hotbarStoreInjectable);
hotbarStore.load();

View File

@ -1,14 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { catalogCategoryRegistry } from "./catalog-category-registry";
const catalogCategoryRegistryInjectable = getInjectable({
id: "catalog-category-registry",
instantiate: () => catalogCategoryRegistry,
causesSideEffects: true,
});
export default catalogCategoryRegistryInjectable;

View File

@ -10,6 +10,7 @@ export type HasCategoryForEntity = (data: CatalogEntityData & CatalogEntityKindD
const hasCategoryForEntityInjectable = getInjectable({
id: "has-category-for-entity",
instantiate: (di): HasCategoryForEntity => {
const registry = di.inject(catalogCategoryRegistryInjectable);

View File

@ -30,8 +30,8 @@ export interface ClusterDependencies {
readonly directoryForKubeConfigs: string;
readonly logger: Logger;
readonly detectorRegistry: DetectorRegistry;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager | undefined;
createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces;

View File

@ -23,11 +23,9 @@ const mockFetch = fetch as FetchMock;
describe("forRemoteCluster", () => {
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
di.override(apiManagerInjectable, () => apiManager);
@ -87,11 +85,9 @@ describe("KubeApi", () => {
let request: KubeJsonApi;
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
request = new KubeJsonApi({
serverAddress: `http://127.0.0.1:9999`,
apiBase: "/api-kube",

View File

@ -45,7 +45,7 @@ describe("runManyFor", () => {
const runMany = runManyFor(rootDi)(someInjectionTokenForRunnables);
actualPromise = runMany();
actualPromise = runMany() as Promise<void>;
});
it("runs all runnables at the same time", () => {
@ -66,7 +66,7 @@ describe("runManyFor", () => {
});
describe("given hierarchy that is three levels deep, when running many", () => {
let runMock: AsyncFnMock<(arg: string) => Promise<void>>;
let runMock: AsyncFnMock<(...args: unknown[]) => Promise<void>>;
let actualPromise: Promise<void>;
beforeEach(() => {
@ -110,7 +110,7 @@ describe("runManyFor", () => {
const runMany = runManyFor(di)(someInjectionTokenForRunnables);
actualPromise = runMany();
actualPromise = runMany() as Promise<void>;
});
it("runs first level runnables", () => {
@ -173,7 +173,7 @@ describe("runManyFor", () => {
});
describe("when running many with parameter", () => {
let runMock: AsyncFnMock<(arg: string, arg2: string) => Promise<void>>;
let runMock: AsyncFnMock<(...args: unknown[]) => Promise<void>>;
beforeEach(() => {
const rootDi = createContainer();

View File

@ -10,35 +10,37 @@ import type {
import { filter, map } from "lodash/fp";
export interface Runnable<TParameter = void> {
run: (parameter: TParameter) => Promise<void> | void;
run: Run<TParameter>;
runAfter?: this;
}
export const runManyFor =
(di: DiContainerForInjection) =>
<TRunnable extends Runnable<unknown>>(
injectionToken: InjectionToken<TRunnable, void>,
type Run<Param> = (parameter: Param) => Promise<void> | void;
export type RunMany = <Param>(
injectionToken: InjectionToken<Runnable<Param>, void>
) => Run<Param>;
export function runManyFor(di: DiContainerForInjection): RunMany {
return (injectionToken) => async (parameter) => {
const allRunnables = di.injectMany(injectionToken);
const recursedRun = async (
runAfterRunnable: Runnable<any> | undefined = undefined,
) =>
async (parameter: Parameters<TRunnable["run"]>[0]) => {
const allRunnables = di.injectMany(injectionToken);
await pipeline(
allRunnables,
const recursedRun = async (runAfterRunnable: Runnable<unknown> = undefined) =>
await pipeline(
allRunnables,
filter((runnable) => runnable.runAfter === runAfterRunnable),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map(async (runnable) => {
await runnable.run(parameter);
await recursedRun(runnable);
}),
promises => Promise.all(promises),
);
await recursedRun();
};
map(async (runnable) => {
await runnable.run(parameter);
await recursedRun(runnable);
}),
(promises) => Promise.all(promises),
);
await recursedRun();
};
}

View File

@ -8,32 +8,38 @@ import type {
InjectionToken,
} from "@ogre-tools/injectable";
import { filter, map } from "lodash/fp";
import type { Runnable } from "./run-many-for";
export interface RunnableSync<TParameter = void> {
run: (parameter: TParameter) => void;
run: RunSync<TParameter>;
runAfter?: this;
}
export const runManySyncFor =
(di: DiContainerForInjection) =>
<TRunnable extends RunnableSync<unknown>>(
injectionToken: InjectionToken<TRunnable, void>,
type RunSync<Param> = (parameter: Param) => void;
export type RunManySync = <Param>(
injectionToken: InjectionToken<Runnable<Param>, void>
) => RunSync<Param>;
export function runManySyncFor(di: DiContainerForInjection): RunManySync {
return (injectionToken) => async (parameter) => {
const allRunnables = di.injectMany(injectionToken);
const recursedRun = (
runAfterRunnable: RunnableSync<any> | undefined = undefined,
) =>
(parameter: Parameters<TRunnable["run"]>[0]) => {
const allRunnables = di.injectMany(injectionToken);
pipeline(
allRunnables,
const recursedRun = (runAfterRunnable: RunnableSync<unknown> = undefined) =>
pipeline(
allRunnables,
filter((runnable) => runnable.runAfter === runAfterRunnable),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map((runnable) => {
runnable.run(parameter);
map((runnable) => {
runnable.run(parameter);
recursedRun(runnable);
}),
);
recursedRun(runnable);
}),
);
recursedRun();
};
recursedRun();
};
}

View File

@ -33,5 +33,11 @@ export const getLegacyGlobalDiForExtensionApi = () => {
};
export function getEnvironmentSpecificLegacyGlobalDiForExtensionApi(environment: Environments) {
return legacyGlobalDis.get(environment);
const di = legacyGlobalDis.get(environment);
if (!di) {
throw new Error("Tried to get DI container using legacy globals in environment which doesn't exist");
}
return di;
}

View File

@ -35,7 +35,7 @@ describe("get-electron-app-path", () => {
di.override(registerChannelInjectable, () => () => undefined);
di.override(joinPathsInjectable, () => joinPathsFake);
getElectronAppPath = di.inject(getElectronAppPathInjectable);
getElectronAppPath = di.inject(getElectronAppPathInjectable) as (name: string) => string;
});
it("given app path exists, when called, returns app path", () => {

View File

@ -7,7 +7,7 @@ import directoryForKubeConfigsInjectable from "../../../common/app-paths/directo
import { KubeconfigSyncManager } from "./manager";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import clusterManagerInjectable from "../../cluster-manager.injectable";
import catalogEntityRegistryInjectable from "../../catalog/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
const kubeconfigSyncManagerInjectable = getInjectable({
id: "kubeconfig-sync-manager",

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import { syncWeblinks } from "./weblinks";
import weblinkStoreInjectable from "../../common/weblink-store.injectable";
import catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const syncWeblinksInjectable = getInjectable({
id: "sync-weblinks",

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable";
import { startCatalogSyncToRenderer } from "../catalog-pusher";
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const catalogSyncToRendererInjectable = getInjectable({
id: "catalog-sync-to-renderer",

View File

@ -4,14 +4,13 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import hasCategoryForEntityInjectable from "../../common/catalog/has-category-for-entity.injectable";
import { CatalogEntityRegistry } from "./catalog-entity-registry";
import catalogCategoryRegistryInjectable from "../../common/catalog/catalog-category-registry.injectable";
import { CatalogEntityRegistry } from "./entity-registry";
const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry",
instantiate: (di) => new CatalogEntityRegistry({
hasCategoryForEntity: di.inject(hasCategoryForEntityInjectable),
categoryRegistryRegistry: di.inject(catalogCategoryRegistryInjectable);
}),
});

View File

@ -13,7 +13,7 @@ interface Dependencies {
k8sRequest: K8sRequest;
}
export type DetectorConstructor = new (cluster: Cluster) => BaseClusterDetector;
export type DetectorConstructor = new (cluster: Cluster, k8sRequest: K8sRequest) => BaseClusterDetector;
export class DetectorRegistry {
constructor(private dependencies: Dependencies) {}

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import { ClusterManager } from "./cluster-manager";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
import catalogEntityRegistryInjectable from "./catalog/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "./catalog/entity-registry.injectable";
const clusterManagerInjectable = getInjectable({
id: "cluster-manager",

View File

@ -10,13 +10,13 @@ import type { Cluster } from "../common/cluster/cluster";
import logger from "./logger";
import { apiKubePrefix } from "../common/vars";
import { getClusterIdFromHost, isErrnoException } from "../common/utils";
import type { catalogEntityRegistry } from "./catalog";
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";
import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { ClusterId } from "../common/cluster-types";
import type { CatalogEntityRegistry } from "./catalog";
const logPrefix = "[CLUSTER-MANAGER]:";
@ -261,7 +261,7 @@ export class ClusterManager {
});
}
getClusterForRequest(req: http.IncomingMessage): Cluster | undefined {
getClusterForRequest = (req: http.IncomingMessage): Cluster | undefined => {
if (!req.headers.host) {
return undefined;
}

View File

@ -19,7 +19,7 @@ const createContextHandlerInjectable = getInjectable({
const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
return (cluster: Cluster): ClusterContextHandler | undefined => {
return (cluster: Cluster): ClusterContextHandler => {
const clusterUrl = new URLParse(cluster.apiUrl);
const dependencies = {

View File

@ -9,10 +9,11 @@ import loggerInjectable from "../../../../common/logger.injectable";
import clusterManagerInjectable from "../../../cluster-manager.injectable";
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
import catalogEntityRegistryInjectable from "../../../catalog/catalog-entity-registry.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 operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
const setupIpcMainHandlersInjectable = getInjectable({
id: "setup-ipc-main-handlers",
@ -30,6 +31,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
const askUserForFilePaths = di.inject(askUserForFilePathsInjectable);
return {
run: () => {
@ -43,6 +45,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
catalogEntityRegistry,
clusterStore,
operatingSystemTheme,
askUserForFilePaths,
});
},
};

View File

@ -21,9 +21,9 @@ import type { MenuItemOpts } from "../../../menu/application-menu-items.injectab
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
import { openFilePickingDialogChannel } from "../../../../common/ipc/dialog";
import { showOpenDialog } from "../../../ipc/dialog";
import { getNativeThemeChannel } from "../../../../common/ipc/native-theme";
import type { Theme } from "../../../theme/operating-system-theme-state.injectable";
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
interface Dependencies {
directoryForLensLocalStorage: string;
@ -33,9 +33,10 @@ interface Dependencies {
catalogEntityRegistry: CatalogEntityRegistry;
clusterStore: ClusterStore;
operatingSystemTheme: IComputedValue<Theme>;
askUserForFilePaths: AskUserForFilePaths;
}
export const setupIpcMainHandlers = ({ applicationMenuItems, directoryForLensLocalStorage, getAbsolutePath, clusterManager, catalogEntityRegistry, clusterStore, operatingSystemTheme }: Dependencies) => {
export const setupIpcMainHandlers = ({ applicationMenuItems, directoryForLensLocalStorage, getAbsolutePath, clusterManager, catalogEntityRegistry, clusterStore, operatingSystemTheme, askUserForFilePaths }: Dependencies) => {
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
return ClusterStore.getInstance()
.getById(clusterId)
@ -149,7 +150,7 @@ export const setupIpcMainHandlers = ({ applicationMenuItems, directoryForLensLoc
ipcMainOn(windowLocationChangedChannel, () => onLocationChange());
ipcMainHandle(openFilePickingDialogChannel, (event, opts) => showOpenDialog(opts));
ipcMainHandle(openFilePickingDialogChannel, (event, opts) => askUserForFilePaths(opts));
ipcMainHandle(broadcastMainChannel, (event, channel, ...args) => broadcastMessage(channel, ...args));

View File

@ -31,7 +31,6 @@ import joinPathsInjectable from "../common/path/join-paths.injectable";
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import type { GetDiForUnitTestingOptions } from "../test-utils/get-dis-for-unit-testing";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import isAutoUpdateEnabledInjectable from "./is-auto-update-enabled.injectable";
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
import { EventEmitter } from "../common/event-emitter";
@ -41,8 +40,6 @@ import initializeExtensionsInjectable from "./start-main-application/runnables/i
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
import registerFileProtocolInjectable from "./electron-app/features/register-file-protocol.injectable";
import environmentVariablesInjectable from "../common/utils/environment-variables.injectable";
import { CatalogCategoryRegistry } from "../common/catalog";
import catalogCategoryRegistryInjectable from "../common/catalog/catalog-category-registry.injectable";
import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable";
import setupLensProxyInjectable from "./start-main-application/runnables/setup-lens-proxy.injectable";
import setupRunnablesForAfterRootFrameIsReadyInjectable from "./start-main-application/runnables/setup-runnables-for-after-root-frame-is-ready.injectable";
@ -125,8 +122,6 @@ export function getDiForUnitTesting(opts: GetDiForUnitTestingOptions = {}) {
di.override(trayInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(applicationMenuInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(catalogCategoryRegistryInjectable, () => new CatalogCategoryRegistry());
// TODO: Remove usages of globally exported appEventBus to get rid of this
di.override(appEventBusInjectable, () => new EventEmitter<[AppEvent]>());
@ -229,7 +224,7 @@ const overrideElectronFeatures = (di: DiContainer) => {
close: () => {},
send: (arg) => {
const sendFake = di.inject(sendToChannelInElectronBrowserWindowInjectable);
const sendFake = di.inject(sendToChannelInElectronBrowserWindowInjectable) as any;
sendFake(null, arg);
},

View File

@ -12,10 +12,14 @@ import logger from "../logger";
import { execHelm } from "./exec";
import type { HelmEnv, HelmRepo, HelmRepoConfig } from "./helm-repo-manager";
class HelmRepoManager {
protected repos: HelmRepo[];
protected helmEnv: HelmEnv;
protected didUpdateOnce: boolean;
interface EnsuredHelmRepoManagerData {
helmEnv: HelmEnv;
didUpdateOnce: boolean;
}
export class HelmRepoManager {
protected helmEnv?: HelmEnv;
protected didUpdateOnce?: boolean;
public async loadAvailableRepos(): Promise<HelmRepo[]> {
const res = await customRequestPromise({
@ -25,13 +29,13 @@ class HelmRepoManager {
timeout: 10000,
});
return orderBy(res.body as HelmRepo[], (repo) => repo.name);
return orderBy(res.body as HelmRepo[], repo => repo.name);
}
private async ensureInitialized() {
private async ensureInitialized(): Promise<EnsuredHelmRepoManagerData> {
this.helmEnv ??= await this.parseHelmEnv();
const repos = await this.list();
const repos = await this.list(this.helmEnv);
if (repos.length === 0) {
await this.addRepo({
@ -44,12 +48,17 @@ class HelmRepoManager {
await this.update();
this.didUpdateOnce = true;
}
return {
didUpdateOnce: this.didUpdateOnce,
helmEnv: this.helmEnv,
};
}
protected async parseHelmEnv() {
const output = await execHelm(["env"]);
const lines = output.split(/\r?\n/); // split by new line feed
const env: HelmEnv = {};
const env: Partial<Record<string, string>> = {};
lines.forEach((line: string) => {
const [key, value] = line.split("=");
@ -59,21 +68,18 @@ class HelmRepoManager {
}
});
return env;
return env as HelmEnv;
}
public async repo(name: string): Promise<HelmRepo> {
public async repo(name: string): Promise<HelmRepo | undefined> {
const repos = await this.repositories();
return repos.find((repo) => repo.name === name);
return repos.find(repo => repo.name === name);
}
private async list(): Promise<HelmRepo[]> {
private async list(helmEnv: HelmEnv): Promise<HelmRepo[]> {
try {
const rawConfig = await readFile(
this.helmEnv.HELM_REPOSITORY_CONFIG,
"utf8",
);
const rawConfig = await readFile(helmEnv.HELM_REPOSITORY_CONFIG, "utf8");
const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig;
if (typeof parsedConfig === "object" && parsedConfig) {
@ -88,13 +94,13 @@ class HelmRepoManager {
public async repositories(): Promise<HelmRepo[]> {
try {
await this.ensureInitialized();
const { helmEnv } = await this.ensureInitialized();
const repos = await this.list();
const repos = await this.list(helmEnv);
return repos.map((repo) => ({
return repos.map(repo => ({
...repo,
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
cacheFilePath: `${helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
}));
} catch (error) {
logger.error(`[HELM]: repositories listing error`, error);
@ -104,21 +110,20 @@ class HelmRepoManager {
}
public async update() {
return execHelm(["repo", "update"]);
return execHelm([
"repo",
"update",
]);
}
public async addRepo({
name,
url,
insecureSkipTlsVerify,
username,
password,
caFile,
keyFile,
certFile,
}: HelmRepo) {
public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
logger.info(`[HELM]: adding repo ${name} from ${url}`);
const args = ["repo", "add", name, url];
const args = [
"repo",
"add",
name,
url,
];
if (insecureSkipTlsVerify) {
args.push("--insecure-skip-tls-verify");
@ -150,7 +155,11 @@ class HelmRepoManager {
public async removeRepo({ name, url }: HelmRepo): Promise<string> {
logger.info(`[HELM]: removing repo ${name} (${url})`);
return execHelm(["repo", "remove", name]);
return execHelm([
"repo",
"remove",
name,
]);
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OpenDialogOptions } from "electron";
import { dialog } from "electron";
import { getInjectable } from "@ogre-tools/injectable";
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
// TODO: Replace leaking electron with abstraction
export type AskUserForFilePaths = (
dialogOptions: OpenDialogOptions
) => Promise<{ canceled: boolean; filePaths: string[] }>;
const askUserForFilePathsInjectable = getInjectable({
id: "ask-user-for-file-paths",
instantiate: (di): AskUserForFilePaths => {
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
return async (dialogOptions) => {
await showApplicationWindow();
const { canceled, filePaths } = await dialog.showOpenDialog(
dialogOptions,
);
return { canceled, filePaths };
};
},
causesSideEffects: true,
});
export default askUserForFilePathsInjectable;

View File

@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OpenDialogOptions } from "electron";
import { dialog } from "electron";
import { WindowManager } from "../window-manager";
export async function showOpenDialog(dialogOptions: OpenDialogOptions): Promise<{ canceled: boolean; filePaths: string[] }> {
const window = await WindowManager.getInstance().ensureMainWindow();
const { canceled, filePaths } = await dialog.showOpenDialog(window, dialogOptions);
return { canceled, filePaths };
}

View File

@ -14,7 +14,7 @@ export interface KubeConfigManagerInstantiationParameter {
cluster: Cluster;
}
export type CreateKubeconfigManager = (cluster: Cluster) => KubeconfigManager | undefined;
export type CreateKubeconfigManager = (cluster: Cluster) => KubeconfigManager;
const createKubeconfigManagerInjectable = getInjectable({
id: "create-kubeconfig-manager",

View File

@ -7,7 +7,7 @@ import net from "net";
import type http from "http";
import spdy from "spdy";
import type httpProxy from "http-proxy";
import { apiPrefix, apiKubePrefix } from "../common/vars";
import { apiPrefix, apiKubePrefix } from "../../common/vars";
import type { Router } from "../router/router";
import type { ClusterContextHandler } from "../context-handler/context-handler";
import logger from "../logger";
@ -59,12 +59,9 @@ const disallowedPorts = new Set([
]);
export class LensProxy {
protected origin: string;
protected proxyServer: http.Server;
protected closed = false;
protected retryCounters = new Map<string, number>();
protected getClusterForRequest: GetClusterForRequest;
constructor(private dependencies: Dependencies) {
this.configureProxy(dependencies.proxy);

View File

@ -15,10 +15,12 @@ import parseRequestInjectable from "./parse-request.injectable";
import { contentTypes } from "./router-content-types";
import mockFs from "mock-fs";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { Route } from "./route";
import type { SetRequired } from "type-fest";
describe("router", () => {
let router: Router;
let routeHandlerMock: AsyncFnMock<RouteHandler<any, string>>;
let routeHandlerMock: AsyncFnMock<() => any>;
beforeEach(async () => {
routeHandlerMock = asyncFn();

View File

@ -8,8 +8,8 @@ import type http from "http";
import { toPairs } from "lodash/fp";
import type { Cluster } from "../../common/cluster/cluster";
import { contentTypes } from "./router-content-types";
import type { ServerIncomingMessage } from "../lens-proxy";
import type { LensApiRequest, LensApiResult, Route } from "./route";
import type { ServerIncomingMessage } from "../lens-proxy/lens-proxy";
export interface RouterRequestOpts {
req: http.IncomingMessage;

View File

@ -5,14 +5,13 @@
import { apiPrefix } from "../../../common/vars";
import { getRouteInjectable } from "../../router/router.injectable";
import { PrometheusProviderRegistry } from "../../prometheus";
import { route } from "../../router/route";
import prometheusProviderRegistryInjectable from "../../prometheus/prometheus-provider-registry.injectable";
const getMetricProvidersRouteInjectable = getRouteInjectable({
id: "get-metric-providers-route",
instantiate: () => {
instantiate: (di) => {
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
return route({
@ -26,7 +25,7 @@ const getMetricProvidersRouteInjectable = getRouteInjectable({
({ name, id, isConfigurable }) => ({ name, id, isConfigurable }),
),
}));
}
},
});
export default getMetricProvidersRouteInjectable;

View File

@ -6,7 +6,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
import { terminalShellEnvModify } from "./terminal-shell-env-modifiers";
import catalogEntityRegistryInjectable from "../../catalog/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
const terminalShellEnvModifyInjectable = getInjectable({
id: "terminal-shell-env-modify",

View File

@ -34,7 +34,7 @@ const createLensWindowInjectable = getInjectable({
instantiate:
(di) =>
(configuration: LensWindowConfiguration) => {
let browserWindow: LensWindow = null;
let browserWindow: LensWindow | undefined;
const createElectronWindow = di.inject(createElectronWindowForInjectable)(
{
@ -52,14 +52,14 @@ const createLensWindowInjectable = getInjectable({
beforeOpen: configuration.beforeOpen,
onClose: () => {
browserWindow = null;
browserWindow = undefined;
},
},
);
return {
get visible() {
return !!this.browserWindow;
return !!browserWindow;
},
show: async () => {
@ -72,7 +72,7 @@ const createLensWindowInjectable = getInjectable({
close: () => {
browserWindow?.close();
browserWindow = null;
browserWindow = undefined;
},
send: async (args: SendToViewArgs) => {

View File

@ -44,7 +44,7 @@ const initializeExtensionsInjectable = getInjectable({
});
extensionLoader.initExtensions(extensions);
} catch (error) {
} catch (error: any) {
showErrorPopup(
"Lens Error",
`Could not load extensions${

View File

@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { afterRootFrameIsReadyInjectionToken } from "../../runnable-tokens/after-root-frame-is-ready-injection-token";
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.injectable";
import ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
const startKubeConfigSyncInjectable = getInjectable({
id: "start-kubeconfig-sync",

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.injectable";
import { beforeQuitOfFrontEndInjectionToken } from "../../runnable-tokens/before-quit-of-front-end-injection-token";
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
const stopKubeConfigSyncInjectable = getInjectable({
id: "stop-kube-config-sync",

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import setupSyncingOfGeneralCatalogEntitiesInjectable from "./setup-syncing-of-general-catalog-entities.injectable";
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
const setupHotbarStoreInjectable = getInjectable({
id: "setup-hotbar-store",

View File

@ -28,7 +28,7 @@ const setupLensProxyInjectable = getInjectable({
try {
logger.info("🔌 Starting LensProxy");
await lensProxy.listen(); // lensProxy.port available
} catch (error) {
} catch (error: any) {
showErrorPopup("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`);
return exitApp();

View File

@ -5,13 +5,19 @@
import { getInjectable } from "@ogre-tools/injectable";
import { apiBase } from "../../common/k8s-api";
import { onApiError } from "./on-api-error";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
const setupOnApiErrorListenersInjectable = getInjectable({
id: "setup-on-api-error-listeners",
setup: () => {
apiBase?.onError.addListener(onApiError);
},
instantiate: () => undefined,
instantiate: () => ({
run: () => {
apiBase?.onError.addListener(onApiError);
},
}),
injectionToken: beforeFrameStartsInjectionToken,
causesSideEffects: true,
});
export default setupOnApiErrorListenersInjectable;

View File

@ -105,7 +105,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
upgradeVersion = (release: HelmRelease) => {
const { hideDetails, createUpgradeChartTab } = this.props;
this.props.createUpgradeChartTab(this.release);
createUpgradeChartTab(release);
hideDetails();
};

View File

@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ClusterFrameHandler } from "./lens-views";
import { ClusterFrameHandler } from "./cluster-frame-handler";
const clusterFramesInjectable = getInjectable({
id: "cluster-frames",
const clusterFrameHandlerInjectable = getInjectable({
id: "cluster-frame-handler",
instantiate: () => new ClusterFrameHandler(),
});
export default clusterFramesInjectable;
export default clusterFrameHandlerInjectable;

View File

@ -9,7 +9,7 @@ import type { IComputedValue } from "mobx";
import { computed, makeObservable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./lens-views";
import type { ClusterFrameHandler } from "./cluster-frame-handler";
import type { Cluster } from "../../../common/cluster/cluster";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { requestClusterActivation } from "../../ipc";
@ -17,7 +17,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable";
import clusterFramesInjectable from "./lens-views.injectable";
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
@ -107,7 +107,7 @@ export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView,
getProps: (di) => ({
clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId,
navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFramesInjectable),
clusterFrames: di.inject(clusterFrameHandlerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}),
});

View File

@ -4,28 +4,20 @@
*/
import "@testing-library/jest-dom/extend-expect";
import { KubeConfig } from "@kubernetes/client-node";
import { fireEvent, screen } from "@testing-library/react";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import type { RenderResult } from "@testing-library/react";
import mockFs from "mock-fs";
import React from "react";
import * as selectEvent from "react-select-event";
import type { Cluster } from "../../../../common/cluster/cluster";
import { DeleteClusterDialog } from "../view";
import { DeleteClusterDialog } from "../delete-cluster-dialog";
import type { ClusterModel } from "../../../../common/cluster-types";
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import type { OpenDeleteClusterDialog } from "../open.injectable";
import openDeleteClusterDialogInjectable from "../open.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
import deleteClusterDialogModelInjectable from "../delete-cluster-dialog-model/delete-cluster-dialog-model.injectable";
import type { DeleteClusterDialogModel } from "../delete-cluster-dialog-model/delete-cluster-dialog-model";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import type { ApplicationBuilder } from "../../test-utils/get-application-builder";
import { getApplicationBuilder } from "../../test-utils/get-application-builder";
import { routeInjectionToken } from "../../../../common/front-end-routing/route-injection-token";
@ -33,6 +25,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
jest.mock("electron", () => ({
app: {
@ -50,33 +43,29 @@ jest.mock("electron", () => ({
},
}));
const kubeconfig = `
const multiClusterConfig = `
apiVersion: v1
clusters:
- cluster:
server: https://localhost
name: test
name: some-current-context-cluster
- cluster:
server: http://localhost
name: other-cluster
name: some-non-current-context-cluster
contexts:
- context:
cluster: test
user: test
name: test
cluster: some-current-context-cluster
user: some-user
name: some-current-context
- context:
cluster: test
user: test
name: test2
- context:
cluster: other-cluster
user: test
name: other-context
current-context: other-context
cluster: some-non-current-context-cluster
user: some-user
name: some-non-current-context
current-context: some-current-context
kind: Config
preferences: {}
users:
- name: test
- name: some-user
user:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
@ -86,17 +75,17 @@ apiVersion: v1
clusters:
- cluster:
server: http://localhost
name: other-cluster
name: some-cluster
contexts:
- context:
cluster: other-cluster
user: test
name: other-context
current-context: other-context
cluster: some-cluster
user: some-user
name: some-context
current-context: some-context
kind: Config
preferences: {}
users:
- name: test
- name: some-user
user:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
@ -107,46 +96,22 @@ describe("<DeleteClusterDialog />", () => {
let applicationBuilder: ApplicationBuilder;
let createCluster: (model: ClusterModel) => Cluster;
let openDeleteClusterDialog: OpenDeleteClusterDialog;
let render: DiRender;
beforeEach(async () => {
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
mainDi.override(createContextHandlerInjectable, () => () => undefined);
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined);
mainDi.override(createContextHandlerInjectable, () => () => undefined as any);
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as any);
rendererDi.override(hotbarStoreInjectable, () => ({}));
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
const testRouteInjectable = getInjectable({
id: "some-test-route",
instantiate: () => ({
path: "/some-test-path",
clusterFrame: false,
isEnabled: computed(() => true),
}),
injectionToken: routeInjectionToken,
});
const testRouteComponent = getInjectable({
id: "some-test-component",
instantiate: (di) => ({
route: di.inject(testRouteInjectable),
Component: () => <DeleteClusterDialog />,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
});
const { rendererDi } = applicationBuilder.dis;
rendererDi.register(testRouteInjectable, testRouteComponentInjectable);
applicationBuilder.beforeRender(({ rendererDi }) => {
const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
const testRoute = rendererDi.inject(testRouteInjectable);
@ -154,70 +119,100 @@ describe("<DeleteClusterDialog />", () => {
navigateToRoute(testRoute);
});
rendererDi.register(testRouteInjectable, testRouteComponent);
mockFs();
openDeleteClusterDialog = rendererDi.inject(openDeleteClusterDialogInjectable);
createCluster = mainDi.inject(createClusterInjectionToken);
applicationBuilder.beforeRender(({ rendererDi }) => {
openDeleteClusterDialog = rendererDi.inject(openDeleteClusterDialogInjectable);
createCluster = rendererDi.inject(createClusterInjectionToken);
});
});
afterEach(() => {
mockFs.restore();
});
it("shows context switcher when deleting current cluster", async () => {
const mockOpts = {
"temp-kube-config": multiClusterConfig,
};
mockFs(mockOpts);
config = new KubeConfig();
config.loadFromString(multiClusterConfig);
applicationBuilder.beforeRender(({ rendererDi }) => {
const createCluster = rendererDi.inject(createClusterInjectionToken);
const cluster = createCluster({
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
});
const rendered = await applicationBuilder.render();
const { getByText } = rendered;
const menu = getByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(getByText("some-current-context")).toBeInTheDocument();
expect(getByText("some-non-current-context")).toBeInTheDocument();
});
describe("Kubeconfig with different clusters", () => {
let rendered: RenderResult;
let createCluster: (model: ClusterModel) => Cluster;
let deleteClusterDialogModel: DeleteClusterDialogModel;
beforeEach(async () => {
const mockOpts = {
"temp-kube-config": kubeconfig,
"temp-kube-config": multiClusterConfig,
};
mockFs(mockOpts);
config = new KubeConfig();
config.loadFromString(kubeconfig);
config.loadFromString(multiClusterConfig);
rendered = await applicationBuilder.render();
deleteClusterDialogModel = applicationBuilder.dis.rendererDi.inject(deleteClusterDialogModelInjectable);
createCluster = applicationBuilder.dis.mainDi.inject(createClusterInjectionToken);
});
afterEach(() => {
mockFs.restore();
});
it("renders w/o errors", () => {
expect(rendered.container).toBeInstanceOf(HTMLElement);
});
it("shows warning when deleting non-current-context cluster", async () => {
it("shows warning when deleting non-current-context cluster", () => {
const cluster = createCluster({
id: "test",
contextName: "test",
id: "some-non-current-context-cluster",
contextName: "some-non-current-context",
preferences: {
clusterName: "minikube",
},
kubeConfigPath: "./temp-kube-config",
});
deleteClusterDialogModel.open({ cluster, config });
openDeleteClusterDialog({ cluster, config });
const message = "The contents of kubeconfig file will be changed!";
expect(rendered.getByText(message)).toBeInstanceOf(HTMLElement);
});
it("shows warning when deleting current-context cluster", async () => {
it("shows warning when deleting current-context cluster", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
@ -227,42 +222,17 @@ describe("<DeleteClusterDialog />", () => {
expect(rendered.getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement);
});
it("shows context switcher when deleting current cluster", async () => {
it("shows context switcher after checkbox click", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
const { getByText } = rendered;
const menu = getByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(getByText("test")).toBeInTheDocument();
expect(getByText("test2")).toBeInTheDocument();
});
it("shows context switcher after checkbox click", async () => {
const cluster = createCluster({
id: "some-cluster",
contextName: "test",
preferences: {
clusterName: "test",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
const { getByText, getByTestId } = rendered;
const link = getByTestId("context-switch");
@ -270,28 +240,30 @@ describe("<DeleteClusterDialog />", () => {
expect(link).toBeInstanceOf(HTMLElement);
fireEvent.click(link);
const menu = await screen.findByText("Select new context...");
const menu = getByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(await screen.findByText("test")).toBeInTheDocument();
expect(await screen.findByText("test2")).toBeInTheDocument();
expect(getByText("some-current-context")).toBeInTheDocument();
expect(getByText("some-non-current-context")).toBeInTheDocument();
});
it("shows warning for internal kubeconfig cluster", async () => {
it("given cluster in internal kubeconfig, when deleting cluster outside of current context, shows warning for internal kubeconfig cluster", () => {
const cluster = createCluster({
id: "some-cluster",
contextName: "test",
id: "some-non-current-context-cluster",
contextName: "some-non-current-context",
preferences: {
clusterName: "test",
clusterName: "some-non-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
deleteClusterDialogModel.open({ cluster, config });
openDeleteClusterDialog({ cluster, config });
expect(rendered.getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement);
@ -301,8 +273,6 @@ describe("<DeleteClusterDialog />", () => {
describe("Kubeconfig with single cluster", () => {
let rendered: RenderResult;
let createCluster: (model: ClusterModel) => Cluster;
let deleteClusterDialogModel: DeleteClusterDialogModel;
beforeEach(async () => {
const mockOpts = {
@ -315,28 +285,44 @@ describe("<DeleteClusterDialog />", () => {
config.loadFromString(singleClusterConfig);
rendered = await applicationBuilder.render();
deleteClusterDialogModel = applicationBuilder.dis.rendererDi.inject(deleteClusterDialogModelInjectable);
createCluster = applicationBuilder.dis.mainDi.inject(createClusterInjectionToken);
});
afterEach(() => {
mockFs.restore();
});
it("shows warning if no other contexts left", async () => {
it("shows warning if no other contexts left", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-cluster",
contextName: "some-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
deleteClusterDialogModel.open({ cluster, config });
openDeleteClusterDialog({ cluster, config });
expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
});
});
});
const testRouteInjectable = getInjectable({
id: "some-test-route",
instantiate: () => ({
path: "/some-test-path",
clusterFrame: false,
isEnabled: computed(() => true),
}),
injectionToken: routeInjectionToken,
});
const testRouteComponentInjectable = getInjectable({
id: "some-test-component",
instantiate: (di) => ({
route: di.inject(testRouteInjectable),
Component: () => <DeleteClusterDialog />,
}),
injectionToken: routeSpecificComponentInjectionToken,
});

View File

@ -50,6 +50,7 @@ class NonInjectedMenuActions extends React.Component<MenuActionsProps & Dependen
removeConfirmationMessage: "Remove item?",
};
// TODO: Make deterministic
public id = uniqueId("menu_actions_");
@observable isOpen = !!this.props.toolbar;

View File

@ -9,7 +9,6 @@ import { extensionRegistratorInjectionToken } from "../../../extensions/extensio
import type { IObservableArray } from "mobx";
import { computed, observable, runInAction } from "mobx";
import { renderFor } from "./renderFor";
import observableHistoryInjectable from "../../navigation/observable-history.injectable";
import React from "react";
import { Router } from "react-router-dom";
import { Observer } from "mobx-react";
@ -37,13 +36,14 @@ import navigateToHelmChartsInjectable from "../../../common/front-end-routing/ro
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
import { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context";
import type { Cluster } from "../../../common/cluster/cluster";
import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable";
import startFrameInjectable from "../../start-frame/start-frame.injectable";
import { flushPromises } from "../../../common/test-utils/flush-promises";
import type { NamespaceStore } from "../+namespaces/store";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import historyInjectable from "../../navigation/history.injectable";
type Callback = (dis: DiContainers) => void | Promise<void>;
@ -235,9 +235,11 @@ export const getApplicationBuilder = () => {
const clusterStub = {
accessibleNamespaces: [],
} as Cluster;
} as unknown as Cluster;
const namespaceStoreStub = {} as NamespaceStore;
const namespaceStoreStub = {
contextNamespaces: [],
} as unknown as NamespaceStore;
const clusterFrameContextFake = new ClusterFrameContext(
clusterStub,
@ -321,7 +323,7 @@ export const getApplicationBuilder = () => {
await startFrame();
const render = renderFor(rendererDi);
const history = rendererDi.inject(observableHistoryInjectable);
const history = rendererDi.inject(historyInjectable) as any;
const currentRouteComponent = rendererDi.inject(currentRouteComponentInjectable);
for (const callback of beforeRenderCallbacks) {

View File

@ -18,12 +18,13 @@ const createClusterInjectable = getInjectable({
logger: di.inject(loggerInjectable),
// TODO: Dismantle wrong abstraction
createKubeconfigManager: () => undefined,
// Note: "as never" to get around strictness in unnatural scenario
createKubeconfigManager: () => undefined as never,
createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");},
createContextHandler: () => undefined,
createContextHandler: () => undefined as never,
createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); },
createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); },
detectorRegistry: undefined,
detectorRegistry: undefined as never,
createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); },
};

View File

@ -34,11 +34,15 @@ import terminalSpawningPoolInjectable from "./components/dock/terminal/terminal-
import hostedClusterIdInjectable from "../common/cluster-store/hosted-cluster-id.injectable";
import type { GetDiForUnitTestingOptions } from "../test-utils/get-dis-for-unit-testing";
import historyInjectable from "./navigation/history.injectable";
import themeStoreInjectable from "./theme-store.injectable";
import apiManagerInjectable from "./components/kube-object-menu/dependencies/api-manager.injectable";
import { ApiManager } from "../common/k8s-api/api-manager";
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable";
import ipcRendererInjectable
from "./app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
import type { IpcRenderer } from "electron";
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
import { observable } from "mobx";
export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
const {
@ -75,6 +79,11 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
di.override(lensResourcesDirInjectable, () => "/irrelevant");
di.override(ipcRendererInjectable, () => ({
invoke: () => {},
on: () => {},
}) as unknown as IpcRenderer);
di.override(broadcastMessageInjectable, () => () => {
throw new Error("Tried to broadcast message over IPC without explicit override.");
});
@ -89,11 +98,17 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
// eslint-disable-next-line unused-imports/no-unused-vars-ts
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
di.override(setupOnApiErrorListenersInjectable, () => ({ run: () => {} }));
di.override(
userStoreInjectable,
() =>
({
isTableColumnHidden: () => false,
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
terminalConfig: { fontSize: 42 },
editorConfiguration: { minimap: {}, tabSize: 42, fontSize: 42 },
} as unknown as UserStore),
);
@ -113,12 +128,6 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
info: noop,
silly: noop,
}));
di.override(themeStoreInjectable, () => ({
activeTheme: {
type: "some-active-theme-type",
},
}));
}
return di;

View File

@ -10,6 +10,7 @@ import historyInjectable from "./history.injectable";
const observableHistoryInjectable = getInjectable({
id: "observable-history",
instantiate: (di) => {
const history = di.inject(historyInjectable);
const logger = di.inject(loggerInjectable);

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainerForInstantiate } from "@ogre-tools/injectable";
import type { DiContainerForInjection } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
@ -46,7 +46,7 @@ export default extensionRouteRegistratorInjectable;
const toRouteInjectableFor =
(
di: DiContainerForInstantiate,
di: DiContainerForInjection,
extension: LensRendererExtension,
extensionInstallationCount: number,
) =>

View File

@ -87,8 +87,8 @@ export const overrideIpcBridge = ({
mainDi.override(
sendToChannelInElectronBrowserWindowInjectable,
() =>
(browserWindow, { channel: channelName, data }: SendToViewArgs) => {
const handles = rendererIpcFakeHandles.get(channelName);
(browserWindow, { channel: channelName, data = [] }: SendToViewArgs) => {
const handles = rendererIpcFakeHandles.get(channelName) || [];
if (isEmpty(handles)) {
throw new Error(