mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix flakiness and improve tests for DeleteClusterDialog
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
7e47c633bf
commit
aec299feb4
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
||||
|
||||
const filteredCategoriesInjectable = getInjectable({
|
||||
id: "filtered-categories",
|
||||
instantiate: (di) => {
|
||||
const registry = di.inject(catalogCategoryRegistryInjectable);
|
||||
|
||||
return computed(() => [...registry.filteredItems]);
|
||||
},
|
||||
});
|
||||
|
||||
export default filteredCategoriesInjectable;
|
||||
15
src/common/fs/delete-file.injectable.ts
Normal file
15
src/common/fs/delete-file.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 fsInjectable from "./fs.injectable";
|
||||
|
||||
export type DeleteFile = (filePath: string) => Promise<void>;
|
||||
|
||||
const deleteFileInjectable = getInjectable({
|
||||
id: "delete-file",
|
||||
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
|
||||
});
|
||||
|
||||
export default deleteFileInjectable;
|
||||
@ -8,9 +8,6 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
||||
export const clusterVisibilityHandler = "cluster:visibility";
|
||||
export const clusterRefreshHandler = "cluster:refresh";
|
||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||
export const clusterDeleteHandler = "cluster:delete";
|
||||
export const clusterSetDeletingHandler = "cluster:deleting:set";
|
||||
export const clusterClearDeletingHandler = "cluster:deleting:clear";
|
||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||
export const clusterStates = "cluster:states";
|
||||
|
||||
@ -12,7 +12,7 @@ export type RequestFromChannel = <
|
||||
channel: TChannel,
|
||||
...request: TChannel["_requestSignature"] extends void
|
||||
? []
|
||||
: [TChannel["_requestSignature"]]
|
||||
: [SetRequired<TChannel, "_requestSignature">["_requestSignature"]]
|
||||
) => Promise<SetRequired<TChannel, "_responseSignature">["_responseSignature"]>;
|
||||
|
||||
export const requestFromChannelInjectionToken =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
|
||||
export type ClearClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
||||
|
||||
const clearClusterAsDeletingChannelInjectable = getInjectable({
|
||||
id: "clear-cluster-as-deleting-channel",
|
||||
instantiate: (): ClearClusterAsDeletingChannel => ({
|
||||
id: "clear-cluster-as-deleting",
|
||||
}),
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default clearClusterAsDeletingChannelInjectable;
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
|
||||
export type DeleteClusterChannel = RequestChannel<ClusterId, void>;
|
||||
|
||||
const deleteClusterChannelInjectable = getInjectable({
|
||||
id: "delete-cluster-channel",
|
||||
instantiate: (): DeleteClusterChannel => ({
|
||||
id: "delete-cluster",
|
||||
}),
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default deleteClusterChannelInjectable;
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../../../../common/utils/channel/request-channel-injection-token";
|
||||
|
||||
export type SetClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
||||
|
||||
const setClusterAsDeletingChannelInjectable = getInjectable({
|
||||
id: "set-cluster-as-deleting-channel",
|
||||
instantiate: (): SetClusterAsDeletingChannel => ({
|
||||
id: "set-cluster-as-deleting",
|
||||
}),
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default setClusterAsDeletingChannelInjectable;
|
||||
@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { CreateCluster } from "../../../common/cluster/create-cluster-injection-token";
|
||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||
import createContextHandlerInjectable from "../../../main/context-handler/create-context-handler.injectable";
|
||||
import createKubeconfigManagerInjectable from "../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable";
|
||||
import openDeleteClusterDialogInjectable, { type OpenDeleteClusterDialog } from "../../../renderer/components/delete-cluster-dialog/open.injectable";
|
||||
import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
import path from "path";
|
||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
|
||||
const currentClusterServerUrl = "https://localhost";
|
||||
const nonCurrentClusterServerUrl = "http://localhost";
|
||||
const multiClusterConfig = `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: ${currentClusterServerUrl}
|
||||
name: some-current-context-cluster
|
||||
- cluster:
|
||||
server: ${nonCurrentClusterServerUrl}
|
||||
name: some-non-current-context-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: some-current-context-cluster
|
||||
user: some-user
|
||||
name: some-current-context
|
||||
- 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: some-user
|
||||
user:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
const singleClusterServerUrl = "http://localhost";
|
||||
const singleClusterConfig = `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: ${singleClusterServerUrl}
|
||||
name: some-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: some-cluster
|
||||
user: some-user
|
||||
name: some-context
|
||||
current-context: some-context
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: some-user
|
||||
user:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
describe("Deleting a cluster", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let createCluster: CreateCluster;
|
||||
let rendered: RenderResult;
|
||||
let config: KubeConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
config = new KubeConfig();
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.override(createContextHandlerInjectable, () => () => undefined as never);
|
||||
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never);
|
||||
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||
});
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
|
||||
// TODO: remove this line when all global uses of appEventBus are removed
|
||||
windowDi.permitSideEffects(appEventBusInjectable);
|
||||
});
|
||||
|
||||
builder.afterWindowStart(windowDi => {
|
||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
|
||||
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
|
||||
|
||||
navigateToCatalog();
|
||||
});
|
||||
|
||||
rendered = await builder.render();
|
||||
});
|
||||
|
||||
describe("when the kubeconfig has multiple clusters", () => {
|
||||
let currentCluster: Cluster;
|
||||
let nonCurrentCluster: Cluster;
|
||||
|
||||
beforeEach(() => {
|
||||
config.loadFromString(multiClusterConfig);
|
||||
|
||||
currentCluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
nonCurrentCluster = createCluster({
|
||||
id: "some-non-current-context-cluster",
|
||||
contextName: "some-non-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-non-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the dialog is opened for the current cluster", () => {
|
||||
// TODO: replace with actual behaviour instead of technical use
|
||||
beforeEach(async () => {
|
||||
openDeleteClusterDialog({
|
||||
cluster: currentCluster,
|
||||
config,
|
||||
});
|
||||
|
||||
await rendered.findByTestId("delete-cluster-dialog");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows context switcher", () => {
|
||||
expect(rendered.queryByText("Select new context...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows warning", () => {
|
||||
expect(rendered.queryByTestId("current-context-warning")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the dialog is opened for not the current cluster", () => {
|
||||
// TODO: replace with actual behaviour instead of technical use
|
||||
beforeEach(async () => {
|
||||
openDeleteClusterDialog({
|
||||
cluster: nonCurrentCluster,
|
||||
config,
|
||||
});
|
||||
|
||||
await rendered.findByTestId("delete-cluster-dialog");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows warning", () => {
|
||||
expect(rendered.queryByTestId("kubeconfig-change-warning")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show context switcher", () => {
|
||||
expect(rendered.queryByText("Select new context...")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when context switching checkbox is clicked", () => {
|
||||
beforeEach(() => {
|
||||
rendered.getByTestId("delete-cluster-dialog-context-switch").click();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows context switcher", () => {
|
||||
expect(rendered.queryByText("Select new context...")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an internal kubeconfig cluster is used", () => {
|
||||
let currentCluster: Cluster;
|
||||
|
||||
beforeEach(() => {
|
||||
config.loadFromString(singleClusterConfig);
|
||||
|
||||
const directoryForKubeConfigs = builder.applicationWindow.only.di.inject(directoryForKubeConfigsInjectable);
|
||||
|
||||
currentCluster = createCluster({
|
||||
id: "some-cluster",
|
||||
contextName: "some-context",
|
||||
preferences: {
|
||||
clusterName: "some-cluster",
|
||||
},
|
||||
kubeConfigPath: path.join(directoryForKubeConfigs, "some-cluster.json"),
|
||||
}, {
|
||||
clusterServerUrl: singleClusterServerUrl,
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the dialog is opened", () => {
|
||||
// TODO: replace with actual behaviour instead of technical use
|
||||
beforeEach(async () => {
|
||||
openDeleteClusterDialog({
|
||||
cluster: currentCluster,
|
||||
config,
|
||||
});
|
||||
|
||||
await rendered.findByTestId("delete-cluster-dialog");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows warning", () => {
|
||||
expect(rendered.queryByTestId("internal-kubeconfig-warning")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the kubeconfig has only one cluster", () => {
|
||||
let currentCluster: Cluster;
|
||||
|
||||
beforeEach(() => {
|
||||
config.loadFromString(singleClusterConfig);
|
||||
|
||||
currentCluster = createCluster({
|
||||
id: "some-cluster",
|
||||
contextName: "some-context",
|
||||
preferences: {
|
||||
clusterName: "some-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: singleClusterServerUrl,
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the dialog is opened", () => {
|
||||
// TODO: replace with actual behaviour instead of technical use
|
||||
beforeEach(async () => {
|
||||
openDeleteClusterDialog({
|
||||
cluster: currentCluster,
|
||||
config,
|
||||
});
|
||||
|
||||
await rendered.findByTestId("delete-cluster-dialog");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows warning", () => {
|
||||
expect(rendered.queryByTestId("no-more-contexts-warning")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
import clustersThatAreBeingDeletedInjectable from "../../../../main/clusters-that-are-being-deleted.injectable";
|
||||
import clearClusterAsDeletingChannelInjectable from "../common/clear-as-deleting-channel.injectable";
|
||||
|
||||
const clearClusterAsDeletingChannelHandlerInjectable = getInjectable({
|
||||
id: "clear-cluster-as-deleting-channel-handler",
|
||||
instantiate: (di) => {
|
||||
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||
|
||||
return {
|
||||
channel: di.inject(clearClusterAsDeletingChannelInjectable),
|
||||
handler: (clusterId) => clustersThatAreBeingDeleted.delete(clusterId),
|
||||
};
|
||||
},
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default clearClusterAsDeletingChannelHandlerInjectable;
|
||||
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable";
|
||||
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
|
||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import deleteFileInjectable from "../../../../common/fs/delete-file.injectable";
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
import { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
import deleteClusterChannelInjectable from "../common/delete-channel.injectable";
|
||||
|
||||
const deleteClusterChannelHandlerInjectable = getInjectable({
|
||||
id: "delete-cluster-channel-handler",
|
||||
instantiate: (di) => {
|
||||
const appEventBus = di.inject(appEventBusInjectable);
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const clusterFrames = di.inject(clusterFramesInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForLensLocalStorage = di.inject(directoryForLensLocalStorageInjectable);
|
||||
const deleteFile = di.inject(deleteFileInjectable);
|
||||
|
||||
return {
|
||||
channel: di.inject(deleteClusterChannelInjectable),
|
||||
handler: async (clusterId) =>{
|
||||
appEventBus.emit({ name: "cluster", action: "remove" });
|
||||
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
|
||||
if (!cluster) {
|
||||
return;
|
||||
}
|
||||
|
||||
cluster.disconnect();
|
||||
clusterFrames.delete(cluster.id);
|
||||
|
||||
// Remove from the cluster store as well, this should clear any old settings
|
||||
clusterStore.clusters.delete(cluster.id);
|
||||
|
||||
try {
|
||||
// remove the local storage file
|
||||
const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
|
||||
await deleteFile(localStorageFilePath);
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default deleteClusterChannelHandlerInjectable;
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
import clustersThatAreBeingDeletedInjectable from "../../../../main/clusters-that-are-being-deleted.injectable";
|
||||
import setClusterAsDeletingChannelInjectable from "../common/set-as-deleting-channel.injectable";
|
||||
|
||||
const setClusterAsDeletingChannelHandlerInjectable = getInjectable({
|
||||
id: "set-cluster-as-deleting-channel-handler",
|
||||
instantiate: (di) => {
|
||||
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||
|
||||
return {
|
||||
channel: di.inject(setClusterAsDeletingChannelInjectable),
|
||||
handler: (clusterId) => clustersThatAreBeingDeleted.add(clusterId),
|
||||
};
|
||||
},
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default setClusterAsDeletingChannelHandlerInjectable;
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||
import clearClusterAsDeletingChannelInjectable from "../common/clear-as-deleting-channel.injectable";
|
||||
|
||||
export type RequestClearClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||
|
||||
const requestClearClusterAsDeletingInjectable = getInjectable({
|
||||
id: "request-clear-cluster-as-deleting",
|
||||
instantiate: (di): RequestClearClusterAsDeleting => {
|
||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||
const clearClusterAsDeletingChannel = di.inject(clearClusterAsDeletingChannelInjectable);
|
||||
|
||||
return (clusterId) => requestChannel(clearClusterAsDeletingChannel, clusterId);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestClearClusterAsDeletingInjectable;
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||
import deleteClusterChannelInjectable from "../common/delete-channel.injectable";
|
||||
|
||||
export type RequestDeleteCluster = (clusterId: ClusterId) => Promise<void>;
|
||||
|
||||
const requestDeleteClusterInjectable = getInjectable({
|
||||
id: "request-delete-cluster",
|
||||
instantiate: (di): RequestDeleteCluster => {
|
||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||
const deleteClusterChannel = di.inject(deleteClusterChannelInjectable);
|
||||
|
||||
return (clusterId) => requestChannel(deleteClusterChannel, clusterId);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestDeleteClusterInjectable;
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../../../../common/cluster-types";
|
||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||
import setClusterAsDeletingChannelInjectable from "../common/set-as-deleting-channel.injectable";
|
||||
|
||||
export type RequestSetClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||
|
||||
const requestSetClusterAsDeletingInjectable = getInjectable({
|
||||
id: "request-set-cluster-as-deleting",
|
||||
instantiate: (di): RequestSetClusterAsDeleting => {
|
||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
||||
const setClusterAsDeletingChannel = di.inject(setClusterAsDeletingChannelInjectable);
|
||||
|
||||
return (clusterId) => requestChannel(setClusterAsDeletingChannel, clusterId);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestSetClusterAsDeletingInjectable;
|
||||
@ -6916,7 +6916,7 @@ exports[`add custom helm repository in preferences when navigating to preference
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter leave"
|
||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter"
|
||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||
>
|
||||
<div
|
||||
@ -7502,7 +7502,7 @@ exports[`add custom helm repository in preferences when navigating to preference
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter leave"
|
||||
class="Animate opacity-scale Dialog flex center AddHelmRepoDialog modal enter"
|
||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||
>
|
||||
<div
|
||||
|
||||
@ -17,6 +17,7 @@ import isPathInjectable from "../../renderer/components/input/validators/is-path
|
||||
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
||||
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
import { useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||
|
||||
describe("add custom helm repository in preferences", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@ -33,6 +34,8 @@ describe("add custom helm repository in preferences", () => {
|
||||
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
useFakeTime("2021-01-01 12:00:00");
|
||||
|
||||
execFileMock = asyncFn();
|
||||
getActiveHelmRepositoriesMock = asyncFn();
|
||||
showSuccessNotificationMock = jest.fn();
|
||||
|
||||
14
src/main/clusters-that-are-being-deleted.injectable.ts
Normal file
14
src/main/clusters-that-are-being-deleted.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { observable } from "mobx";
|
||||
import type { ClusterId } from "../common/cluster-types";
|
||||
|
||||
const clustersThatAreBeingDeletedInjectable = getInjectable({
|
||||
id: "clusters-that-are-being-deleted",
|
||||
instantiate: () => observable.set<ClusterId>(),
|
||||
});
|
||||
|
||||
export default clustersThatAreBeingDeletedInjectable;
|
||||
@ -3,12 +3,10 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
||||
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 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";
|
||||
@ -21,14 +19,8 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
const directoryForLensLocalStorage = di.inject(
|
||||
directoryForLensLocalStorageInjectable,
|
||||
);
|
||||
|
||||
const clusterManager = di.inject(clusterManagerInjectable);
|
||||
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
||||
@ -42,8 +34,6 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
||||
|
||||
setupIpcMainHandlers({
|
||||
applicationMenuItems,
|
||||
getAbsolutePath,
|
||||
directoryForLensLocalStorage,
|
||||
clusterManager,
|
||||
catalogEntityRegistry,
|
||||
clusterStore,
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import type { IpcMainInvokeEvent } from "electron";
|
||||
import { BrowserWindow, Menu } from "electron";
|
||||
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler, clusterSetDeletingHandler, clusterClearDeletingHandler } from "../../../../common/ipc/cluster";
|
||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster";
|
||||
import type { ClusterId } from "../../../../common/cluster-types";
|
||||
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
||||
import { appEventBus } from "../../../../common/app-event-bus/event-bus";
|
||||
@ -14,9 +14,7 @@ import type { CatalogEntityRegistry } from "../../../catalog";
|
||||
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
||||
import type { ClusterManager } from "../../../cluster/manager";
|
||||
import { ResourceApplier } from "../../../resource-applier";
|
||||
import { remove } from "fs-extra";
|
||||
import type { IComputedValue, ObservableSet } from "mobx";
|
||||
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
||||
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
|
||||
@ -26,27 +24,21 @@ import type { Theme } from "../../../theme/operating-system-theme-state.injectab
|
||||
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
directoryForLensLocalStorage: string;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
|
||||
clusterManager: ClusterManager;
|
||||
catalogEntityRegistry: CatalogEntityRegistry;
|
||||
clusterStore: ClusterStore;
|
||||
operatingSystemTheme: IComputedValue<Theme>;
|
||||
askUserForFilePaths: AskUserForFilePaths;
|
||||
clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
||||
}
|
||||
|
||||
export const setupIpcMainHandlers = ({
|
||||
applicationMenuItems,
|
||||
directoryForLensLocalStorage,
|
||||
getAbsolutePath,
|
||||
clusterManager,
|
||||
catalogEntityRegistry,
|
||||
clusterStore,
|
||||
operatingSystemTheme,
|
||||
askUserForFilePaths,
|
||||
clustersThatAreBeingDeleted,
|
||||
}: Dependencies) => {
|
||||
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||
return ClusterStore.getInstance()
|
||||
@ -85,40 +77,6 @@ export const setupIpcMainHandlers = ({
|
||||
}
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterDeleteHandler, async (event, clusterId: ClusterId) => {
|
||||
appEventBus.emit({ name: "cluster", action: "remove" });
|
||||
|
||||
const clusterStore = ClusterStore.getInstance();
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
|
||||
if (!cluster) {
|
||||
return;
|
||||
}
|
||||
|
||||
cluster.disconnect();
|
||||
clusterFrameMap.delete(cluster.id);
|
||||
|
||||
// Remove from the cluster store as well, this should clear any old settings
|
||||
clusterStore.clusters.delete(cluster.id);
|
||||
|
||||
try {
|
||||
// remove the local storage file
|
||||
const localStorageFilePath = getAbsolutePath(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
|
||||
await remove(localStorageFilePath);
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => {
|
||||
clustersThatAreBeingDeleted.add(clusterId);
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterClearDeletingHandler, (event, clusterId: string) => {
|
||||
clustersThatAreBeingDeleted.delete(clusterId);
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||
appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" });
|
||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { HelmChartManager } from "../helm-chart-manager";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
|
||||
const getHelmChartVersionsInjectable = getInjectable({
|
||||
@ -11,6 +12,7 @@ const getHelmChartVersionsInjectable = getInjectable({
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string) => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
@ -19,7 +21,7 @@ const getHelmChartVersionsInjectable = getInjectable({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return HelmChartManager.forRepo(repo).chartVersions(chartName);
|
||||
return getChartManager(repo).chartVersions(chartName);
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -9,23 +9,21 @@ import styles from "./catalog-menu.module.scss";
|
||||
import React from "react";
|
||||
import type { TreeItemProps } from "@material-ui/lab";
|
||||
import { TreeItem, TreeView } from "@material-ui/lab";
|
||||
import { catalogCategoryRegistry } from "../../api/catalog-category-registry";
|
||||
import { Icon } from "../icon";
|
||||
import { StylesProvider } from "@material-ui/core";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { CatalogCategory } from "../../api/catalog-entity";
|
||||
import { observer } from "mobx-react";
|
||||
import { CatalogCategoryLabel } from "./catalog-category-label";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import filteredCategoriesInjectable from "../../../common/catalog/filtered-categories.injectable";
|
||||
|
||||
export interface CatalogMenuProps {
|
||||
activeTab: string | undefined;
|
||||
onItemClick: (id: string) => void;
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
return catalogCategoryRegistry.filteredItems;
|
||||
}
|
||||
|
||||
function getCategoryIcon(category: CatalogCategory) {
|
||||
const { icon } = category.metadata ?? {};
|
||||
|
||||
@ -44,44 +42,56 @@ function Item(props: TreeItemProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export const CatalogMenu = observer((props: CatalogMenuProps) => {
|
||||
return (
|
||||
// Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4
|
||||
<StylesProvider injectFirst>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className={styles.catalog}>Catalog</div>
|
||||
<TreeView
|
||||
defaultExpanded={["catalog"]}
|
||||
defaultCollapseIcon={<Icon material="expand_more"/>}
|
||||
defaultExpandIcon={<Icon material="chevron_right" />}
|
||||
selected={props.activeTab || "browse"}
|
||||
interface Dependencies {
|
||||
filteredCategories: IComputedValue<CatalogCategory[]>;
|
||||
}
|
||||
|
||||
const NonInjectedCatalogMenu = observer(({
|
||||
activeTab,
|
||||
filteredCategories,
|
||||
onItemClick,
|
||||
}: CatalogMenuProps & Dependencies) => (
|
||||
// Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4
|
||||
<StylesProvider injectFirst>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className={styles.catalog}>Catalog</div>
|
||||
<TreeView
|
||||
defaultExpanded={["catalog"]}
|
||||
defaultCollapseIcon={<Icon material="expand_more" />}
|
||||
defaultExpandIcon={<Icon material="chevron_right" />}
|
||||
selected={activeTab || "browse"}
|
||||
>
|
||||
<Item
|
||||
nodeId="browse"
|
||||
label="Browse"
|
||||
data-testid="*-tab"
|
||||
onClick={() => onItemClick("*")} />
|
||||
<Item
|
||||
nodeId="catalog"
|
||||
label={<div className={styles.parent}>Categories</div>}
|
||||
className={cssNames(styles.bordered)}
|
||||
>
|
||||
<Item
|
||||
nodeId="browse"
|
||||
label="Browse"
|
||||
data-testid="*-tab"
|
||||
onClick={() => props.onItemClick("*")}
|
||||
/>
|
||||
<Item
|
||||
nodeId="catalog"
|
||||
label={<div className={styles.parent}>Categories</div>}
|
||||
className={cssNames(styles.bordered)}
|
||||
>
|
||||
{
|
||||
getCategories().map(category => (
|
||||
{
|
||||
filteredCategories.get()
|
||||
.map(category => (
|
||||
<Item
|
||||
icon={getCategoryIcon(category)}
|
||||
key={category.getId()}
|
||||
nodeId={category.getId()}
|
||||
label={<CatalogCategoryLabel category={category}/>}
|
||||
label={<CatalogCategoryLabel category={category} />}
|
||||
data-testid={`${category.getId()}-tab`}
|
||||
onClick={() => props.onItemClick(category.getId())}
|
||||
/>
|
||||
onClick={() => onItemClick(category.getId())} />
|
||||
))
|
||||
}
|
||||
</Item>
|
||||
</TreeView>
|
||||
</div>
|
||||
</StylesProvider>
|
||||
);
|
||||
}
|
||||
</Item>
|
||||
</TreeView>
|
||||
</div>
|
||||
</StylesProvider>
|
||||
));
|
||||
|
||||
export const CatalogMenu = withInjectables<Dependencies, CatalogMenuProps>(NonInjectedCatalogMenu, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
filteredCategories: di.inject(filteredCategoriesInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@ import "./animate.scss";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { cssNames, noop } from "../../utils";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { RequestAnimationFrame } from "./request-animation-frame.injectable";
|
||||
import requestAnimationFrameInjectable from "./request-animation-frame.injectable";
|
||||
import defaultEnterDurationForAnimatedInjectable from "./default-enter-duration.injectable";
|
||||
import defaultLeaveDurationForAnimatedInjectable from "./default-leave-duration.injectable";
|
||||
@ -24,7 +25,7 @@ export interface AnimateProps {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
requestAnimationFrame: (callback: () => void) => void;
|
||||
requestAnimationFrame: RequestAnimationFrame;
|
||||
defaultEnterDuration: number;
|
||||
defaultLeaveDuration: number;
|
||||
}
|
||||
@ -46,21 +47,25 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
||||
onLeave: onLeaveHandler = noop<[]>,
|
||||
} = props;
|
||||
|
||||
const [isVisible, setIsVisible] = useState(enter);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [showClassNameEnter, setShowClassNameEnter] = useState(false);
|
||||
const [showClassNameLeave, setShowClassNameLeave] = useState(false);
|
||||
|
||||
const contentElem = React.Children.only(children) as React.ReactElement<React.HTMLAttributes<any>>;
|
||||
const onEnter = () => {
|
||||
setIsVisible(true);
|
||||
const classNames = cssNames("Animate", name, contentElem.props.className, {
|
||||
enter: showClassNameEnter,
|
||||
leave: showClassNameLeave,
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setShowClassNameEnter(true);
|
||||
onEnterHandler();
|
||||
});
|
||||
};
|
||||
const onLeave = () => {
|
||||
if (isVisible) {
|
||||
useEffect(() => {
|
||||
if (enter) {
|
||||
setIsVisible(true);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setShowClassNameEnter(true);
|
||||
onEnterHandler();
|
||||
});
|
||||
} else if (isVisible) {
|
||||
setShowClassNameLeave(true);
|
||||
onLeaveHandler();
|
||||
|
||||
@ -71,16 +76,7 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
||||
setShowClassNameLeave(false);
|
||||
}, leaveDuration);
|
||||
}
|
||||
};
|
||||
const toggle = (entering: boolean) => {
|
||||
if (entering) {
|
||||
onEnter();
|
||||
} else {
|
||||
onLeave();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => toggle(enter), [enter]);
|
||||
}, [enter]);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
@ -92,10 +88,7 @@ const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
|
||||
} as React.CSSProperties;
|
||||
|
||||
return React.cloneElement(contentElem, {
|
||||
className: cssNames("Animate", name, contentElem.props.className, {
|
||||
enter: showClassNameEnter,
|
||||
leave: showClassNameLeave,
|
||||
}),
|
||||
className: classNames,
|
||||
children: contentElem.props.children,
|
||||
style: {
|
||||
...contentElem.props.style,
|
||||
@ -112,5 +105,3 @@ export const Animate = withInjectables<Dependencies, AnimateProps>(NonInjectedAn
|
||||
defaultLeaveDuration: di.inject(defaultLeaveDurationForAnimatedInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -1,275 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import * as selectEvent from "react-select-event";
|
||||
import type { CreateCluster } from "../../../../common/cluster/create-cluster-injection-token";
|
||||
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
|
||||
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||
import type { ApplicationBuilder } from "../../test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../test-utils/get-application-builder";
|
||||
import normalizedPlatformInjectable from "../../../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "../../../../main/kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../../../../main/kubectl/normalized-arch.injectable";
|
||||
import type { OpenDeleteClusterDialog } from "../open.injectable";
|
||||
import openDeleteClusterDialogInjectable from "../open.injectable";
|
||||
|
||||
const currentClusterServerUrl = "https://localhost";
|
||||
const nonCurrentClusterServerUrl = "http://localhost";
|
||||
const multiClusterConfig = `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: ${currentClusterServerUrl}
|
||||
name: some-current-context-cluster
|
||||
- cluster:
|
||||
server: ${nonCurrentClusterServerUrl}
|
||||
name: some-non-current-context-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: some-current-context-cluster
|
||||
user: some-user
|
||||
name: some-current-context
|
||||
- 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: some-user
|
||||
user:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
const singleClusterServerUrl = "http://localhost";
|
||||
const singleClusterConfig = `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: ${singleClusterServerUrl}
|
||||
name: some-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: some-cluster
|
||||
user: some-user
|
||||
name: some-context
|
||||
current-context: some-context
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: some-user
|
||||
user:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
describe("<DeleteClusterDialog />", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.override(createContextHandlerInjectable, () => () => undefined as never);
|
||||
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never);
|
||||
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||
});
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows context switcher when deleting current cluster", async () => {
|
||||
const config = new KubeConfig();
|
||||
|
||||
config.loadFromString(multiClusterConfig);
|
||||
|
||||
const rendered = await builder.render();
|
||||
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
const createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
|
||||
const cluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
|
||||
const openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
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 openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let createCluster: CreateCluster;
|
||||
let config: KubeConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
config = new KubeConfig();
|
||||
|
||||
config.loadFromString(multiClusterConfig);
|
||||
|
||||
rendered = await builder.render();
|
||||
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
});
|
||||
|
||||
it("renders w/o errors", () => {
|
||||
expect(rendered.container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("shows warning when deleting non-current-context cluster", () => {
|
||||
const cluster = createCluster({
|
||||
id: "some-non-current-context-cluster",
|
||||
contextName: "some-non-current-context",
|
||||
preferences: {
|
||||
clusterName: "minikube",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: nonCurrentClusterServerUrl,
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const cluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
expect(rendered.getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("shows context switcher after checkbox click", () => {
|
||||
const cluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
const { getByText, getByTestId } = rendered;
|
||||
const link = getByTestId("context-switch");
|
||||
|
||||
expect(link).toBeInstanceOf(HTMLElement);
|
||||
fireEvent.click(link);
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it("given cluster in internal kubeconfig, when deleting cluster outside of current context, shows warning for internal kubeconfig cluster", () => {
|
||||
const cluster = createCluster({
|
||||
id: "some-non-current-context-cluster",
|
||||
contextName: "some-non-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-non-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: nonCurrentClusterServerUrl,
|
||||
});
|
||||
|
||||
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
expect(rendered.getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Kubeconfig with single cluster", () => {
|
||||
let rendered: RenderResult;
|
||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let createCluster: CreateCluster;
|
||||
let config: KubeConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
config = new KubeConfig();
|
||||
|
||||
config.loadFromString(singleClusterConfig);
|
||||
|
||||
rendered = await builder.render();
|
||||
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
});
|
||||
|
||||
it("shows warning if no other contexts left", () => {
|
||||
const cluster = createCluster({
|
||||
id: "some-cluster",
|
||||
contextName: "some-context",
|
||||
preferences: {
|
||||
clusterName: "some-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: singleClusterServerUrl,
|
||||
});
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -17,8 +17,6 @@ const deleteClusterDialogClusterFrameChildComponentInjectable = getInjectable({
|
||||
}),
|
||||
|
||||
injectionToken: clusterFrameChildComponentInjectionToken,
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default deleteClusterDialogClusterFrameChildComponentInjectable;
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { dumpYaml } from "@kubernetes/client-node";
|
||||
import fs from "fs";
|
||||
import * as lockFile from "proper-lockfile";
|
||||
|
||||
export async function saveKubeconfig(config: KubeConfig, path: string) {
|
||||
try {
|
||||
const release = await lockFile.lock(path);
|
||||
const contents = dumpYaml(JSON.parse(config.exportConfig()));
|
||||
|
||||
await fs.promises.writeFile(path, contents);
|
||||
await release();
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to acquire lock file.\n${e}`);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import saveKubeconfigInjectable from "./save-kubeconfig.injectable";
|
||||
|
||||
export default getGlobalOverride(saveKubeconfigInjectable, () => async () => {
|
||||
throw new Error("tried to save a mondified kubeconfig without override");
|
||||
});
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { dumpYaml } from "@kubernetes/client-node";
|
||||
import * as lockFile from "proper-lockfile";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import writeFileInjectable from "../../../common/fs/write-file.injectable";
|
||||
|
||||
export type SaveKubeconfig = (config: KubeConfig, path: string) => Promise<void>;
|
||||
|
||||
const saveKubeconfigInjectable = getInjectable({
|
||||
id: "save-kubeconfig",
|
||||
instantiate: (di): SaveKubeconfig => {
|
||||
const writeFile = di.inject(writeFileInjectable);
|
||||
|
||||
return async (config, filePath) => {
|
||||
const release = await lockFile.lock(filePath);
|
||||
|
||||
try {
|
||||
const contents = dumpYaml(JSON.parse(config.exportConfig()));
|
||||
|
||||
await writeFile(filePath, contents);
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
};
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default saveKubeconfigInjectable;
|
||||
|
||||
@ -10,22 +10,34 @@ import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { saveKubeconfig } from "./save-config";
|
||||
import { Notifications } from "../notifications";
|
||||
import type { ShowNotification } from "../notifications";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Icon } from "../icon";
|
||||
import { Select } from "../select";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc";
|
||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||
import type { DeleteClusterDialogState } from "./state.injectable";
|
||||
import deleteClusterDialogStateInjectable from "./state.injectable";
|
||||
import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
||||
import requestSetClusterAsDeletingInjectable from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
||||
import type { RequestClearClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-clear-as-deleting.injectable";
|
||||
import requestClearClusterAsDeletingInjectable from "../../../features/cluster/delete-dialog/renderer/request-clear-as-deleting.injectable";
|
||||
import type { RequestDeleteCluster } from "../../../features/cluster/delete-dialog/renderer/request-delete.injectable";
|
||||
import requestDeleteClusterInjectable from "../../../features/cluster/delete-dialog/renderer/request-delete.injectable";
|
||||
import type { SaveKubeconfig } from "./save-kubeconfig.injectable";
|
||||
import saveKubeconfigInjectable from "./save-kubeconfig.injectable";
|
||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
state: IObservableValue<DeleteClusterDialogState | undefined>;
|
||||
hotbarStore: HotbarStore;
|
||||
requestSetClusterAsDeleting: RequestSetClusterAsDeleting;
|
||||
requestDeleteCluster: RequestDeleteCluster;
|
||||
requestClearClusterAsDeleting: RequestClearClusterAsDeleting;
|
||||
showErrorNotification: ShowNotification;
|
||||
saveKubeconfig: SaveKubeconfig;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -61,18 +73,18 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
async onDelete(state: DeleteClusterDialogState) {
|
||||
const { cluster, config } = state;
|
||||
|
||||
await requestSetClusterAsDeleting(cluster.id);
|
||||
await this.props.requestSetClusterAsDeleting(cluster.id);
|
||||
this.removeContext(state);
|
||||
this.changeCurrentContext(state);
|
||||
|
||||
try {
|
||||
await saveKubeconfig(config, cluster.kubeConfigPath);
|
||||
await this.props.saveKubeconfig(config, cluster.kubeConfigPath);
|
||||
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
|
||||
await requestDeleteCluster(cluster.id);
|
||||
await this.props.requestDeleteCluster(cluster.id);
|
||||
} catch(error) {
|
||||
Notifications.error(`Cannot remove cluster, failed to process config file. ${error}`);
|
||||
this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`);
|
||||
} finally {
|
||||
await requestClearClusterAsDeleting(cluster.id);
|
||||
await this.props.requestClearClusterAsDeleting(cluster.id);
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
@ -155,6 +167,14 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
}
|
||||
|
||||
getWarningMessage({ cluster, config }: DeleteClusterDialogState) {
|
||||
if (cluster.isInLocalKubeconfig()) {
|
||||
return (
|
||||
<p data-testid="internal-kubeconfig-warning">
|
||||
Are you sure you want to delete it? It can be re-added through the copy/paste mechanism.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
||||
|
||||
if (!contexts.length) {
|
||||
@ -173,14 +193,6 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
);
|
||||
}
|
||||
|
||||
if (cluster.isInLocalKubeconfig()) {
|
||||
return (
|
||||
<p data-testid="internal-kubeconfig-warning">
|
||||
Are you sure you want to delete it? It can be re-added through the copy/paste mechanism.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p data-testid="kubeconfig-change-warning">The contents of kubeconfig file will be changed!</p>
|
||||
);
|
||||
@ -209,7 +221,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
<hr className={styles.hr} />
|
||||
<div className="mt-4">
|
||||
<Checkbox
|
||||
data-testid="context-switch"
|
||||
data-testid="delete-cluster-dialog-context-switch"
|
||||
label={(
|
||||
<>
|
||||
<span className="font-semibold">Select current-context</span>
|
||||
@ -255,6 +267,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
close={this.close}
|
||||
onClose={this.onClose}
|
||||
onOpen={state && (() => this.onOpen(state))}
|
||||
data-testid={state ? "delete-cluster-dialog" : undefined}
|
||||
>
|
||||
{state && this.renderContents(state)}
|
||||
</Dialog>
|
||||
@ -266,5 +279,10 @@ export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDele
|
||||
getProps: (di) => ({
|
||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||
state: di.inject(deleteClusterDialogStateInjectable),
|
||||
requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable),
|
||||
requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable),
|
||||
requestDeleteCluster: di.inject(requestDeleteClusterInjectable),
|
||||
saveKubeconfig: di.inject(saveKubeconfigInjectable),
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -146,13 +146,11 @@ class NonInjectedDialog extends React.PureComponent<DialogProps & Dependencies &
|
||||
};
|
||||
|
||||
render() {
|
||||
const { modal, animated, pinned, "data-testid": testId } = this.props;
|
||||
let { className } = this.props;
|
||||
const { modal, animated, pinned, "data-testid": testId, className } = this.props;
|
||||
|
||||
className = cssNames("Dialog flex center", className, { modal, pinned });
|
||||
let dialog = (
|
||||
<div
|
||||
className={className}
|
||||
className={cssNames("Dialog flex center", className, { modal, pinned })}
|
||||
onClick={stopPropagation}
|
||||
ref={this.ref}
|
||||
data-testid={testId}
|
||||
|
||||
@ -68,6 +68,7 @@ import type { FakeExtensionOptions } from "./get-extension-fake";
|
||||
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
|
||||
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import { overrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
|
||||
|
||||
type Callback = (di: DiContainer) => void | Promise<void>;
|
||||
|
||||
@ -114,6 +115,7 @@ export interface ApplicationBuilder {
|
||||
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
|
||||
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
|
||||
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
|
||||
afterWindowStart: (callback: Callback) => ApplicationBuilder;
|
||||
|
||||
startHidden: () => Promise<void>;
|
||||
render: () => Promise<RenderResult>;
|
||||
@ -168,6 +170,11 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
const beforeApplicationStartCallbacks: Callback[] = [];
|
||||
const beforeWindowStartCallbacks: Callback[] = [];
|
||||
const afterWindowStartCallbacks: Callback[] = [];
|
||||
|
||||
const fsState = new Map();
|
||||
|
||||
overrideFsWithFakes(mainDi, fsState);
|
||||
|
||||
let environment = environments.application;
|
||||
|
||||
@ -202,6 +209,7 @@ export const getApplicationBuilder = () => {
|
||||
const windowDi = getRendererDi({ doGeneralOverrides: true });
|
||||
|
||||
overrideChannelsForWindow(windowDi, windowId);
|
||||
overrideFsWithFakes(windowDi, fsState);
|
||||
|
||||
runInAction(() => {
|
||||
windowDi.register(rendererExtensionsStateInjectable);
|
||||
@ -235,6 +243,10 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
await startFrame();
|
||||
|
||||
for (const callback of afterWindowStartCallbacks) {
|
||||
await callback(windowDi);
|
||||
}
|
||||
|
||||
const history = windowDi.inject(historyInjectable);
|
||||
|
||||
const render = renderFor(windowDi);
|
||||
@ -646,6 +658,18 @@ export const getApplicationBuilder = () => {
|
||||
return builder;
|
||||
},
|
||||
|
||||
afterWindowStart(callback) {
|
||||
const alreadyRenderedWindows = builder.applicationWindow.getAll();
|
||||
|
||||
alreadyRenderedWindows.forEach((window) => {
|
||||
callback(window.di);
|
||||
});
|
||||
|
||||
afterWindowStartCallbacks.push(callback);
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
startHidden: async () => {
|
||||
mainDi.inject(lensProxyPortInjectable).set(42);
|
||||
|
||||
|
||||
@ -53,7 +53,6 @@ import cronJobTriggerDialogClusterFrameChildComponentInjectable from "./componen
|
||||
import deploymentScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-deployments/scale/deployment-scale-dialog-cluster-frame-child-component.injectable";
|
||||
import replicasetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-replicasets/scale-dialog/replicaset-scale-dialog-cluster-frame-child-component.injectable";
|
||||
import statefulsetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-statefulsets/scale/statefulset-scale-dialog-cluster-frame-child-component.injectable";
|
||||
import deleteClusterDialogClusterFrameChildComponentInjectable from "./components/delete-cluster-dialog/delete-cluster-dialog-cluster-frame-child-component.injectable";
|
||||
import kubeObjectDetailsClusterFrameChildComponentInjectable from "./components/kube-object-details/kube-object-details-cluster-frame-child-component.injectable";
|
||||
import kubeconfigDialogClusterFrameChildComponentInjectable from "./components/kubeconfig-dialog/kubeconfig-dialog-cluster-frame-child-component.injectable";
|
||||
import portForwardDialogClusterFrameChildComponentInjectable from "./port-forward/port-forward-dialog-cluster-frame-child-component.injectable";
|
||||
@ -149,7 +148,6 @@ export const getDiForUnitTesting = (
|
||||
deploymentScaleDialogClusterFrameChildComponentInjectable,
|
||||
replicasetScaleDialogClusterFrameChildComponentInjectable,
|
||||
statefulsetScaleDialogClusterFrameChildComponentInjectable,
|
||||
deleteClusterDialogClusterFrameChildComponentInjectable,
|
||||
kubeObjectDetailsClusterFrameChildComponentInjectable,
|
||||
kubeconfigDialogClusterFrameChildComponentInjectable,
|
||||
portForwardDialogClusterFrameChildComponentInjectable,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { OpenDialogOptions } from "electron";
|
||||
import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
||||
import { clusterActivateHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
||||
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
||||
import { openFilePickingDialogChannel } from "../../common/ipc/dialog";
|
||||
@ -60,18 +60,6 @@ export function requestClusterDisconnection(clusterId: ClusterId, force?: boolea
|
||||
return requestMain(clusterDisconnectHandler, clusterId, force);
|
||||
}
|
||||
|
||||
export function requestSetClusterAsDeleting(clusterId: ClusterId): Promise<void> {
|
||||
return requestMain(clusterSetDeletingHandler, clusterId);
|
||||
}
|
||||
|
||||
export function requestClearClusterAsDeleting(clusterId: ClusterId): Promise<void> {
|
||||
return requestMain(clusterClearDeletingHandler, clusterId);
|
||||
}
|
||||
|
||||
export function requestDeleteCluster(clusterId: ClusterId): Promise<void> {
|
||||
return requestMain(clusterDeleteHandler, clusterId);
|
||||
}
|
||||
|
||||
export function requestInitialClusterStates(): Promise<{ id: string; state: ClusterState }[]> {
|
||||
return requestMain(clusterStates);
|
||||
}
|
||||
|
||||
@ -7,10 +7,9 @@ import readFileInjectable from "../common/fs/read-file.injectable";
|
||||
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
||||
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
||||
import pathExistsInjectable from "../common/fs/path-exists.injectable";
|
||||
import deleteFileInjectable from "../common/fs/delete-file.injectable";
|
||||
|
||||
export const overrideFsWithFakes = (di: DiContainer) => {
|
||||
const state = new Map();
|
||||
|
||||
export const overrideFsWithFakes = (di: DiContainer, state = new Map()) => {
|
||||
const readFile = readFileFor(state);
|
||||
|
||||
di.override(readFileInjectable, () => readFile);
|
||||
@ -25,6 +24,9 @@ export const overrideFsWithFakes = (di: DiContainer) => {
|
||||
di.override(pathExistsInjectable, () => (
|
||||
(filePath: string) => Promise.resolve(state.has(filePath))
|
||||
));
|
||||
di.override(deleteFileInjectable, () => async (filePath: string) => {
|
||||
state.delete(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
const readFileFor = (state: Map<string, string>) => (filePath: string) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user