diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index 31827dd761..687ab1d292 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -5,69 +5,21 @@ import { anyObject } from "jest-mock-extended"; import mockFs from "mock-fs"; -import logger from "../../main/logger"; import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../hotbar-store.injectable"; -import { HotbarStore } from "../hotbar-store"; +import hotbarStoreInjectable from "../hotbars/store.injectable"; +import type { HotbarStore } from "../hotbars/store"; +import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable"; +import { computed } from "mobx"; +import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable"; import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import loggerInjectable from "../logger.injectable"; +import type { Logger } from "../logger"; -jest.mock("../../main/catalog/catalog-entity-registry", () => ({ - catalogEntityRegistry: { - items: [ - getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - - status: { - phase: "Running", - }, - - metadata: { - uid: "1dfa26e2ebab15780a3547e9c7fa785c", - name: "mycluster", - source: "local", - labels: {}, - }, - }), - - getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - - status: { - phase: "Running", - }, - - metadata: { - uid: "55b42c3c7ba3b04193416cda405269a5", - name: "my_shiny_cluster", - source: "remote", - labels: {}, - }, - }), - - getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - - status: { - phase: "Running", - }, - - metadata: { - uid: "catalog-entity", - name: "Catalog", - source: "app", - labels: {}, - }, - }), - ], - }, -})); +console.log("I am here as reminder against mockfs (and to fix console logging)"); function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { return { @@ -84,69 +36,81 @@ function getMockCatalogEntity(data: Partial & CatalogEntityKi } as CatalogEntity; } -const testCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "test", - name: "test", - labels: {}, - }, -}); - -const minikubeCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "minikube", - name: "minikube", - labels: {}, - }, -}); - -const awsCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "aws", - name: "aws", - labels: {}, - }, -}); - describe("HotbarStore", () => { let di: DiContainer; let hotbarStore: HotbarStore; + let testCluster: CatalogEntity; + let minikubeCluster: CatalogEntity; + let awsCluster: CatalogEntity; + let logger: jest.Mocked; beforeEach(async () => { - di = getDiForUnitTesting({ doGeneralOverrides: true }); + di = getDiForUnitTesting({ doGeneralOverrides: true, overrideHotbarStore: false }); + + testCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-test-id", + name: "my-test-cluster", + source: "local", + labels: {}, + }, + }); + minikubeCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-minikube-id", + name: "my-minikube-cluster", + source: "local", + labels: {}, + }, + }); + awsCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-aws-id", + name: "my-aws-cluster", + source: "local", + labels: {}, + }, + }); + + di.override(hasCategoryForEntityInjectable, () => () => true); + + logger = di.inject(loggerInjectable) as jest.Mocked; + + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + + catalogEntityRegistry.addComputedSource("some-id", computed(() => [ + testCluster, + minikubeCluster, + awsCluster, + catalogCatalogEntity, + ])); di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); - - di.override(hotbarStoreInjectable, () => { - HotbarStore.resetInstance(); - - return HotbarStore.createInstance({ - catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), - }); - }); + di.permitSideEffects(hotbarStoreInjectable); }); afterEach(() => { mockFs.restore(); }); - describe("given no migrations", () => { + describe("given no previous data in store, running all migrations", () => { beforeEach(async () => { mockFs(); @@ -186,7 +150,7 @@ describe("HotbarStore", () => { it("removes items", () => { hotbarStore.addToHotbar(testCluster); - hotbarStore.removeFromHotbar("test"); + hotbarStore.removeFromHotbar("some-test-id"); hotbarStore.removeFromHotbar("catalog-entity"); const items = hotbarStore.getActive().items.filter(Boolean); @@ -211,7 +175,7 @@ describe("HotbarStore", () => { hotbarStore.restackItems(1, 5); expect(hotbarStore.getActive().items[5]).toBeTruthy(); - expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("test"); + expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id"); }); it("moves items down", () => { @@ -224,7 +188,7 @@ describe("HotbarStore", () => { const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]); + expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]); }); it("moves items up", () => { @@ -237,28 +201,21 @@ describe("HotbarStore", () => { const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]); + expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]); }); it("logs an error if cellIndex is out of bounds", () => { hotbarStore.add({ name: "hottest", id: "hottest" }); hotbarStore.setActiveHotbar("hottest"); - const { error } = logger; - const mocked = jest.fn(); - - logger.error = mocked; - hotbarStore.addToHotbar(testCluster, -1); - expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); + expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); hotbarStore.addToHotbar(testCluster, 12); - expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); + expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); hotbarStore.addToHotbar(testCluster, 13); - expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); - - logger.error = error; + expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); }); it("throws an error if getId is invalid or returns not a string", () => { @@ -275,7 +232,7 @@ describe("HotbarStore", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.restackItems(1, 1); - expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("test"); + expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id"); }); it("new items takes first empty cell", () => { @@ -284,7 +241,7 @@ describe("HotbarStore", () => { hotbarStore.restackItems(0, 3); hotbarStore.addToHotbar(minikubeCluster); - expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("minikube"); + expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id"); }); it("throws if invalid arguments provided", () => { @@ -315,7 +272,7 @@ describe("HotbarStore", () => { }); }); - describe("given pre beta-5 configurations", () => { + describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => { beforeEach(async () => { const configurationToBeMigrated = { "some-electron-app-path-for-user-data": { @@ -332,7 +289,7 @@ describe("HotbarStore", () => { items: [ { entity: { - uid: "1dfa26e2ebab15780a3547e9c7fa785c", + uid: "some-aws-id", }, }, { @@ -381,13 +338,15 @@ describe("HotbarStore", () => { mockFs(configurationToBeMigrated); + di.override(appVersionInjectable, () => "5.0.0-beta.10"); + await di.runSetups(); hotbarStore = di.inject(hotbarStoreInjectable); }); it("allows to retrieve a hotbar", () => { - const hotbar = hotbarStore.getById("3caac17f-aec2-4723-9694-ad204465d935"); + const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935"); expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935"); }); @@ -403,17 +362,9 @@ describe("HotbarStore", () => { expect(items[0]).toEqual({ entity: { - name: "mycluster", + name: "my-aws-cluster", source: "local", - uid: "1dfa26e2ebab15780a3547e9c7fa785c", - }, - }); - - expect(items[1]).toEqual({ - entity: { - name: "my_shiny_cluster", - source: "remote", - uid: "55b42c3c7ba3b04193416cda405269a5", + uid: "some-aws-id", }, }); }); diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 4671e377e8..9f267285d2 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -10,7 +10,7 @@ import { ipcMain, ipcRenderer } from "electron"; import type { IEqualsComparer } from "mobx"; import { makeObservable, reaction, runInAction } from "mobx"; import type { Disposer } from "./utils"; -import { getAppVersion, Singleton, toJS } from "./utils"; +import { Singleton, toJS } from "./utils"; import logger from "../main/logger"; import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc"; import isEqual from "lodash/isEqual"; @@ -59,8 +59,6 @@ export abstract class BaseStore extends Singleton { this.storeConfig = getConfigurationFileModel({ ...this.params, - projectName: "lens", - projectVersion: getAppVersion(), cwd: this.cwd(), }); diff --git a/src/common/catalog-entities/general.ts b/src/common/catalog-entities/general.ts index 975b24e10f..259491a04f 100644 --- a/src/common/catalog-entities/general.ts +++ b/src/common/catalog-entities/general.ts @@ -6,7 +6,6 @@ import { navigate } from "../../renderer/navigation"; import type { CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog"; import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog"; -import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; interface GeneralEntitySpec extends CatalogEntitySpec { path: string; @@ -54,5 +53,3 @@ export class GeneralCategory extends CatalogCategory { }, }; } - -catalogCategoryRegistry.add(new GeneralCategory()); diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 83bc9f3e1e..a28fb6bbd2 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -158,5 +158,3 @@ class KubernetesClusterCategory extends CatalogCategory { } export const kubernetesClusterCategory = new KubernetesClusterCategory(); - -catalogCategoryRegistry.add(kubernetesClusterCategory); diff --git a/src/common/catalog-entities/web-link.ts b/src/common/catalog-entities/web-link.ts index 8429df7ab1..8bb8773306 100644 --- a/src/common/catalog-entities/web-link.ts +++ b/src/common/catalog-entities/web-link.ts @@ -69,5 +69,3 @@ export class WebLinkCategory extends CatalogCategory { }, }; } - -catalogCategoryRegistry.add(new WebLinkCategory()); diff --git a/src/common/catalog/catalog-category-registry.ts b/src/common/catalog/catalog-category-registry.ts index 7eb3e0fac8..d456f38f06 100644 --- a/src/common/catalog/catalog-category-registry.ts +++ b/src/common/catalog/catalog-category-registry.ts @@ -3,96 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { action, computed, observable, makeObservable } from "mobx"; -import { once } from "lodash"; -import { iter, getOrInsertMap, strictSet } from "../utils"; -import type { Disposer } from "../utils"; -import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; +import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; +import catalogCategoryRegistryInjectable from "./category-registry.injectable"; -export type CategoryFilter = (category: CatalogCategory) => any; - -export class CatalogCategoryRegistry { - protected categories = observable.set(); - protected groupKinds = new Map>(); - protected filters = observable.set([], { - deep: false, - }); - - constructor() { - makeObservable(this); - } - - @action add(category: CatalogCategory): Disposer { - const byGroup = getOrInsertMap(this.groupKinds, category.spec.group); - - this.categories.add(category); - strictSet(byGroup, category.spec.names.kind, category); - - return () => { - this.categories.delete(category); - byGroup.delete(category.spec.names.kind); - }; - } - - @computed get items() { - return Array.from(this.categories); - } - - @computed get filteredItems() { - return Array.from( - iter.reduce( - this.filters, - iter.filter, - this.items.values(), - ), - ); - } - - - getForGroupKind(group: string, kind: string): T | undefined { - return this.groupKinds.get(group)?.get(kind) as T; - } - - getEntityForData(data: CatalogEntityData & CatalogEntityKindData) { - const category = this.getCategoryForEntity(data); - - if (!category) { - return null; - } - - const splitApiVersion = data.apiVersion.split("/"); - const version = splitApiVersion[1]; - - const specVersion = category.spec.versions.find((v) => v.name === version); - - if (!specVersion) { - return null; - } - - return new specVersion.entityClass(data); - } - - getCategoryForEntity(data: CatalogEntityData & CatalogEntityKindData): T | undefined { - const splitApiVersion = data.apiVersion.split("/"); - const group = splitApiVersion[0]; - - return this.getForGroupKind(group, data.kind); - } - - getByName(name: string) { - return this.items.find(category => category.metadata?.name == name); - } - - /** - * Add a new filter to the set of category filters - * @param fn The function that should return a truthy value if that category should be displayed - * @returns A function to remove that filter - */ - addCatalogCategoryFilter(fn: CategoryFilter): Disposer { - this.filters.add(fn); - - return once(() => void this.filters.delete(fn)); - } -} - -export const catalogCategoryRegistry = new CatalogCategoryRegistry(); +/** + * @deprecated use `di.inject(catalogCategoryRegistryInjectable)` instead + */ +export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable); diff --git a/src/common/catalog/category-registry.injectable.ts b/src/common/catalog/category-registry.injectable.ts new file mode 100644 index 0000000000..0b9aa321cd --- /dev/null +++ b/src/common/catalog/category-registry.injectable.ts @@ -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 { GeneralCategory, kubernetesClusterCategory, WebLinkCategory } from "../catalog-entities"; +import { CatalogCategoryRegistry } from "./category-registry"; + +const catalogCategoryRegistryInjectable = getInjectable({ + id: "catalog-category-registry", + instantiate: () => { + const registry = new CatalogCategoryRegistry(); + + // TODO: move to different place + registry.add(new GeneralCategory()); + registry.add(kubernetesClusterCategory); + registry.add(new WebLinkCategory()); + + return registry; + }, +}); + +export default catalogCategoryRegistryInjectable; diff --git a/src/common/catalog/category-registry.ts b/src/common/catalog/category-registry.ts new file mode 100644 index 0000000000..75c33d8a10 --- /dev/null +++ b/src/common/catalog/category-registry.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { action, computed, observable, makeObservable } from "mobx"; +import { once } from "lodash"; +import { iter, getOrInsertMap, strictSet } from "../utils"; +import type { Disposer } from "../utils"; +import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; + +export type CategoryFilter = (category: CatalogCategory) => any; + +export class CatalogCategoryRegistry { + protected readonly categories = observable.set(); + protected readonly groupKinds = new Map>(); + protected readonly filters = observable.set([], { + deep: false, + }); + + constructor() { + makeObservable(this); + } + + @action add(category: CatalogCategory): Disposer { + const byGroup = getOrInsertMap(this.groupKinds, category.spec.group); + + this.categories.add(category); + strictSet(byGroup, category.spec.names.kind, category); + + return () => { + this.categories.delete(category); + byGroup.delete(category.spec.names.kind); + }; + } + + @computed get items() { + return Array.from(this.categories); + } + + @computed get filteredItems() { + return Array.from( + iter.reduce( + this.filters, + iter.filter, + this.items.values(), + ), + ); + } + + + getForGroupKind(group: string, kind: string): T | undefined { + return this.groupKinds.get(group)?.get(kind) as T; + } + + getEntityForData(data: CatalogEntityData & CatalogEntityKindData) { + const category = this.getCategoryForEntity(data); + + if (!category) { + return null; + } + + const splitApiVersion = data.apiVersion.split("/"); + const version = splitApiVersion[1]; + + const specVersion = category.spec.versions.find((v) => v.name === version); + + if (!specVersion) { + return null; + } + + return new specVersion.entityClass(data); + } + + hasCategoryForEntity({ kind, apiVersion }: CatalogEntityData & CatalogEntityKindData): boolean { + const splitApiVersion = apiVersion.split("/"); + const group = splitApiVersion[0]; + + return this.groupKinds.get(group)?.has(kind) ?? false; + } + + getCategoryForEntity(data: CatalogEntityData & CatalogEntityKindData): T | undefined { + const splitApiVersion = data.apiVersion.split("/"); + const group = splitApiVersion[0]; + + return this.getForGroupKind(group, data.kind); + } + + getByName(name: string) { + return this.items.find(category => category.metadata?.name == name); + } + + /** + * Add a new filter to the set of category filters + * @param fn The function that should return a truthy value if that category should be displayed + * @returns A function to remove that filter + */ + addCatalogCategoryFilter(fn: CategoryFilter): Disposer { + this.filters.add(fn); + + return once(() => void this.filters.delete(fn)); + } +} diff --git a/src/common/catalog/has-category-for-entity.injectable.ts b/src/common/catalog/has-category-for-entity.injectable.ts new file mode 100644 index 0000000000..97084ec96d --- /dev/null +++ b/src/common/catalog/has-category-for-entity.injectable.ts @@ -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 { CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; +import catalogCategoryRegistryInjectable from "./category-registry.injectable"; + +export type HasCategoryForEntity = (data: CatalogEntityData & CatalogEntityKindData) => boolean; + +const hasCategoryForEntityInjectable = getInjectable({ + id: "has-category-for-entity", + instantiate: (di): HasCategoryForEntity => { + const registry = di.inject(catalogCategoryRegistryInjectable); + + return (data) => registry.hasCategoryForEntity(data); + }, +}); + +export default hasCategoryForEntityInjectable; diff --git a/src/common/catalog/index.ts b/src/common/catalog/index.ts index 2e897fff01..a5c5ec4276 100644 --- a/src/common/catalog/index.ts +++ b/src/common/catalog/index.ts @@ -4,4 +4,5 @@ */ export * from "./catalog-category-registry"; +export * from "./category-registry"; export * from "./catalog-entity"; diff --git a/src/common/hotbars/add-hotbar.injectable.ts b/src/common/hotbars/add-hotbar.injectable.ts new file mode 100644 index 0000000000..25ee0f588a --- /dev/null +++ b/src/common/hotbars/add-hotbar.injectable.ts @@ -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 hotbarStoreInjectable from "./store.injectable"; +import type { CreateHotbarData, CreateHotbarOptions } from "./types"; + +export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void; + +const addHotbarInjectable = getInjectable({ + id: "add-hotbar", + instantiate: (di): AddHotbar => { + const store = di.inject(hotbarStoreInjectable); + + return (data, opts) => store.add(data, opts); + }, +}); + +export default addHotbarInjectable; diff --git a/src/common/hotbar-store.injectable.ts b/src/common/hotbars/store.injectable.ts similarity index 65% rename from src/common/hotbar-store.injectable.ts rename to src/common/hotbars/store.injectable.ts index e8a883cf0a..ace13b8be4 100644 --- a/src/common/hotbar-store.injectable.ts +++ b/src/common/hotbars/store.injectable.ts @@ -3,8 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import { HotbarStore } from "./hotbar-store"; +import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import { HotbarStore } from "./store"; +import loggerInjectable from "../logger.injectable"; const hotbarStoreInjectable = getInjectable({ id: "hotbar-store", @@ -14,6 +15,7 @@ const hotbarStoreInjectable = getInjectable({ return HotbarStore.createInstance({ catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), + logger: di.inject(loggerInjectable), }); }, diff --git a/src/common/hotbar-store.ts b/src/common/hotbars/store.ts similarity index 80% rename from src/common/hotbar-store.ts rename to src/common/hotbars/store.ts index 3c6a0bf810..7cf7633f02 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbars/store.ts @@ -3,23 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { action, comparer, observable, makeObservable, computed, runInAction } from "mobx"; -import { BaseStore } from "./base-store"; -import migrations from "../migrations/hotbar-store"; -import { toJS } from "./utils"; -import type { CatalogEntity } from "./catalog"; -import logger from "../main/logger"; -import { broadcastMessage } from "./ipc"; -import type { - Hotbar, - CreateHotbarData, - CreateHotbarOptions } from "./hotbar-types"; -import { - defaultHotbarCells, - getEmptyHotbar, -} from "./hotbar-types"; -import { hotbarTooManyItemsChannel } from "./ipc/hotbar"; -import type { GeneralEntity } from "./catalog-entities"; +import { action, comparer, observable, makeObservable, computed } from "mobx"; +import { BaseStore } from "../base-store"; +import migrations from "../../migrations/hotbar-store"; +import { toJS } from "../utils"; +import type { CatalogEntity } from "../catalog"; +import { broadcastMessage } from "../ipc"; +import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types"; +import { defaultHotbarCells, getEmptyHotbar } from "./types"; +import { hotbarTooManyItemsChannel } from "../ipc/hotbar"; +import type { GeneralEntity } from "../catalog-entities"; +import type { Logger } from "../logger"; +import assert from "assert"; export interface HotbarStoreModel { hotbars: Hotbar[]; @@ -27,7 +22,8 @@ export interface HotbarStoreModel { } interface Dependencies { - catalogCatalogEntity: GeneralEntity; + readonly catalogCatalogEntity: GeneralEntity; + readonly logger: Logger; } export class HotbarStore extends BaseStore { @@ -35,7 +31,7 @@ export class HotbarStore extends BaseStore { @observable hotbars: Hotbar[] = []; @observable private _activeHotbarId!: string; - constructor(private dependencies: Dependencies) { + constructor(private readonly dependencies: Dependencies) { super({ configName: "lens-hotbar-store", accessPropertiesByDotNotation: false, // To make dots safe in cluster context names @@ -62,7 +58,7 @@ export class HotbarStore extends BaseStore { this._activeHotbarId = this.hotbars[hotbar].id; } } else if (typeof hotbar === "string") { - if (this.getById(hotbar)) { + if (this.findById(hotbar)) { this._activeHotbarId = hotbar; } } else { @@ -121,47 +117,34 @@ export class HotbarStore extends BaseStore { } getActive(): Hotbar { - const hotbar = this.getById(this.activeHotbarId); + const hotbar = this.findById(this.activeHotbarId); - if (hotbar) { - return hotbar; - } + assert(hotbar, "There MUST always be an active hotbar"); - runInAction(() => { - if (this.hotbars.length === 0) { - this.hotbars.push(getEmptyHotbar("Default")); - } - - this._activeHotbarId = this.hotbars[0].id; - }); - - return this.hotbars[0]; + return hotbar; } - getByName(name: string) { + findByName(name: string) { return this.hotbars.find((hotbar) => hotbar.name === name); } - getById(id: string) { + findById(id: string) { return this.hotbars.find((hotbar) => hotbar.id === id); } - add = action( - ( - data: CreateHotbarData, - { setActive = false }: CreateHotbarOptions = {}, - ) => { - const hotbar = getEmptyHotbar(data.name, data.id); + @action + add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) { + const hotbar = getEmptyHotbar(data.name, data.id); - this.hotbars.push(hotbar); + this.hotbars.push(hotbar); - if (setActive) { - this._activeHotbarId = hotbar.id; - } - }, - ); + if (setActive) { + this._activeHotbarId = hotbar.id; + } + } - setHotbarName = action((id: string, name: string): void => { + @action + setHotbarName(id: string, name: string): void { const index = this.hotbars.findIndex((hotbar) => hotbar.id === id); if (index < 0) { @@ -172,19 +155,18 @@ export class HotbarStore extends BaseStore { } this.hotbars[index].name = name; - }); + } - remove = action((hotbar: Hotbar) => { - if (this.hotbars.length <= 1) { - throw new Error("Cannot remove the last hotbar"); - } + @action + remove(hotbar: Hotbar) { + assert(this.hotbars.length >= 2, "Cannot remove the last hotbar"); this.hotbars = this.hotbars.filter((h) => h !== hotbar); if (this.activeHotbarId === hotbar.id) { this.setActiveHotbar(0); } - }); + } @action addToHotbar(item: CatalogEntity, cellIndex?: number) { @@ -223,7 +205,7 @@ export class HotbarStore extends BaseStore { } else if (0 <= cellIndex && cellIndex < hotbar.items.length) { hotbar.items[cellIndex] = newItem; } else { - logger.error( + this.dependencies.logger.error( `[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`, { entityId: uid, hotbarId: hotbar.id, cellIndex }, ); @@ -260,8 +242,9 @@ export class HotbarStore extends BaseStore { findClosestEmptyIndex(from: number, direction = 1) { let index = from; + const hotbar = this.getActive(); - while (this.getActive().items[index] != null) { + while (hotbar.items[index] != null) { index += direction; } @@ -328,11 +311,9 @@ export class HotbarStore extends BaseStore { return false; } - return ( - this.getActive().items.findIndex( - (item) => item?.entity.uid === entity.getId(), - ) >= 0 - ); + const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId()); + + return indexInActiveHotbar >= 0; } getDisplayLabel(hotbar: Hotbar): string { diff --git a/src/common/hotbar-types.ts b/src/common/hotbars/types.ts similarity index 91% rename from src/common/hotbar-types.ts rename to src/common/hotbars/types.ts index 13f6e8360d..6370fe136d 100644 --- a/src/common/hotbar-types.ts +++ b/src/common/hotbars/types.ts @@ -4,8 +4,8 @@ */ import * as uuid from "uuid"; -import type { Tuple } from "./utils"; -import { tuple } from "./utils"; +import type { Tuple } from "../utils"; +import { tuple } from "../utils"; export interface HotbarItem { entity: { diff --git a/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts b/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts index f9b8f15365..aeb31d57cd 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts @@ -4,6 +4,7 @@ */ import type { DiContainer } from "@ogre-tools/injectable"; import assert from "assert"; +import { iter } from "../../common/utils"; const legacyGlobalDis = new Map(); @@ -20,13 +21,17 @@ export const setLegacyGlobalDiForExtensionApi = ( }; export const getLegacyGlobalDiForExtensionApi = () => { - const globalDis = [...legacyGlobalDis.values()]; - - if (globalDis.length > 1) { + if (legacyGlobalDis.size > 1) { throw new Error("Tried to get DI container using legacy globals where there is multiple containers available."); } - return globalDis[0]; + const di = iter.first(legacyGlobalDis.values()); + + if (!di) { + throw new Error("Tried to get DI container using legacy globals where there is no containers available."); + } + + return di; }; export function getEnvironmentSpecificLegacyGlobalDiForExtensionApi(environment: Environments) { diff --git a/src/main/__test__/shell-session.test.ts b/src/main/__test__/shell-session.test.ts index a9cb9b93fa..1fe8915e26 100644 --- a/src/main/__test__/shell-session.test.ts +++ b/src/main/__test__/shell-session.test.ts @@ -3,10 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -/** - * @jest-environment jsdom - */ - import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars"; describe("clearKubeconfigEnvVars tests", () => { diff --git a/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts b/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts index 41b6324915..a94c4b0d93 100644 --- a/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts +++ b/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts @@ -227,7 +227,6 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Dependen } } } catch (error) { - console.log(error); logger.warn(`${logPrefix} Failed to compute diff: ${error}`, { filePath }); source.clear(); // clear source if we have failed so as to not show outdated information } diff --git a/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts b/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts index 167e74c719..aaadef36cf 100644 --- a/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts +++ b/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable"; +import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable"; import { generalCatalogEntityInjectionToken } from "../../common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token"; import { computed } from "mobx"; diff --git a/src/main/catalog/catalog-entity-registry.ts b/src/main/catalog/catalog-entity-registry.ts index 084b4fdb84..0329a56e74 100644 --- a/src/main/catalog/catalog-entity-registry.ts +++ b/src/main/catalog/catalog-entity-registry.ts @@ -3,50 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { action, computed, type IComputedValue, type IObservableArray, makeObservable, observable } from "mobx"; -import type { CatalogCategoryRegistry, CatalogEntity } from "../../common/catalog"; -import { catalogCategoryRegistry } from "../../common/catalog"; -import { iter } from "../../common/utils"; +import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; +import catalogEntityRegistryInjectable from "./entity-registry.injectable"; -export class CatalogEntityRegistry { - protected sources = observable.map>(); - - constructor(private categoryRegistry: CatalogCategoryRegistry) { - makeObservable(this); - } - - @action addObservableSource(id: string, source: IObservableArray) { - this.sources.set(id, computed(() => source)); - } - - @action addComputedSource(id: string, source: IComputedValue) { - this.sources.set(id, source); - } - - @action removeSource(id: string) { - this.sources.delete(id); - } - - @computed get items(): CatalogEntity[] { - return Array.from( - iter.filter( - iter.flatMap(this.sources.values(), source => source.get()), - entity => this.categoryRegistry.getCategoryForEntity(entity), - ), - ); - } - - findById(id: string): CatalogEntity | undefined { - return this.items.find(entity => entity.getId() === id); - } - - filterItemsForApiKind(apiVersion: string, kind: string): CatalogEntity[] { - return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind); - } - - filterItemsByPredicate(filter: (item: CatalogEntity) => item is E): E[] { - return this.items.filter(filter); - } -} - -export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); +/** + * @deprecated use `di.inject(catalogEntityRegistryInjectable)` instead + */ +export const catalogEntityRegistry = asLegacyGlobalForExtensionApi(catalogEntityRegistryInjectable); diff --git a/src/main/catalog/catalog-entity-registry.injectable.ts b/src/main/catalog/entity-registry.injectable.ts similarity index 54% rename from src/main/catalog/catalog-entity-registry.injectable.ts rename to src/main/catalog/entity-registry.injectable.ts index 37584bab78..275d692046 100644 --- a/src/main/catalog/catalog-entity-registry.injectable.ts +++ b/src/main/catalog/entity-registry.injectable.ts @@ -3,12 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { catalogEntityRegistry } from "./catalog-entity-registry"; +import hasCategoryForEntityInjectable from "../../common/catalog/has-category-for-entity.injectable"; +import { CatalogEntityRegistry } from "./entity-registry"; const catalogEntityRegistryInjectable = getInjectable({ id: "catalog-entity-registry", - instantiate: () => catalogEntityRegistry, - causesSideEffects: true, + instantiate: (di) => new CatalogEntityRegistry({ + hasCategoryForEntity: di.inject(hasCategoryForEntityInjectable), + }), }); export default catalogEntityRegistryInjectable; diff --git a/src/main/catalog/entity-registry.ts b/src/main/catalog/entity-registry.ts new file mode 100644 index 0000000000..972da73132 --- /dev/null +++ b/src/main/catalog/entity-registry.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { action, computed, type IComputedValue, type IObservableArray, makeObservable, observable } from "mobx"; +import type { CatalogEntity } from "../../common/catalog"; +import type { HasCategoryForEntity } from "../../common/catalog/has-category-for-entity.injectable"; +import { iter } from "../../common/utils"; + +interface Dependencies { + readonly hasCategoryForEntity: HasCategoryForEntity; +} + +export class CatalogEntityRegistry { + protected sources = observable.map>(); + + constructor(protected readonly dependencies: Dependencies) { + makeObservable(this); + } + + @action addObservableSource(id: string, source: IObservableArray) { + this.sources.set(id, computed(() => source)); + } + + @action addComputedSource(id: string, source: IComputedValue) { + this.sources.set(id, source); + } + + @action removeSource(id: string) { + this.sources.delete(id); + } + + @computed get items(): CatalogEntity[] { + return Array.from( + iter.filter( + iter.flatMap(this.sources.values(), source => source.get()), + entity => this.dependencies.hasCategoryForEntity(entity), + ), + ); + } + + findById(id: string): CatalogEntity | undefined { + return this.items.find(entity => entity.getId() === id); + } + + filterItemsForApiKind(apiVersion: string, kind: string): CatalogEntity[] { + return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind); + } + + filterItemsByPredicate(filter: (item: CatalogEntity) => item is E): E[] { + return this.items.filter(filter); + } +} diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 5473407285..d949d8f970 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -4,7 +4,7 @@ */ import glob from "glob"; -import { kebabCase, memoize, noop } from "lodash/fp"; +import { kebabCase, memoize } from "lodash/fp"; import { createContainer } from "@ogre-tools/injectable"; import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; @@ -26,7 +26,6 @@ import clusterStoreInjectable from "../common/cluster-store/cluster-store.inject import type { ClusterStore } from "../common/cluster-store/cluster-store"; import type { Cluster } from "../common/cluster/cluster"; import userStoreInjectable from "../common/user-store/user-store.injectable"; -import type { UserStore } from "../common/user-store"; import isMacInjectable from "../common/vars/is-mac.injectable"; import isWindowsInjectable from "../common/vars/is-windows.injectable"; import isLinuxInjectable from "../common/vars/is-linux.injectable"; @@ -34,11 +33,29 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake"; import joinPathsInjectable from "../common/path/join-paths.injectable"; import { joinPathsFake } from "../common/test-utils/join-paths-fake"; -import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../common/hotbars/store.injectable"; + +export interface GetDiOptions { + doGeneralOverrides?: boolean; + overrideHotbarStore?: boolean; + overrideUserStore?: boolean; + overrideExtensionsStore?: boolean; + overrideClusterStore?: boolean; + overrideFileSystemProvisionerStore?: boolean; +} + +export function getDiForUnitTesting(opts: GetDiOptions = {}) { + const { + doGeneralOverrides = false, + } = opts; + const { + overrideHotbarStore = doGeneralOverrides, + overrideUserStore = doGeneralOverrides, + overrideExtensionsStore = doGeneralOverrides, + overrideClusterStore = doGeneralOverrides, + overrideFileSystemProvisionerStore = doGeneralOverrides, + } = opts; -export const getDiForUnitTesting = ( - { doGeneralOverrides } = { doGeneralOverrides: false }, -) => { const di = createContainer(); setLegacyGlobalDiForExtensionApi(di, Environments.main); @@ -55,6 +72,26 @@ export const getDiForUnitTesting = ( di.preventSideEffects(); + if (overrideHotbarStore) { + di.override(hotbarStoreInjectable, () => ({})); + } + + if (overrideUserStore) { + di.override(userStoreInjectable, () => ({})); + } + + if (overrideExtensionsStore) { + di.override(extensionsStoreInjectable, () => ({ isEnabled: (opts) => (void opts, false) }) as ExtensionsStore); + } + + if (overrideClusterStore) { + di.override(clusterStoreInjectable, () => ({ getById: (id) => (void id, {}) as Cluster }) as ClusterStore); + } + + if (overrideFileSystemProvisionerStore) { + di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore); + } + if (doGeneralOverrides) { di.override(isMacInjectable, () => true); di.override(isWindowsInjectable, () => false); @@ -63,17 +100,6 @@ export const getDiForUnitTesting = ( di.override(getAbsolutePathInjectable, () => getAbsolutePathFake); di.override(joinPathsInjectable, () => joinPathsFake); - // eslint-disable-next-line unused-imports/no-unused-vars-ts - di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore); - - di.override(hotbarStoreInjectable, () => ({})); - - di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore); - - // eslint-disable-next-line unused-imports/no-unused-vars-ts - di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore); - di.override(userStoreInjectable, () => ({}) as UserStore); - di.override( getElectronAppPathInjectable, () => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`, @@ -104,16 +130,16 @@ export const getDiForUnitTesting = ( }); di.override(loggerInjectable, () => ({ - warn: noop, - debug: noop, - error: (message: string, ...args: any) => console.error(message, ...args), - info: noop, - silly: noop, + warn: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + silly: jest.fn(), })); } return di; -}; +} const getInjectableFilePaths = memoize(() => [ ...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }), diff --git a/src/main/index.ts b/src/main/index.ts index 550fa10c4f..b51232effb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -59,7 +59,7 @@ import assert from "assert"; import windowManagerInjectable from "./window-manager.injectable"; import navigateToPreferencesInjectable from "../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable"; import syncGeneralCatalogEntitiesInjectable from "./catalog-sources/sync-general-catalog-entities.injectable"; -import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../common/hotbars/store.injectable"; import applicationMenuItemsInjectable from "./menu/application-menu-items.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import { init } from "@sentry/electron/main"; 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 3515a927bd..c8f99dc4f6 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.0.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.0.ts @@ -5,7 +5,7 @@ // Cleans up a store that had the state related data stored import type { MigrationDeclaration } from "../helpers"; -import { getEmptyHotbar } from "../../common/hotbar-types"; +import { getEmptyHotbar } from "../../common/hotbars/types"; import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import catalogCatalogEntityInjectable from "../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; diff --git a/src/migrations/hotbar-store/5.0.0-alpha.2.ts b/src/migrations/hotbar-store/5.0.0-alpha.2.ts index 1ede94f87b..0885caa1c1 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.2.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.2.ts @@ -4,7 +4,7 @@ */ // Cleans up a store that had the state related data stored -import type { Hotbar } from "../../common/hotbar-types"; +import type { Hotbar } from "../../common/hotbars/types"; import * as uuid from "uuid"; import type { MigrationDeclaration } from "../helpers"; diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index b547a52832..95a4c616d4 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -8,8 +8,8 @@ import { isNull } from "lodash"; import path from "path"; import * as uuid from "uuid"; import type { ClusterStoreModel } from "../../common/cluster-store/cluster-store"; -import type { Hotbar, HotbarItem } from "../../common/hotbar-types"; -import { defaultHotbarCells, getEmptyHotbar } from "../../common/hotbar-types"; +import type { Hotbar, HotbarItem } from "../../common/hotbars/types"; +import { defaultHotbarCells, getEmptyHotbar } from "../../common/hotbars/types"; import type { MigrationDeclaration } from "../helpers"; import { migrationLog } from "../helpers"; import { generateNewIdFor } from "../utils"; diff --git a/src/migrations/hotbar-store/5.0.0-beta.5.ts b/src/migrations/hotbar-store/5.0.0-beta.5.ts index f65bc7825d..39f8c054db 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.5.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.5.ts @@ -3,8 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Hotbar } from "../../common/hotbar-types"; -import { catalogEntityRegistry } from "../../main/catalog"; +import type { Hotbar } from "../../common/hotbars/types"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable"; import type { MigrationDeclaration } from "../helpers"; export default { @@ -12,6 +13,8 @@ export default { run(store) { const rawHotbars = store.get("hotbars"); const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : []; + const di = getLegacyGlobalDiForExtensionApi(); + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); for (const hotbar of hotbars) { for (let i = 0; i < hotbar.items.length; i += 1) { diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index d416722526..053ff4a472 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -40,7 +40,7 @@ import historyInjectable from "./navigation/history.injectable"; import themeStoreInjectable from "./theme-store.injectable"; import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable"; import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable"; -import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../common/hotbars/store.injectable"; import { bindEvents } from "./navigation/events"; import assert from "assert"; import openDeleteClusterDialogInjectable from "./components/delete-cluster-dialog/open.injectable"; diff --git a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts index 34d7ddb8fa..a2bb9279e3 100644 --- a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts +++ b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts @@ -13,7 +13,7 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns"; import type { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable"; import getCategoryColumnsInjectable from "../get-category-columns.injectable"; -import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; class TestCategory extends CatalogCategory { apiVersion = "catalog.k8slens.dev/v1alpha1"; diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 53e08f9839..8b3b7e8a82 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -41,8 +41,8 @@ import catalogRouteParametersInjectable from "./catalog-route-parameters.injecta import { browseCatalogTab } from "./catalog-browse-tab"; import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; +import type { HotbarStore } from "../../../common/hotbars/store"; interface Dependencies { catalogPreviousActiveTabStorage: { set: (value: string ) => void; get: () => string }; diff --git a/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx b/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx index 8a277b962e..bae8895ff7 100644 --- a/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx +++ b/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx @@ -9,17 +9,17 @@ import { MenuItem } from "../menu"; import type { CatalogEntity } from "../../api/catalog-entity"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; +import type { HotbarStore } from "../../../common/hotbars/store"; interface Dependencies { hotbarStore: HotbarStore; } interface HotbarToggleMenuItemProps { - entity: CatalogEntity; - addContent: ReactNode; - removeContent: ReactNode; + entity: CatalogEntity; + addContent: ReactNode; + removeContent: ReactNode; } function NonInjectedHotbarToggleMenuItem({ diff --git a/src/renderer/components/+catalog/name-category-column.injectable.tsx b/src/renderer/components/+catalog/name-category-column.injectable.tsx index 173a8bc5fa..0ba9e146d1 100644 --- a/src/renderer/components/+catalog/name-category-column.injectable.tsx +++ b/src/renderer/components/+catalog/name-category-column.injectable.tsx @@ -10,8 +10,8 @@ import { Avatar } from "../avatar"; import { Icon } from "../icon"; import React from "react"; import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; +import type { HotbarStore } from "../../../common/hotbars/store"; const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => { const isItemInHotbar = hotbarStore.isAddedToActive(entity); diff --git a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx index b32032fea6..cb40583bec 100644 --- a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx +++ b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx @@ -16,7 +16,7 @@ import { createClusterInjectionToken } from "../../../../common/cluster/create-c import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; -import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import type { OpenDeleteClusterDialog } from "../open.injectable"; import openDeleteClusterDialogInjectable from "../open.injectable"; diff --git a/src/renderer/components/delete-cluster-dialog/view.tsx b/src/renderer/components/delete-cluster-dialog/view.tsx index 796e0ca836..7744a1c6c3 100644 --- a/src/renderer/components/delete-cluster-dialog/view.tsx +++ b/src/renderer/components/delete-cluster-dialog/view.tsx @@ -17,9 +17,9 @@ import { Icon } from "../icon"; import { Select } from "../select"; import { Checkbox } from "../checkbox"; import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import type { HotbarStore } from "../../../common/hotbars/store"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { DeleteClusterDialogState } from "./state.injectable"; import deleteClusterDialogStateInjectable from "./state.injectable"; diff --git a/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx b/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx index e88492623b..639ff6584e 100644 --- a/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx +++ b/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx @@ -11,7 +11,7 @@ import type { DiContainer } from "@ogre-tools/injectable"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; -import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import { ThemeStore } from "../../../theme.store"; import { ConfirmDialog } from "../../confirm-dialog"; import { UserStore } from "../../../../common/user-store"; @@ -19,7 +19,7 @@ import mockFs from "mock-fs"; import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable"; -import type { HotbarStore } from "../../../../common/hotbar-store"; +import type { HotbarStore } from "../../../../common/hotbars/store"; const mockHotbars: Partial> = { "1": { diff --git a/src/renderer/components/hotbar/hotbar-add-command.tsx b/src/renderer/components/hotbar/hotbar-add-command.tsx index 527e00dfff..3fd261b213 100644 --- a/src/renderer/components/hotbar/hotbar-add-command.tsx +++ b/src/renderer/components/hotbar/hotbar-add-command.tsx @@ -7,11 +7,11 @@ import React from "react"; import { observer } from "mobx-react"; import type { InputValidator } from "../input"; import { Input } from "../input"; -import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbar-types"; +import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbars/types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable"; +import addHotbarInjectable from "../../../common/hotbars/add-hotbar.injectable"; interface Dependencies { closeCommandOverlay: () => void; @@ -51,7 +51,7 @@ const NonInjectedHotbarAddCommand = observer(({ closeCommandOverlay, addHotbar, export const HotbarAddCommand = withInjectables(NonInjectedHotbarAddCommand, { getProps: (di, props) => ({ closeCommandOverlay: di.inject(commandOverlayInjectable).close, - addHotbar: di.inject(hotbarStoreInjectable).add, + addHotbar: di.inject(addHotbarInjectable), uniqueHotbarName: di.inject(uniqueHotbarNameInjectable), ...props, }), diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index 61e24e90aa..2fb7d74d1e 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -16,12 +16,12 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from "react-be import { HotbarSelector } from "./hotbar-selector"; import { HotbarCell } from "./hotbar-cell"; import { HotbarIcon } from "./hotbar-icon"; -import type { HotbarItem } from "../../../common/hotbar-types"; -import { defaultHotbarCells } from "../../../common/hotbar-types"; +import type { HotbarItem } from "../../../common/hotbars/types"; +import { defaultHotbarCells } from "../../../common/hotbars/types"; import { action, makeObservable, observable } from "mobx"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import type { HotbarStore } from "../../../common/hotbars/store"; export interface HotbarMenuProps { className?: IClassName; @@ -118,7 +118,7 @@ class NonInjectedHotbarMenu extends React.Component {(provided, snapshot) => { const style = { diff --git a/src/renderer/components/hotbar/hotbar-remove-command.tsx b/src/renderer/components/hotbar/hotbar-remove-command.tsx index 07e37a3e80..91079fdb23 100644 --- a/src/renderer/components/hotbar/hotbar-remove-command.tsx +++ b/src/renderer/components/hotbar/hotbar-remove-command.tsx @@ -6,19 +6,15 @@ import React from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import { ConfirmDialog } from "../confirm-dialog"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import type { Hotbar } from "../../../common/hotbar-types"; +import type { HotbarStore } from "../../../common/hotbars/store"; interface Dependencies { closeCommandOverlay: () => void; - hotbarStore: { - hotbars: Hotbar[]; - remove: (hotbar: Hotbar) => void; - getDisplayLabel: (hotbar: Hotbar) => string; - }; + hotbarStore: HotbarStore; } const NonInjectedHotbarRemoveCommand = observer(({ diff --git a/src/renderer/components/hotbar/hotbar-rename-command.tsx b/src/renderer/components/hotbar/hotbar-rename-command.tsx index 429e42598f..8ef812e1a3 100644 --- a/src/renderer/components/hotbar/hotbar-rename-command.tsx +++ b/src/renderer/components/hotbar/hotbar-rename-command.tsx @@ -6,22 +6,18 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { InputValidator } from "../input"; import { Input } from "../input"; -import type { Hotbar } from "../../../common/hotbar-types"; +import type { Hotbar } from "../../../common/hotbars/types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable"; +import type { HotbarStore } from "../../../common/hotbars/store"; interface Dependencies { closeCommandOverlay: () => void; - hotbarStore: { - hotbars: Hotbar[]; - getById: (id: string) => Hotbar | undefined; - setHotbarName: (id: string, name: string) => void; - getDisplayLabel: (hotbar: Hotbar) => string; - }; + hotbarStore: HotbarStore; uniqueHotbarName: InputValidator; } diff --git a/src/renderer/components/hotbar/hotbar-selector.tsx b/src/renderer/components/hotbar/hotbar-selector.tsx index 636e7dd033..d6a52dcae6 100644 --- a/src/renderer/components/hotbar/hotbar-selector.tsx +++ b/src/renderer/components/hotbar/hotbar-selector.tsx @@ -7,22 +7,18 @@ import styles from "./hotbar-selector.module.scss"; import React, { useRef, useState } from "react"; import { Icon } from "../icon"; import { Badge } from "../badge"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import { HotbarSwitchCommand } from "./hotbar-switch-command"; import { Tooltip, TooltipPosition } from "../tooltip"; import { observer } from "mobx-react"; -import type { Hotbar } from "../../../common/hotbar-types"; +import type { Hotbar } from "../../../common/hotbars/types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; import { cssNames } from "../../utils"; +import type { HotbarStore } from "../../../common/hotbars/store"; interface Dependencies { - hotbarStore: { - switchToPrevious: () => void; - switchToNext: () => void; - getActive: () => Hotbar; - getDisplayIndex: (hotbar: Hotbar) => string; - }; + hotbarStore: HotbarStore; openCommandOverlay: (component: React.ReactElement) => void; } diff --git a/src/renderer/components/hotbar/hotbar-switch-command.tsx b/src/renderer/components/hotbar/hotbar-switch-command.tsx index 0740c869cf..5e58461268 100644 --- a/src/renderer/components/hotbar/hotbar-switch-command.tsx +++ b/src/renderer/components/hotbar/hotbar-switch-command.tsx @@ -6,15 +6,15 @@ import React from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { CommandOverlay } from "../command-palette"; import { HotbarAddCommand } from "./hotbar-add-command"; import { HotbarRemoveCommand } from "./hotbar-remove-command"; import { HotbarRenameCommand } from "./hotbar-rename-command"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import type { HotbarStore } from "../../../common/hotbar-store"; -import type { Hotbar } from "../../../common/hotbar-types"; +import type { HotbarStore } from "../../../common/hotbars/store"; +import type { Hotbar } from "../../../common/hotbars/types"; const hotbarAddAction = Symbol("hotbar-add"); const hotbarRemoveAction = Symbol("hotbar-remove"); diff --git a/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts b/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts index 9b26fb35d2..e4d68e9f29 100644 --- a/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts +++ b/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts @@ -4,17 +4,21 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import { inputValidator } from "../input_validators"; const uniqueHotbarNameInjectable = getInjectable({ id: "unique-hotbar-name", - instantiate: di => inputValidator({ - condition: ({ required }) => required, - message: () => "Hotbar with this name already exists", - validate: value => !di.inject(hotbarStoreInjectable).getByName(value), - }), + instantiate: di => { + const store = di.inject(hotbarStoreInjectable); + + return inputValidator({ + condition: ({ required }) => required, + message: () => "Hotbar with this name already exists", + validate: value => !store.findByName(value), + }); + }, }); export default uniqueHotbarNameInjectable; diff --git a/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx b/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx index d132967230..831bb1d704 100644 --- a/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx +++ b/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx @@ -11,8 +11,8 @@ import { KubernetesCluster } from "../../../../common/catalog-entities"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; -import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; -import type { HotbarStore } from "../../../../common/hotbar-store"; +import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; +import type { HotbarStore } from "../../../../common/hotbars/store"; const clusterEntity = new KubernetesCluster({ metadata: { diff --git a/src/renderer/components/layout/sidebar-cluster.tsx b/src/renderer/components/layout/sidebar-cluster.tsx index abd83e47eb..99ed35aa7c 100644 --- a/src/renderer/components/layout/sidebar-cluster.tsx +++ b/src/renderer/components/layout/sidebar-cluster.tsx @@ -16,8 +16,8 @@ import { Menu, MenuItem } from "../menu"; import { ConfirmDialog } from "../confirm-dialog"; import { Tooltip } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; -import type { HotbarStore } from "../../../common/hotbar-store"; +import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; +import type { HotbarStore } from "../../../common/hotbars/store"; import { observer } from "mobx-react"; const contextMenu: CatalogEntityContextMenuContext = observable({ diff --git a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx index a1f0094b6b..5860239fd3 100644 --- a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx +++ b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx @@ -195,8 +195,6 @@ describe(" dataTree inside ", () => { expect(queryByTitle("Application")).toHaveAttribute("aria-expanded"); expect(queryByTitle("Kubernetes")).toHaveAttribute("aria-expanded"); }); - - // console.log(prettyDOM()); }); it("skips sections without headings", async () => { diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index 5521a657f1..c6ad222c63 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -4,7 +4,7 @@ */ import glob from "glob"; -import { isEqual, isPlainObject, memoize, noop } from "lodash/fp"; +import { isEqual, isPlainObject, memoize } from "lodash/fp"; import { createContainer } from "@ogre-tools/injectable"; import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import getValueFromRegisteredChannelInjectable from "./app-paths/get-value-from-registered-channel/get-value-from-registered-channel.injectable"; @@ -32,7 +32,7 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake"; import joinPathsInjectable from "../common/path/join-paths.injectable"; import { joinPathsFake } from "../common/test-utils/join-paths-fake"; -import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../common/hotbars/store.injectable"; import terminalSpawningPoolInjectable from "./components/dock/terminal/terminal-spawning-pool.injectable"; import hostedClusterIdInjectable from "../common/cluster-store/hosted-cluster-id.injectable"; import createStorageInjectable from "./utils/create-storage/create-storage.injectable"; @@ -130,10 +130,11 @@ export const getDiForUnitTesting = ( di.override(focusWindowInjectable, () => () => {}); di.override(loggerInjectable, () => ({ - warn: noop, - debug: noop, - error: (message: string, ...args: any) => console.error(message, ...args), - info: noop, + warn: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + silly: jest.fn(), })); } diff --git a/src/renderer/ipc/register-listeners.tsx b/src/renderer/ipc/register-listeners.tsx index a77d30eaa7..a9e0e76d2d 100644 --- a/src/renderer/ipc/register-listeners.tsx +++ b/src/renderer/ipc/register-listeners.tsx @@ -11,7 +11,7 @@ import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, ipcR import { Notifications, notificationsStore } from "../components/notifications"; import { Button } from "../components/button"; import { isMac } from "../../common/vars"; -import { defaultHotbarCells } from "../../common/hotbar-types"; +import { defaultHotbarCells } from "../../common/hotbars/types"; import { type ListNamespaceForbiddenArgs, clusterListNamespaceForbiddenChannel, isListNamespaceForbiddenArgs } from "../../common/ipc/cluster"; import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar"; @@ -31,7 +31,7 @@ function RenderYesButtons(props: { backchannel: string; notificationId: string }