diff --git a/package.json b/package.json index a5c25143c4..c956435b81 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "open-lens", "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", - "version": "5.0.0-alpha.1", + "version": "5.0.0-alpha.2", "main": "static/build/main.js", - "copyright": "© 2021, OpenLens Authors", + "copyright": "© 2021 OpenLens Authors", "license": "MIT", "author": { "name": "OpenLens Authors" diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index fc1b36c548..2dc36b795c 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -59,7 +59,7 @@ describe("empty config", () => { mockFs(mockOpts); - await ClusterStore.getInstanceOrCreate().load(); + await ClusterStore.createInstance().load(); }); afterEach(() => { @@ -177,7 +177,7 @@ describe("config with existing clusters", () => { mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -274,7 +274,7 @@ users: mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -316,7 +316,7 @@ describe("pre 2.0 config with an existing cluster", () => { mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -386,7 +386,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -430,7 +430,7 @@ describe("pre 2.6.0 config with a cluster icon", () => { mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -469,7 +469,7 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => { mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { @@ -505,7 +505,7 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => { mockFs(mockOpts); - return ClusterStore.getInstanceOrCreate().load(); + return ClusterStore.createInstance().load(); }); afterEach(() => { diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index 8b245b2a0c..ceb45e15e0 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -5,7 +5,7 @@ import { HotbarStore } from "../hotbar-store"; describe("HotbarStore", () => { beforeEach(() => { ClusterStore.resetInstance(); - ClusterStore.getInstanceOrCreate(); + ClusterStore.createInstance(); HotbarStore.resetInstance(); mockFs({ tmp: { "lens-hotbar-store.json": "{}" } }); @@ -17,8 +17,18 @@ describe("HotbarStore", () => { describe("load", () => { it("loads one hotbar by default", () => { - HotbarStore.getInstanceOrCreate().load(); + HotbarStore.createInstance().load(); expect(HotbarStore.getInstance().hotbars.length).toEqual(1); }); }); + + describe("add", () => { + it("adds a hotbar", () => { + const hotbarStore = HotbarStore.createInstance(); + + hotbarStore.load(); + hotbarStore.add({ name: "hottest" }); + expect(hotbarStore.hotbars.length).toEqual(2); + }); + }); }); diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index 0bb1b90de4..e4622ffa64 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -28,7 +28,7 @@ describe("user store tests", () => { UserStore.resetInstance(); mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } }); - (UserStore.getInstanceOrCreate() as any).refreshNewContexts = jest.fn(() => Promise.resolve()); + (UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve()); return UserStore.getInstance().load(); }); @@ -102,7 +102,7 @@ describe("user store tests", () => { } }); - return UserStore.getInstanceOrCreate().load(); + return UserStore.createInstance().load(); }); afterEach(() => { diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 86ecff96ce..486626bbd9 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -5,6 +5,7 @@ import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEnti import { clusterDisconnectHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; import { requestMain } from "../ipc"; +import { productName } from "../vars"; export type KubernetesClusterSpec = { kubeconfigPath: string; @@ -59,7 +60,7 @@ export class KubernetesCluster implements CatalogEntity { onlyVisibleForSource: "local", onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid), confirm: { - message: `Remove Kubernetes Cluster "${this.metadata.name} from Lens?` + message: `Remove Kubernetes Cluster "${this.metadata.name} from ${productName}?` } }, ]; diff --git a/src/common/hotbar-store.ts b/src/common/hotbar-store.ts index 4dd00205ef..a3957bbd5c 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbar-store.ts @@ -1,6 +1,7 @@ import { action, comparer, observable, toJS, makeObservable } from "mobx"; import { BaseStore } from "./base-store"; import migrations from "../migrations/hotbar-store"; +import * as uuid from "uuid"; export interface HotbarItem { entity: { @@ -12,16 +13,25 @@ export interface HotbarItem { } export interface Hotbar { + id: string; name: string; items: HotbarItem[]; } +export interface HotbarCreateOptions { + id?: string; + name: string; + items?: HotbarItem[]; +} + export interface HotbarStoreModel { hotbars: Hotbar[]; + activeHotbarId: string; } export class HotbarStore extends BaseStore { @observable hotbars: Hotbar[] = []; + @observable private _activeHotbarId: string; constructor() { super({ @@ -35,32 +45,105 @@ export class HotbarStore extends BaseStore { makeObservable(this); } + get activeHotbarId() { + return this._activeHotbarId; + } + + set activeHotbarId(id: string) { + if (this.getById(id)) { + this._activeHotbarId = id; + } + } + + get activeHotbarIndex() { + return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId); + } + @action protected async fromStore(data: Partial = {}) { if (data.hotbars?.length === 0) { this.hotbars = [{ - name: "default", + id: uuid.v4(), + name: "Default", items: [] }]; } else { this.hotbars = data.hotbars; } + + if (data.activeHotbarId) { + if (this.getById(data.activeHotbarId)) { + this.activeHotbarId = data.activeHotbarId; + } + } + + if (!this.activeHotbarId) { + this.activeHotbarId = this.hotbars[0].id; + } + } + + getActive() { + return this.getById(this.activeHotbarId); } getByName(name: string) { return this.hotbars.find((hotbar) => hotbar.name === name); } - add(hotbar: Hotbar) { - this.hotbars.push(hotbar); + getById(id: string) { + return this.hotbars.find((hotbar) => hotbar.id === id); } + add(data: HotbarCreateOptions) { + const { + id = uuid.v4(), + items = [], + name, + } = data; + + const hotbar = { id, name, items }; + + this.hotbars.push(hotbar as Hotbar); + + return hotbar as Hotbar; + } + + @action remove(hotbar: Hotbar) { this.hotbars = this.hotbars.filter((h) => h !== hotbar); + + if (this.activeHotbarId === hotbar.id) { + this.activeHotbarId = this.hotbars[0].id; + } + } + + switchToPrevious() { + const hotbarStore = HotbarStore.getInstance(); + let index = hotbarStore.activeHotbarIndex - 1; + + if (index < 0) { + index = hotbarStore.hotbars.length - 1; + } + + hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id; + } + + switchToNext() { + const hotbarStore = HotbarStore.getInstance(); + let index = hotbarStore.activeHotbarIndex + 1; + + if (index >= hotbarStore.hotbars.length) { + index = 0; + } + + hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id; } toJSON(): HotbarStoreModel { - return toJS({ - hotbars: this.hotbars - }); + const model: HotbarStoreModel = { + hotbars: this.hotbars, + activeHotbarId: this.activeHotbarId + }; + + return toJS(model); } } diff --git a/src/common/utils/singleton.ts b/src/common/utils/singleton.ts index 2f19fd638d..04c0b063a5 100644 --- a/src/common/utils/singleton.ts +++ b/src/common/utils/singleton.ts @@ -1,10 +1,3 @@ -/** - * Narrowing class instances to the one. - * Use "private" or "protected" modifier for constructor (when overriding) to disallow "new" usage. - * - * @example - * const usersStore: UsersStore = UsersStore.getInstance(); - */ type StaticThis = { new(...args: R): T }; export class Singleton { @@ -13,12 +6,28 @@ export class Singleton { constructor() { if (Singleton.creating.length === 0) { - throw new TypeError("A singleton class must be created by getInstanceOrCreate()"); + throw new TypeError("A singleton class must be created by createInstance()"); } } - static getInstanceOrCreate(this: StaticThis, ...args: R): T { + /** + * Creates the single instance of the child class if one was not already created. + * + * Multiple calls will return the same instance. + * Essentially throwing away the arguments to the subsequent calls. + * + * Note: this is a racy function, if two (or more) calls are racing to call this function + * only the first's arguments will be used. + * @param this Implicit argument that is the child class type + * @param args The constructor arguments for the child class + * @returns An instance of the child class + */ + static createInstance(this: StaticThis, ...args: R): T { if (!Singleton.instances.has(this)) { + if (Singleton.creating.length > 0) { + throw new TypeError("Cannot create a second singleton while creating a first"); + } + Singleton.creating = this.name; Singleton.instances.set(this, new this(...args)); Singleton.creating = ""; @@ -27,6 +36,13 @@ export class Singleton { return Singleton.instances.get(this) as T; } + /** + * Get the instance of the child class that was previously created. + * @param this Implicit argument that is the child class type + * @param strict If false will return `undefined` instead of throwing when an instance doesn't exist. + * Default: `true` + * @returns An instance of the child class + */ static getInstance(this: StaticThis, strict = true): T | undefined { if (!Singleton.instances.has(this) && strict) { throw new TypeError(`instance of ${this.name} is not created`); @@ -35,6 +51,13 @@ export class Singleton { return Singleton.instances.get(this) as (T | undefined); } + /** + * Delete the instance of the child class. + * + * Note: this doesn't prevent callers of `getInstance` from storing the result in a global. + * + * There is *no* way in JS or TS to prevent globals like that. + */ static resetInstance() { Singleton.instances.delete(this); } diff --git a/src/common/vars.ts b/src/common/vars.ts index 396a1077c5..e30c050a02 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -11,7 +11,9 @@ export const isSnap = !!process.env.SNAP; export const isProduction = process.env.NODE_ENV === "production"; export const isTestEnv = !!process.env.JEST_WORKER_ID; export const isDevelopment = !isTestEnv && !isProduction; +export const isPublishConfigured = Object.keys(packageInfo.build).includes("publish"); +export const productName = packageInfo.productName; export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`; export const publicPath = "/build/"; diff --git a/src/extensions/__tests__/extension-discovery.test.ts b/src/extensions/__tests__/extension-discovery.test.ts index d185302b65..c4775f29fe 100644 --- a/src/extensions/__tests__/extension-discovery.test.ts +++ b/src/extensions/__tests__/extension-discovery.test.ts @@ -21,7 +21,7 @@ describe("ExtensionDiscovery", () => { beforeEach(() => { ExtensionDiscovery.resetInstance(); ExtensionsStore.resetInstance(); - ExtensionsStore.getInstanceOrCreate(); + ExtensionsStore.createInstance(); }); it("emits add for added extension", async done => { @@ -43,7 +43,7 @@ describe("ExtensionDiscovery", () => { mockedWatch.mockImplementationOnce(() => (mockWatchInstance) as any ); - const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate(); + const extensionDiscovery = ExtensionDiscovery.createInstance(); // Need to force isLoaded to be true so that the file watching is started extensionDiscovery.isLoaded = true; @@ -83,7 +83,7 @@ describe("ExtensionDiscovery", () => { mockedWatch.mockImplementationOnce(() => (mockWatchInstance) as any ); - const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate(); + const extensionDiscovery = ExtensionDiscovery.createInstance(); // Need to force isLoaded to be true so that the file watching is started extensionDiscovery.isLoaded = true; diff --git a/src/extensions/__tests__/extension-loader.test.ts b/src/extensions/__tests__/extension-loader.test.ts index ed7025f218..1115a3d5bc 100644 --- a/src/extensions/__tests__/extension-loader.test.ts +++ b/src/extensions/__tests__/extension-loader.test.ts @@ -110,7 +110,7 @@ describe("ExtensionLoader", () => { }); it.only("renderer updates extension after ipc broadcast", async (done) => { - const extensionLoader = ExtensionLoader.getInstanceOrCreate(); + const extensionLoader = ExtensionLoader.createInstance(); expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`); @@ -155,7 +155,7 @@ describe("ExtensionLoader", () => { // Disable sending events in this test (ipcRenderer.on as any).mockImplementation(); - const extensionLoader = ExtensionLoader.getInstanceOrCreate(); + const extensionLoader = ExtensionLoader.createInstance(); await extensionLoader.init(); diff --git a/src/extensions/npm/extensions/package.json b/src/extensions/npm/extensions/package.json index 77fcc25a11..6857b12dda 100644 --- a/src/extensions/npm/extensions/package.json +++ b/src/extensions/npm/extensions/package.json @@ -1,9 +1,9 @@ { "name": "@k8slens/extensions", - "productName": "Lens extensions", - "description": "Lens - The Kubernetes IDE: extensions", + "productName": "OpenLens extensions", + "description": "OpenLens - Open Source Kubernetes IDE: extensions", "version": "0.0.0", - "copyright": "© 2021, Mirantis, Inc.", + "copyright": "© 2021 OpenLens Authors", "license": "MIT", "main": "dist/src/extensions/extension-api.js", "types": "dist/src/extensions/extension-api.d.ts", @@ -13,8 +13,7 @@ "dist/**/*.js" ], "author": { - "name": "Mirantis, Inc.", - "email": "info@k8slens.dev" + "name": "OpenLens Authors" }, "dependencies": { "@types/node": "*", diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts index dbed900fd7..612db10a01 100644 --- a/src/main/__test__/kube-auth-proxy.test.ts +++ b/src/main/__test__/kube-auth-proxy.test.ts @@ -50,7 +50,7 @@ describe("kube auth proxy tests", () => { beforeEach(() => { jest.clearAllMocks(); UserStore.resetInstance(); - UserStore.getInstanceOrCreate(); + UserStore.createInstance(); }); it("calling exit multiple times shouldn't throw", async () => { diff --git a/src/main/__test__/kubeconfig-manager.test.ts b/src/main/__test__/kubeconfig-manager.test.ts index 5ae7681d37..cf93f71957 100644 --- a/src/main/__test__/kubeconfig-manager.test.ts +++ b/src/main/__test__/kubeconfig-manager.test.ts @@ -76,7 +76,7 @@ describe("kubeconfig manager tests", () => { }); const contextHandler = new ContextHandler(cluster); const port = await getFreePort(); - const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port); + const kubeConfManager = new KubeconfigManager(cluster, contextHandler, port); expect(logger.error).not.toBeCalled(); expect(await kubeConfManager.getPath()).toBe(`tmp${path.sep}kubeconfig-foo`); @@ -98,13 +98,15 @@ describe("kubeconfig manager tests", () => { }); const contextHandler = new ContextHandler(cluster); const port = await getFreePort(); - const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port); + const kubeConfManager = new KubeconfigManager(cluster, contextHandler, port); const configPath = await kubeConfManager.getPath(); expect(await fse.pathExists(configPath)).toBe(true); await kubeConfManager.unlink(); expect(await fse.pathExists(configPath)).toBe(false); await kubeConfManager.unlink(); // doesn't throw - expect(await kubeConfManager.getPath()).toBeUndefined(); + expect(async () => { + await kubeConfManager.getPath(); + }).rejects.toThrow("already unlinked"); }); }); diff --git a/src/main/app-updater.ts b/src/main/app-updater.ts index 8f31df8b57..77ae2bff18 100644 --- a/src/main/app-updater.ts +++ b/src/main/app-updater.ts @@ -1,6 +1,6 @@ import { autoUpdater, UpdateInfo } from "electron-updater"; import logger from "./logger"; -import { isDevelopment, isTestEnv } from "../common/vars"; +import { isDevelopment, isPublishConfigured, isTestEnv } from "../common/vars"; import { delay } from "../common/utils"; import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc"; import { once } from "lodash"; @@ -8,6 +8,10 @@ import { app, ipcMain } from "electron"; let installVersion: null | string = null; +export function isAutoUpdateEnabled() { + return autoUpdater.isUpdaterActive() && isPublishConfigured; +} + function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) { if (arg.doUpdate) { if (arg.now) { diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 2ac463fc31..ee8f076091 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -293,7 +293,7 @@ export class Cluster implements ClusterModel, ClusterState { try { this.initializing = true; this.contextHandler = new ContextHandler(this); - this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port); + this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port); this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`; this.initialized = true; logger.info(`[CLUSTER]: "${this.contextName}" init success`, { diff --git a/src/main/index.ts b/src/main/index.ts index 21195e7b45..c1d99cbd76 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,7 +6,7 @@ import "../common/prometheus-providers"; import * as Mobx from "mobx"; import * as LensExtensions from "../extensions/core-api"; import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron"; -import { appName, isMac } from "../common/vars"; +import { appName, isMac, productName } from "../common/vars"; import path from "path"; import { LensProxy } from "./lens-proxy"; import { WindowManager } from "./window-manager"; @@ -38,12 +38,12 @@ const workingDir = path.join(app.getPath("appData"), appName); app.setName(appName); -logger.info("📟 Setting Lens as protocol client for lens://"); +logger.info(`📟 Setting ${productName} as protocol client for lens://`); if (app.setAsDefaultProtocolClient("lens")) { - logger.info("📟 succeeded ✅"); + logger.info("📟 Protocol client register succeeded ✅"); } else { - logger.info("📟 failed ❗"); + logger.info("📟 Protocol client register failed ❗"); } if (!process.env.CICD) { @@ -63,7 +63,7 @@ if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (!app.requestSingleInstanceLock()) { app.exit(); } else { - const lprm = LensProtocolRouterMain.getInstanceOrCreate(); + const lprm = LensProtocolRouterMain.createInstance(); for (const arg of process.argv) { if (arg.toLowerCase().startsWith("lens://")) { @@ -74,7 +74,7 @@ if (!app.requestSingleInstanceLock()) { } app.on("second-instance", (event, argv) => { - const lprm = LensProtocolRouterMain.getInstanceOrCreate(); + const lprm = LensProtocolRouterMain.createInstance(); for (const arg of argv) { if (arg.toLowerCase().startsWith("lens://")) { @@ -87,7 +87,7 @@ app.on("second-instance", (event, argv) => { }); app.on("ready", async () => { - logger.info(`🚀 Starting Lens from "${workingDir}"`); + logger.info(`🚀 Starting ${productName} from "${workingDir}"`); logger.info("🐚 Syncing shell environment"); await shellSync(); @@ -99,11 +99,11 @@ app.on("ready", async () => { registerFileProtocol("static", __static); - const userStore = UserStore.getInstanceOrCreate(); - const clusterStore = ClusterStore.getInstanceOrCreate(); - const hotbarStore = HotbarStore.getInstanceOrCreate(); - const extensionsStore = ExtensionsStore.getInstanceOrCreate(); - const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate(); + const userStore = UserStore.createInstance(); + const clusterStore = ClusterStore.createInstance(); + const hotbarStore = HotbarStore.createInstance(); + const extensionsStore = ExtensionsStore.createInstance(); + const filesystemStore = FilesystemProvisionerStore.createInstance(); logger.info("💾 Loading stores"); // preload @@ -115,36 +115,35 @@ app.on("ready", async () => { filesystemStore.load(), ]); - // find free port - let proxyPort; - try { logger.info("🔑 Getting free port for LensProxy server"); - proxyPort = await getFreePort(); + const proxyPort = await getFreePort(); + + // create cluster manager + ClusterManager.createInstance(proxyPort); } catch (error) { logger.error(error); dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy"); app.exit(); } - // create cluster manager - ClusterManager.getInstanceOrCreate(proxyPort); + const clusterManager = ClusterManager.getInstance(); // run proxy try { logger.info("🔌 Starting LensProxy"); // eslint-disable-next-line unused-imports/no-unused-vars-ts - LensProxy.getInstanceOrCreate(proxyPort).listen(); + LensProxy.createInstance(clusterManager.port).listen(); } catch (error) { - logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`); - dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`); + logger.error(`Could not start proxy (127.0.0:${clusterManager.port}): ${error?.message}`); + dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${clusterManager.port}): ${error?.message || "unknown error"}`); app.exit(); } // test proxy connection try { logger.info("🔎 Testing LensProxy connection ..."); - const versionFromProxy = await getAppVersionFromProxyServer(proxyPort); + const versionFromProxy = await getAppVersionFromProxyServer(clusterManager.port); if (getAppVersion() !== versionFromProxy) { logger.error(`Proxy server responded with invalid response`); @@ -154,9 +153,9 @@ app.on("ready", async () => { logger.error("Checking proxy server connection failed", error); } - const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate(); + const extensionDiscovery = ExtensionDiscovery.createInstance(); - ExtensionLoader.getInstanceOrCreate().init(); + ExtensionLoader.createInstance().init(); extensionDiscovery.init(); // Start the app without showing the main window when auto starting on login @@ -164,7 +163,7 @@ app.on("ready", async () => { const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden); logger.info("🖥️ Starting WindowManager"); - const windowManager = WindowManager.getInstanceOrCreate(proxyPort); + const windowManager = WindowManager.createInstance(clusterManager.port); installDeveloperTools(); diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index f88ce87840..6fe5cddf39 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -9,16 +9,39 @@ import logger from "./logger"; export class KubeconfigManager { protected configDir = app.getPath("temp"); - protected tempFile: string; + protected tempFile: string = null; - private constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { } + constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { } - static async create(cluster: Cluster, contextHandler: ContextHandler, port: number) { - const kcm = new KubeconfigManager(cluster, contextHandler, port); + async getPath(): Promise { + if (this.tempFile === undefined) { + throw new Error("kubeconfig is already unlinked"); + } - await kcm.init(); + if (!this.tempFile) { + await this.init(); + } - return kcm; + // create proxy kubeconfig if it is removed without unlink called + if (!(await fs.pathExists(this.tempFile))) { + try { + this.tempFile = await this.createProxyKubeconfig(); + } catch (err) { + logger.error(`Failed to created temp config for auth-proxy`, { err }); + } + } + + return this.tempFile; + } + + async unlink() { + if (!this.tempFile) { + return; + } + + logger.info(`Deleting temporary kubeconfig: ${this.tempFile}`); + await fs.unlink(this.tempFile); + this.tempFile = undefined; } protected async init() { @@ -30,20 +53,6 @@ export class KubeconfigManager { } } - async getPath() { - // create proxy kubeconfig if it is removed - if (this.tempFile !== undefined && !(await fs.pathExists(this.tempFile))) { - try { - this.tempFile = await this.createProxyKubeconfig(); - } catch (err) { - logger.error(`Failed to created temp config for auth-proxy`, { err }); - } - } - - return this.tempFile; - - } - protected resolveProxyUrl() { return `http://127.0.0.1:${this.port}/${this.cluster.id}`; } @@ -87,14 +96,4 @@ export class KubeconfigManager { return tempFile; } - - async unlink() { - if (!this.tempFile) { - return; - } - - logger.info(`Deleting temporary kubeconfig: ${this.tempFile}`); - await fs.unlink(this.tempFile); - this.tempFile = undefined; - } } diff --git a/src/main/menu.ts b/src/main/menu.ts index 49d4cfdb92..842f95efae 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow, dialog, ipcMain, IpcMainEvent, Menu, MenuItem, MenuItemConstructorOptions, webContents, shell } from "electron"; import { autorun } from "mobx"; import { WindowManager } from "./window-manager"; -import { appName, isMac, isWindows, isTestEnv, docsUrl, supportUrl } from "../common/vars"; +import { appName, isMac, isWindows, isTestEnv, docsUrl, supportUrl, productName } from "../common/vars"; import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route"; import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route"; @@ -34,7 +34,7 @@ export function showAbout(browserWindow: BrowserWindow) { title: `${isWindows ? " ".repeat(2) : ""}${appName}`, type: "info", buttons: ["Close"], - message: `Lens`, + message: productName, detail: appInfo.join("\r\n") }); } @@ -55,7 +55,7 @@ export function buildMenu(windowManager: WindowManager) { label: app.getName(), submenu: [ { - label: "About Lens", + label: `About ${productName}`, click(menuItem: MenuItem, browserWindow: BrowserWindow) { showAbout(browserWindow); } @@ -220,7 +220,7 @@ export function buildMenu(windowManager: WindowManager) { }, ...ignoreOnMac([ { - label: "About Lens", + label: `About ${productName}`, click(menuItem: MenuItem, browserWindow: BrowserWindow) { showAbout(browserWindow); } diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index 88cc4fec44..408fde02aa 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -17,10 +17,10 @@ function throwIfDefined(val: any): void { describe("protocol router tests", () => { beforeEach(() => { - ExtensionsStore.getInstanceOrCreate(); - ExtensionLoader.getInstanceOrCreate(); + ExtensionsStore.createInstance(); + ExtensionLoader.createInstance(); - const lpr = LensProtocolRouterMain.getInstanceOrCreate(); + const lpr = LensProtocolRouterMain.createInstance(); lpr.extensionsLoaded = true; lpr.rendererLoaded = true; diff --git a/src/main/tray.ts b/src/main/tray.ts index a15fa845d3..21ad116c6d 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -3,11 +3,11 @@ import packageInfo from "../../package.json"; import { Menu, Tray } from "electron"; import { autorun } from "mobx"; import { showAbout } from "./menu"; -import { checkForUpdates } from "./app-updater"; +import { checkForUpdates, isAutoUpdateEnabled } from "./app-updater"; import { WindowManager } from "./window-manager"; import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; import logger from "./logger"; -import { isDevelopment, isWindows } from "../common/vars"; +import { isDevelopment, isWindows, productName } from "../common/vars"; import { exitApp } from "./exit-app"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -58,9 +58,9 @@ export function initTray(windowManager: WindowManager) { } function createTrayMenu(windowManager: WindowManager): Menu { - return Menu.buildFromTemplate([ + const template: Electron.MenuItemConstructorOptions[] = [ { - label: "Open Lens", + label: `Open ${productName}`, click() { windowManager .ensureMainWindow() @@ -74,16 +74,22 @@ function createTrayMenu(windowManager: WindowManager): Menu { .navigate(preferencesURL()) .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to Preferences`, { error })); }, - }, - { + } + ]; + + if (isAutoUpdateEnabled()) { + template.push({ label: "Check for updates", click() { checkForUpdates() .then(() => windowManager.ensureMainWindow()); }, - }, + }); + } + + return Menu.buildFromTemplate(template.concat([ { - label: "About Lens", + label: `About ${productName}`, click() { windowManager.ensureMainWindow() .then(showAbout) @@ -97,5 +103,5 @@ function createTrayMenu(windowManager: WindowManager): Menu { exitApp(); } } - ]); + ])); } diff --git a/src/migrations/hotbar-store/5.0.0-alpha.0.ts b/src/migrations/hotbar-store/5.0.0-alpha.0.ts index 7f2866b193..08d3e9785c 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.0.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.0.ts @@ -2,6 +2,7 @@ import { Hotbar } from "../../common/hotbar-store"; import { ClusterStore } from "../../common/cluster-store"; import { migration } from "../migration-wrapper"; +import { v4 as uuid } from "uuid"; export default migration({ version: "5.0.0-alpha.0", @@ -9,11 +10,14 @@ export default migration({ const hotbars: Hotbar[] = []; ClusterStore.getInstance().enabledClustersList.forEach((cluster: any) => { - const name = cluster.workspace || "default"; + const name = cluster.workspace; + + if (!name) return; + let hotbar = hotbars.find((h) => h.name === name); if (!hotbar) { - hotbar = { name, items: [] }; + hotbar = { id: uuid(), name, items: [] }; hotbars.push(hotbar); } diff --git a/src/migrations/hotbar-store/5.0.0-alpha.2.ts b/src/migrations/hotbar-store/5.0.0-alpha.2.ts new file mode 100644 index 0000000000..060fa9f542 --- /dev/null +++ b/src/migrations/hotbar-store/5.0.0-alpha.2.ts @@ -0,0 +1,16 @@ +// Cleans up a store that had the state related data stored +import { Hotbar } from "../../common/hotbar-store"; +import { migration } from "../migration-wrapper"; +import * as uuid from "uuid"; + +export default migration({ + version: "5.0.0-alpha.2", + run(store) { + const hotbars = (store.get("hotbars") || []) as Hotbar[]; + + store.set("hotbars", hotbars.map((hotbar) => ({ + id: uuid.v4(), + ...hotbar + }))); + } +}); diff --git a/src/migrations/hotbar-store/index.ts b/src/migrations/hotbar-store/index.ts index ae9d4bc125..842f144a18 100644 --- a/src/migrations/hotbar-store/index.ts +++ b/src/migrations/hotbar-store/index.ts @@ -1,7 +1,9 @@ // Hotbar store migrations import version500alpha0 from "./5.0.0-alpha.0"; +import version500alpha2 from "./5.0.0-alpha.2"; export default { ...version500alpha0, + ...version500alpha2 }; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 10a129ae63..18cc3e92a5 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -51,16 +51,16 @@ export async function bootstrap(App: AppComponent) { await attachChromeDebugger(); rootElem.classList.toggle("is-mac", isMac); - ExtensionLoader.getInstanceOrCreate().init(); - ExtensionDiscovery.getInstanceOrCreate().init(); + ExtensionLoader.createInstance().init(); + ExtensionDiscovery.createInstance().init(); - const userStore = UserStore.getInstanceOrCreate(); - const clusterStore = ClusterStore.getInstanceOrCreate(); - const extensionsStore = ExtensionsStore.getInstanceOrCreate(); - const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate(); - const themeStore = ThemeStore.getInstanceOrCreate(); - const hotbarStore = HotbarStore.getInstanceOrCreate(); - const helmRepoManager = HelmRepoManager.getInstanceOrCreate(); + const userStore = UserStore.createInstance(); + const clusterStore = ClusterStore.createInstance(); + const extensionsStore = ExtensionsStore.createInstance(); + const filesystemStore = FilesystemProvisionerStore.createInstance(); + const themeStore = ThemeStore.createInstance(); + const hotbarStore = HotbarStore.createInstance(); + const helmRepoManager = HelmRepoManager.createInstance(); // preload common stores await Promise.all([ diff --git a/src/renderer/components/+apps-helm-charts/helm-chart.store.ts b/src/renderer/components/+apps-helm-charts/helm-chart.store.ts index 678bda72e9..e52eccd7c8 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart.store.ts +++ b/src/renderer/components/+apps-helm-charts/helm-chart.store.ts @@ -38,7 +38,10 @@ export class HelmChartStore extends ItemStore { protected sortVersions = (versions: IChartVersion[]) => { return versions.sort((first, second) => { - return semver.compare(second.version, first.version); + const firstVersion = semver.coerce(first.version || 0); + const secondVersion = semver.coerce(second.version || 0); + + return semver.compare(secondVersion, firstVersion); }); }; diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index aec3e76ee9..e81004e97a 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -62,7 +62,7 @@ export class Catalog extends React.Component { } addToHotbar(item: CatalogEntityItem) { - const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME + const hotbar = HotbarStore.getInstance().getActive(); if (!hotbar) { return; @@ -71,16 +71,6 @@ export class Catalog extends React.Component { hotbar.items.push({ entity: { uid: item.id }}); } - removeFromHotbar(item: CatalogEntityItem) { - const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME - - if (!hotbar) { - return; - } - - hotbar.items = hotbar.items.filter((i) => i.entity.uid !== item.id); - } - onDetails(item: CatalogEntityItem) { item.onRun(catalogEntityRunContext); } @@ -142,9 +132,6 @@ export class Catalog extends React.Component { this.addToHotbar(item) }> Add to Hotbar - this.removeFromHotbar(item) }> - Remove from Hotbar - { menuItems.map((menuItem, index) => { return ( this.onMenuItemClick(menuItem)}> diff --git a/src/renderer/components/+extensions/__tests__/extensions.test.tsx b/src/renderer/components/+extensions/__tests__/extensions.test.tsx index d51b1223f0..6e56c112ca 100644 --- a/src/renderer/components/+extensions/__tests__/extensions.test.tsx +++ b/src/renderer/components/+extensions/__tests__/extensions.test.tsx @@ -42,16 +42,16 @@ describe("Extensions", () => { UserStore.resetInstance(); ThemeStore.resetInstance(); - await UserStore.getInstanceOrCreate().load(); - await ThemeStore.getInstanceOrCreate().init(); + await UserStore.createInstance().load(); + await ThemeStore.createInstance().init(); ExtensionLoader.resetInstance(); ExtensionDiscovery.resetInstance(); Extensions.installStates.clear(); - ExtensionDiscovery.getInstanceOrCreate().uninstallExtension = jest.fn(() => Promise.resolve()); + ExtensionDiscovery.createInstance().uninstallExtension = jest.fn(() => Promise.resolve()); - ExtensionLoader.getInstanceOrCreate().addExtension({ + ExtensionLoader.createInstance().addExtension({ id: "extensionId", manifest: { name: "test", diff --git a/src/renderer/components/dock/__test__/log-resource-selector.test.tsx b/src/renderer/components/dock/__test__/log-resource-selector.test.tsx index 90f4b79972..ac57f50ef7 100644 --- a/src/renderer/components/dock/__test__/log-resource-selector.test.tsx +++ b/src/renderer/components/dock/__test__/log-resource-selector.test.tsx @@ -44,8 +44,8 @@ const getFewPodsTabData = (): LogTabData => { describe("", () => { beforeEach(() => { - UserStore.getInstanceOrCreate(); - ThemeStore.getInstanceOrCreate(); + UserStore.createInstance(); + ThemeStore.createInstance(); }); afterEach(() => { diff --git a/src/renderer/components/hotbar/hotbar-add-command.tsx b/src/renderer/components/hotbar/hotbar-add-command.tsx new file mode 100644 index 0000000000..5ec5734219 --- /dev/null +++ b/src/renderer/components/hotbar/hotbar-add-command.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { observer } from "mobx-react"; +import { HotbarStore } from "../../../common/hotbar-store"; +import { CommandOverlay } from "../command-palette"; +import { Input, InputValidator } from "../input"; + +const uniqueHotbarName: InputValidator = { + condition: ({ required }) => required, + message: () => "Hotbar with this name already exists", + validate: value => !HotbarStore.getInstance().getByName(value), +}; + +@observer +export class HotbarAddCommand extends React.Component { + + onSubmit(name: string) { + if (!name.trim()) { + return; + } + + const hotbarStore = HotbarStore.getInstance(); + + const hotbar = hotbarStore.add({ + name + }); + + hotbarStore.activeHotbarId = hotbar.id; + + CommandOverlay.close(); + } + + render() { + return ( + <> + this.onSubmit(v)} + dirty={true} + showValidationLine={true} /> + + Please provide a new hotbar name (Press "Enter" to confirm or "Escape" to cancel) + + + ); + } +} diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index d88f5525fc..b11c5975d9 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -65,7 +65,7 @@ export class HotbarIcon extends React.Component { } removeFromHotbar(item: CatalogEntity) { - const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME + const hotbar = HotbarStore.getInstance().getActive(); if (!hotbar) { return; diff --git a/src/renderer/components/hotbar/hotbar-menu.scss b/src/renderer/components/hotbar/hotbar-menu.scss index 22af27e486..e8cf6efc1a 100644 --- a/src/renderer/components/hotbar/hotbar-menu.scss +++ b/src/renderer/components/hotbar/hotbar-menu.scss @@ -22,4 +22,14 @@ display: none; } } + + .HotbarSelector { + position: absolute; + bottom: 0; + width: 100%; + + .Badge { + cursor: pointer; + } + } } diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index 056494338d..197671b034 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -1,4 +1,5 @@ import "./hotbar-menu.scss"; +import "./hotbar.commands"; import React from "react"; import { observer } from "mobx-react"; @@ -7,6 +8,10 @@ import { cssNames, IClassName } from "../../utils"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { HotbarStore } from "../../../common/hotbar-store"; import { catalogEntityRunContext } from "../../api/catalog-entity"; +import { Icon } from "../icon"; +import { Badge } from "../badge"; +import { CommandOverlay } from "../command-palette"; +import { HotbarSwitchCommand } from "./hotbar-switch-command"; interface Props { className?: IClassName; @@ -14,9 +19,8 @@ interface Props { @observer export class HotbarMenu extends React.Component { - get hotbarItems() { - const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME + const hotbar = HotbarStore.getInstance().getActive(); if (!hotbar) { return []; @@ -25,8 +29,21 @@ export class HotbarMenu extends React.Component { return hotbar.items.map((item) => catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid)).filter(Boolean); } + previous() { + HotbarStore.getInstance().switchToPrevious(); + } + + next() { + HotbarStore.getInstance().switchToNext(); + } + + openSelector() { + CommandOverlay.open(); + } + render() { const { className } = this.props; + const hotbarIndex = HotbarStore.getInstance().activeHotbarIndex + 1; return (
@@ -43,6 +60,13 @@ export class HotbarMenu extends React.Component { ); })}
+
+ this.previous()} /> +
+ this.openSelector()} /> +
+ this.next()} /> +
); } diff --git a/src/renderer/components/hotbar/hotbar-remove-command.tsx b/src/renderer/components/hotbar/hotbar-remove-command.tsx new file mode 100644 index 0000000000..185ff65497 --- /dev/null +++ b/src/renderer/components/hotbar/hotbar-remove-command.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { observer } from "mobx-react"; +import { Select } from "../select"; +import { computed } from "mobx"; +import { HotbarStore } from "../../../common/hotbar-store"; +import { CommandOverlay } from "../command-palette"; +import { ConfirmDialog } from "../confirm-dialog"; + +@observer +export class HotbarRemoveCommand extends React.Component { + @computed get options() { + return HotbarStore.getInstance().hotbars.map((hotbar) => { + return { value: hotbar.id, label: hotbar.name }; + }); + } + + onChange(id: string): void { + const hotbarStore = HotbarStore.getInstance(); + const hotbar = hotbarStore.getById(id); + + if (!hotbar) { + return; + } + + CommandOverlay.close(); + ConfirmDialog.open({ + okButtonProps: { + label: `Remove Hotbar`, + primary: false, + accent: true, + }, + ok: () => { + hotbarStore.remove(hotbar); + }, + message: ( +
+

+ Are you sure you want remove hotbar {hotbar.name}? +

+
+ ), + }); + } + + render() { + return ( + this.onChange(v.value)} + components={{ DropdownIndicator: null, IndicatorSeparator: null }} + menuIsOpen={true} + options={this.options} + autoFocus={true} + escapeClearsValue={false} + placeholder="Switch to hotbar" /> + ); + } +} diff --git a/src/renderer/components/hotbar/hotbar.commands.tsx b/src/renderer/components/hotbar/hotbar.commands.tsx new file mode 100644 index 0000000000..c2ee3a05dd --- /dev/null +++ b/src/renderer/components/hotbar/hotbar.commands.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { commandRegistry } from "../../../extensions/registries"; +import { CommandOverlay } from "../command-palette"; +import { HotbarAddCommand } from "./hotbar-add-command"; +import { HotbarRemoveCommand } from "./hotbar-remove-command"; +import { HotbarSwitchCommand } from "./hotbar-switch-command"; + +commandRegistry.add({ + id: "hotbar.switchHotbar", + title: "Hotbar: Switch ...", + scope: "global", + action: () => CommandOverlay.open() +}); + +commandRegistry.add({ + id: "hotbar.addHotbar", + title: "Hotbar: Add Hotbar ...", + scope: "global", + action: () => CommandOverlay.open() +}); + +commandRegistry.add({ + id: "hotbar.removeHotbar", + title: "Hotbar: Remove Hotbar ...", + scope: "global", + action: () => CommandOverlay.open() +}); diff --git a/src/renderer/components/layout/__test__/main-layout-header.test.tsx b/src/renderer/components/layout/__test__/main-layout-header.test.tsx index d639db92ab..11985e9b42 100644 --- a/src/renderer/components/layout/__test__/main-layout-header.test.tsx +++ b/src/renderer/components/layout/__test__/main-layout-header.test.tsx @@ -16,7 +16,7 @@ const cluster: Cluster = new Cluster({ describe("", () => { beforeEach(() => { - ClusterStore.getInstanceOrCreate(); + ClusterStore.createInstance(); }); afterEach(() => { diff --git a/src/renderer/ipc/invalid-kubeconfig-handler.tsx b/src/renderer/ipc/invalid-kubeconfig-handler.tsx index 82dc866a4e..0f1e12d070 100644 --- a/src/renderer/ipc/invalid-kubeconfig-handler.tsx +++ b/src/renderer/ipc/invalid-kubeconfig-handler.tsx @@ -4,6 +4,7 @@ import { ClusterStore } from "../../common/cluster-store"; import { InvalidKubeConfigArgs, InvalidKubeconfigChannel } from "../../common/ipc/invalid-kubeconfig"; import { Notifications, notificationsStore } from "../components/notifications"; import { Button } from "../components/button"; +import { productName } from "../../common/vars"; export const invalidKubeconfigHandler = { source: ipcRenderer, @@ -24,7 +25,7 @@ function InvalidKubeconfigListener(event: IpcRendererEvent, ...[clusterId]: Inva
Cluster with Invalid Kubeconfig Detected!

Cluster {cluster.name} has invalid kubeconfig {contextName} and cannot be displayed. - Please fix the { e.preventDefault(); shell.showItemInFolder(cluster.kubeConfigPath); }}>kubeconfig manually and restart Lens + Please fix the { e.preventDefault(); shell.showItemInFolder(cluster.kubeConfigPath); }}>kubeconfig manually and restart {productName} or remove the cluster.

Do you want to remove the cluster now?

diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index 85f31e6eab..adbc32c9e8 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -25,7 +25,7 @@ export class LensApp extends React.Component { static async init() { catalogEntityRegistry.init(); ExtensionLoader.getInstance().loadOnClusterManagerRenderer(); - LensProtocolRouterRenderer.getInstanceOrCreate().init(); + LensProtocolRouterRenderer.createInstance().init(); bindProtocolAddRouteHandlers(); window.addEventListener("offline", () => broadcastMessage("network:offline")); diff --git a/src/renderer/template.html b/src/renderer/template.html index 82e137d046..71440e5645 100755 --- a/src/renderer/template.html +++ b/src/renderer/template.html @@ -2,11 +2,11 @@ - Lens - The Kubernetes IDE + OpenLens - Open Source Kubernetes IDE
- \ No newline at end of file + diff --git a/src/renderer/utils/__tests__/storageHelper.test.ts b/src/renderer/utils/__tests__/storageHelper.test.ts index b5e8748d44..e0854be11c 100644 --- a/src/renderer/utils/__tests__/storageHelper.test.ts +++ b/src/renderer/utils/__tests__/storageHelper.test.ts @@ -5,7 +5,7 @@ import { ClusterStore } from "../../../common/cluster-store"; describe("renderer/utils/StorageHelper", () => { beforeEach(() => { - ClusterStore.getInstanceOrCreate(); + ClusterStore.createInstance(); }); afterEach(() => {