diff --git a/.eslintrc.js b/.eslintrc.js index 76ba63ad07..b405dcc815 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -259,6 +259,10 @@ module.exports = { "name": ".", "message": "No importing from local index.ts(x?) file. A common way to make circular dependencies.", }, + { + "name": "..", + "message": "No importing from parent index.ts(x?) file. A common way to make circular dependencies.", + }, ], "patterns": [ { diff --git a/build/download_binaries.ts b/build/download_binaries.ts index 73c9dff175..5b4f960c5e 100644 --- a/build/download_binaries.ts +++ b/build/download_binaries.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import packageInfo from "../package.json"; -import { type WriteStream } from "fs"; import type { FileHandle } from "fs/promises"; import { open } from "fs/promises"; +import type { WriteStream } from "fs-extra"; import { constants, ensureDir, unlink } from "fs-extra"; import path from "path"; import fetch from "node-fetch"; @@ -17,6 +17,7 @@ import AbortController from "abort-controller"; import { extract } from "tar-stream"; import gunzip from "gunzip-maybe"; import { getBinaryName, normalizedPlatform } from "../src/common/vars"; +import { isErrnoException } from "../src/common/utils"; const pipeline = promisify(_pipeline); @@ -44,6 +45,10 @@ abstract class BinaryDownloader { } async ensureBinary(): Promise { + if (process.env.LENS_SKIP_DOWNLOAD_BINARIES === "true") { + return; + } + const controller = new AbortController(); const stream = await fetch(this.url, { timeout: 15 * 60 * 1000, // 15min @@ -51,7 +56,7 @@ abstract class BinaryDownloader { }); const total = Number(stream.headers.get("content-length")); const bar = this.bar; - let fileHandle: FileHandle; + let fileHandle: FileHandle | undefined = undefined; if (isNaN(total)) { throw new Error("no content-length header was present"); @@ -66,7 +71,7 @@ abstract class BinaryDownloader { * This is necessary because for some reason `createWriteStream({ flags: "wx" })` * was throwing someplace else and not here */ - fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL); + const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL); await pipeline( stream.body, @@ -79,7 +84,7 @@ abstract class BinaryDownloader { }), ...this.getTransformStreams(new Writable({ write(chunk, encoding, cb) { - fileHandle.write(chunk) + handle.write(chunk) .then(() => cb()) .catch(cb); }, @@ -90,7 +95,7 @@ abstract class BinaryDownloader { } catch (error) { await fileHandle?.close(); - if (error.code === "EEXIST") { + if (isErrnoException(error) && error.code === "EEXIST") { bar.increment(total); // mark as finished controller.abort(); // stop trying to download } else { diff --git a/src/behaviours/extensions/navigation-using-application-menu.test.ts b/src/behaviours/extensions/navigation-using-application-menu.test.ts index ef714a75d3..1489d40fc8 100644 --- a/src/behaviours/extensions/navigation-using-application-menu.test.ts +++ b/src/behaviours/extensions/navigation-using-application-menu.test.ts @@ -9,8 +9,8 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get- import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable"; import extensionsStoreInjectable from "../../extensions/extensions-store/extensions-store.injectable"; import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store"; -import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; -import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; +import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store"; import focusWindowInjectable from "../../renderer/ipc-channel-listeners/focus-window.injectable"; // TODO: Make components free of side effects by making them deterministic diff --git a/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts b/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts index bbddddcca7..d681713a80 100644 --- a/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts +++ b/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts @@ -2,20 +2,30 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { kubernetesClusterCategory } from "../kubernetes-cluster"; + +import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; +import kubernetesClusterCategoryInjectable from "../../catalog/categories/kubernetes-cluster.injectable"; +import type { KubernetesClusterCategory } from "../kubernetes-cluster"; + describe("kubernetesClusterCategory", () => { + let kubernetesClusterCategory: KubernetesClusterCategory; + + beforeEach(() => { + const di = getDiForUnitTesting(); + + kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable); + }); + describe("filteredItems", () => { const item1 = { icon: "Icon", title: "Title", - // eslint-disable-next-line @typescript-eslint/no-empty-function onClick: () => {}, }; const item2 = { icon: "Icon 2", title: "Title 2", - // eslint-disable-next-line @typescript-eslint/no-empty-function onClick: () => {}, }; diff --git a/src/common/catalog/category-registry.injectable.ts b/src/common/catalog/category-registry.injectable.ts index cf7de38826..a73ed973f6 100644 --- a/src/common/catalog/category-registry.injectable.ts +++ b/src/common/catalog/category-registry.injectable.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; -import { WebLinkCategory } from "../catalog-entities"; import type { CatalogCategory } from "./catalog-entity"; import { CatalogCategoryRegistry } from "./category-registry"; @@ -21,8 +20,6 @@ const catalogCategoryRegistryInjectable = getInjectable({ registry.add(category); } - registry.add(new WebLinkCategory()); - return registry; }, }); diff --git a/src/common/utils/collection-functions.ts b/src/common/utils/collection-functions.ts index 48f9fa4b4e..9badbad824 100644 --- a/src/common/utils/collection-functions.ts +++ b/src/common/utils/collection-functions.ts @@ -4,6 +4,7 @@ */ import { runInAction } from "mobx"; +import { inspect } from "util"; import { isDefined } from "./type-narrowing"; /** @@ -79,7 +80,7 @@ export async function getOrInsertWithAsync(map: Map, key: K, asyncBu */ export function strictSet(map: Map, key: K, val: V): typeof map { if (map.has(key)) { - throw new TypeError("Duplicate key in map"); + throw new TypeError(`Map already contains key: ${inspect(key)}`); } return map.set(key, val); @@ -92,7 +93,7 @@ export function strictSet(map: Map, key: K, val: V): typeof map { */ export function strictGet(map: Map, key: K): V { if (!map.has(key)) { - throw new TypeError("key not in map"); + throw new TypeError(`Map does not contains key: ${inspect(key)}`); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/src/extensions/extension-loader/create-extension-instance.token.ts b/src/extensions/extension-loader/create-extension-instance.token.ts new file mode 100644 index 0000000000..d7680b018b --- /dev/null +++ b/src/extensions/extension-loader/create-extension-instance.token.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { InstalledExtension } from "../extension-discovery/extension-discovery"; +import type { LensExtension, LensExtensionConstructor } from "../lens-extension"; + +export type CreateExtensionInstance = (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension; + +export const createExtensionInstanceInjectionToken = getInjectionToken({ + id: "create-extension-instance-token", +}); diff --git a/src/extensions/extension-loader/create-extension-instance/create-extension-instance.injectable.ts b/src/extensions/extension-loader/create-extension-instance/create-extension-instance.injectable.ts deleted file mode 100644 index d5bba359bc..0000000000 --- a/src/extensions/extension-loader/create-extension-instance/create-extension-instance.injectable.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { createExtensionInstance } from "./create-extension-instance"; -import fileSystemProvisionerStoreInjectable from "./file-system-provisioner-store/file-system-provisioner-store.injectable"; - -const createExtensionInstanceInjectable = getInjectable({ - id: "create-extension-instance", - - instantiate: (di) => createExtensionInstance({ - fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), - }), -}); - -export default createExtensionInstanceInjectable; diff --git a/src/extensions/extension-loader/create-extension-instance/create-extension-instance.ts b/src/extensions/extension-loader/create-extension-instance/create-extension-instance.ts deleted file mode 100644 index bd5dc6c6bb..0000000000 --- a/src/extensions/extension-loader/create-extension-instance/create-extension-instance.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { LensExtensionConstructor } from "../../lens-extension"; -import type { InstalledExtension } from "../../extension-discovery/extension-discovery"; -import type { - LensExtensionDependencies } from "../../lens-extension-set-dependencies"; -import { - setLensExtensionDependencies, -} from "../../lens-extension-set-dependencies"; - -export const createExtensionInstance = - (dependencies: LensExtensionDependencies) => - (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => { - const instance = new ExtensionClass(extension); - - instance[setLensExtensionDependencies](dependencies); - - return instance; - }; diff --git a/src/extensions/extension-loader/extension-instances.injectable.ts b/src/extensions/extension-loader/extension-instances.injectable.ts new file mode 100644 index 0000000000..08e7ad4cc5 --- /dev/null +++ b/src/extensions/extension-loader/extension-instances.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { LensExtensionId, LensExtension } from "../lens-extension"; + +const extensionInstancesInjectable = getInjectable({ + id: "extension-instances", + instantiate: () => observable.map(), +}); + +export default extensionInstancesInjectable; diff --git a/src/extensions/extension-loader/extension-loader.injectable.ts b/src/extensions/extension-loader/extension-loader.injectable.ts index 87f53cd686..1686b69f96 100644 --- a/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/src/extensions/extension-loader/extension-loader.injectable.ts @@ -5,20 +5,21 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionLoader } from "./extension-loader"; import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; -import createExtensionInstanceInjectable from "./create-extension-instance/create-extension-instance.injectable"; import { extensionRegistratorInjectionToken } from "./extension-registrator-injection-token"; import extensionInstallationCounterInjectable from "./extension-installation-counter.injectable"; +import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token"; +import extensionInstancesInjectable from "./extension-instances.injectable"; const extensionLoaderInjectable = getInjectable({ id: "extension-loader", - instantiate: (di) => - new ExtensionLoader({ - updateExtensionsState: di.inject(updateExtensionsStateInjectable), - createExtensionInstance: di.inject(createExtensionInstanceInjectable), - extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken), - extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable), - }), + instantiate: (di) => new ExtensionLoader({ + updateExtensionsState: di.inject(updateExtensionsStateInjectable), + createExtensionInstance: di.inject(createExtensionInstanceInjectionToken), + extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken), + extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable), + extensionInstances: di.inject(extensionInstancesInjectable), + }), }); export default extensionLoaderInjectable; diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index d957dc4d4a..3fc89b8547 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -4,8 +4,8 @@ */ import { ipcRenderer } from "electron"; -import { EventEmitter } from "events"; import { isEqual } from "lodash"; +import type { ObservableMap } from "mobx"; import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx"; import path from "path"; import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc"; @@ -21,14 +21,17 @@ import type { LensExtensionState } from "../extensions-store/extensions-store"; import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling"; import { requestExtensionLoaderInitialState } from "../../renderer/ipc"; import assert from "assert"; +import { EventEmitter } from "../../common/event-emitter"; +import type { CreateExtensionInstance } from "./create-extension-instance.token"; const logModule = "[EXTENSIONS-LOADER]"; interface Dependencies { updateExtensionsState: (extensionsState: Record) => void; - createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension; - extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => void)[]; - extensionInstallationCounter: Map; + createExtensionInstance: CreateExtensionInstance; + readonly extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => void)[]; + readonly extensionInstallationCounter: Map; + readonly extensionInstances: ObservableMap; } export interface ExtensionLoading { @@ -40,23 +43,21 @@ export interface ExtensionLoading { * Loads installed extensions to the Lens application */ export class ExtensionLoader { - protected extensions = observable.map(); - protected instances = observable.map(); + protected readonly extensions = observable.map(); /** * This is the set of extensions that don't come with either * - Main.LensExtension when running in the main process * - Renderer.LensExtension when running in the renderer process */ - protected nonInstancesByName = observable.set(); + protected readonly nonInstancesByName = observable.set(); /** * This is updated by the `observe` in the constructor. DO NOT write directly to it */ - protected instancesByName = observable.map(); + protected readonly instancesByName = observable.map(); - // emits event "remove" of type LensExtension when the extension is removed - private events = new EventEmitter(); + private readonly onRemoveExtensionId = new EventEmitter<[string]>(); @observable isLoaded = false; @@ -64,10 +65,10 @@ export class ExtensionLoader { return when(() => this.isLoaded); } - constructor(protected dependencies : Dependencies) { + constructor(protected readonly dependencies: Dependencies) { makeObservable(this); - observe(this.instances, change => { + observe(this.dependencies.extensionInstances, change => { switch (change.type) { case "add": if (this.instancesByName.has(change.newValue.name)) { @@ -85,10 +86,6 @@ export class ExtensionLoader { }); } - @computed get enabledExtensionInstances() : LensExtension[] { - return [...this.instances.values()].filter(extension => extension.isEnabled); - } - @computed get userExtensions(): Map { const extensions = this.toJSON(); @@ -164,7 +161,7 @@ export class ExtensionLoader { @action removeInstance(lensExtensionId: LensExtensionId) { logger.info(`${logModule} deleting extension instance ${lensExtensionId}`); - const instance = this.instances.get(lensExtensionId); + const instance = this.dependencies.extensionInstances.get(lensExtensionId); if (!instance) { return; @@ -172,8 +169,8 @@ export class ExtensionLoader { try { instance.disable(); - this.events.emit("remove", instance); - this.instances.delete(lensExtensionId); + this.onRemoveExtensionId.emit(instance.id); + this.dependencies.extensionInstances.delete(lensExtensionId); this.nonInstancesByName.delete(instance.name); } catch (error) { logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error }); @@ -260,8 +257,8 @@ export class ExtensionLoader { registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), ]; - this.events.on("remove", (removedExtension: LensRendererExtension) => { - if (removedExtension.id === extension.id) { + this.onRemoveExtensionId.addListener((removedExtensionId) => { + if (removedExtensionId === extension.id) { removeItems.forEach(remove => { remove(); }); @@ -288,8 +285,8 @@ export class ExtensionLoader { registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems), ]; - this.events.on("remove", (removedExtension: LensRendererExtension) => { - if (removedExtension.id === extension.id) { + this.onRemoveExtensionId.addListener((removedExtensionId) => { + if (removedExtensionId === extension.id) { removeItems.forEach(remove => { remove(); }); @@ -309,7 +306,7 @@ export class ExtensionLoader { const extensions = [...installedExtensions.entries()] .map(([extId, extension]) => { - const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); + const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); if (extension.isCompatible && extension.isEnabled && !alreadyInit) { try { @@ -326,7 +323,7 @@ export class ExtensionLoader { extension, ); - this.instances.set(extId, instance); + this.dependencies.extensionInstances.set(extId, instance); return { instance, @@ -415,7 +412,7 @@ export class ExtensionLoader { } getInstanceById(extId: LensExtensionId) { - return this.instances.get(extId); + return this.dependencies.extensionInstances.get(extId); } toJSON(): Map { diff --git a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/directory-for-extension-data/directory-for-extension-data.injectable.ts b/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts similarity index 71% rename from src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/directory-for-extension-data/directory-for-extension-data.injectable.ts rename to src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts index 854953e336..896c74da6d 100644 --- a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/directory-for-extension-data/directory-for-extension-data.injectable.ts +++ b/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getAbsolutePathInjectable from "../../../../../common/path/get-absolute-path.injectable"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import getAbsolutePathInjectable from "../../../common/path/get-absolute-path.injectable"; const directoryForExtensionDataInjectable = getInjectable({ id: "directory-for-extension-data", diff --git a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts b/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts similarity index 67% rename from src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts rename to src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts index 5c80330c0c..a0951baa8f 100644 --- a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts +++ b/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts @@ -4,17 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { FileSystemProvisionerStore } from "./file-system-provisioner-store"; -import directoryForExtensionDataInjectable from "./directory-for-extension-data/directory-for-extension-data.injectable"; +import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable"; const fileSystemProvisionerStoreInjectable = getInjectable({ id: "file-system-provisioner-store", - instantiate: (di) => - FileSystemProvisionerStore.createInstance({ - directoryForExtensionData: di.inject( - directoryForExtensionDataInjectable, - ), - }), + instantiate: (di) => { + FileSystemProvisionerStore.resetInstance(); + + return FileSystemProvisionerStore.createInstance({ + directoryForExtensionData: di.inject(directoryForExtensionDataInjectable), + }); + }, causesSideEffects: true, }); diff --git a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.ts b/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts similarity index 91% rename from src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.ts rename to src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts index 003d9eed6b..fab21df3cc 100644 --- a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.ts +++ b/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts @@ -8,9 +8,9 @@ import { SHA256 } from "crypto-js"; import fse from "fs-extra"; import { action, makeObservable, observable } from "mobx"; import path from "path"; -import { BaseStore } from "../../../../common/base-store"; -import type { LensExtensionId } from "../../../lens-extension"; -import { getOrInsertWith, toJS } from "../../../../common/utils"; +import { BaseStore } from "../../../common/base-store"; +import type { LensExtensionId } from "../../lens-extension"; +import { getOrInsertWith, toJS } from "../../../common/utils"; interface FSProvisionModel { extensions: Record; // extension names to paths diff --git a/src/extensions/extensions.injectable.ts b/src/extensions/extensions.injectable.ts index 36d6af1a92..7cc019a318 100644 --- a/src/extensions/extensions.injectable.ts +++ b/src/extensions/extensions.injectable.ts @@ -4,15 +4,14 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import extensionLoaderInjectable from "./extension-loader/extension-loader.injectable"; +import extensionInstancesInjectable from "./extension-loader/extension-instances.injectable"; const extensionsInjectable = getInjectable({ id: "extensions", - instantiate: (di) => { - const extensionLoader = di.inject(extensionLoaderInjectable); + const extensionInstances = di.inject(extensionInstancesInjectable); - return computed(() => extensionLoader.enabledExtensionInstances); + return computed(() => [...extensionInstances.values()].filter(extension => extension.isEnabled)); }, }); diff --git a/src/extensions/lens-extension-set-dependencies.ts b/src/extensions/lens-extension-set-dependencies.ts index d20f5cb1fc..9f30eeb8b8 100644 --- a/src/extensions/lens-extension-set-dependencies.ts +++ b/src/extensions/lens-extension-set-dependencies.ts @@ -3,12 +3,27 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { FileSystemProvisionerStore } from "./extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; - -// This symbol encapsulates setting of dependencies to only happen locally in Lens Core -// and not by e.g. authors of extensions -export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies"); +import type { IComputedValue } from "mobx"; +import type { CatalogCategoryRegistry } from "../common/catalog"; +import type { NavigateToRoute } from "../common/front-end-routing/navigate-to-route-injection-token"; +import type { Route } from "../common/front-end-routing/route-injection-token"; +import type { CatalogEntityRegistry as MainCatalogEntityRegistry } from "../main/catalog"; +import type { CatalogEntityRegistry as RendererCatalogEntityRegistry } from "../renderer/api/catalog/entity/registry"; +import type { GetExtensionPageParameters } from "../renderer/routes/get-extension-page-parameters.injectable"; +import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store"; export interface LensExtensionDependencies { - fileSystemProvisionerStore: FileSystemProvisionerStore; + readonly fileSystemProvisionerStore: FileSystemProvisionerStore; +} + +export interface LensMainExtensionDependencies extends LensExtensionDependencies { + readonly entityRegistry: MainCatalogEntityRegistry; +} + +export interface LensRendererExtensionDependencies extends LensExtensionDependencies { + navigateToRoute: NavigateToRoute; + getExtensionPageParameters: GetExtensionPageParameters; + readonly routes: IComputedValue[]>; + readonly entityRegistry: RendererCatalogEntityRegistry; + readonly categoryRegistry: CatalogCategoryRegistry; } diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index 1758f0698e..c0f6d48ee7 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -10,11 +10,7 @@ import type { ProtocolHandlerRegistration } from "./registries"; import type { PackageJson } from "type-fest"; import type { Disposer } from "../common/utils"; import { disposer } from "../common/utils"; -import type { - LensExtensionDependencies } from "./lens-extension-set-dependencies"; -import { - setLensExtensionDependencies, -} from "./lens-extension-set-dependencies"; +import type { LensExtensionDependencies } from "./lens-extension-set-dependencies"; export type LensExtensionId = string; // path to manifest (package.json) export type LensExtensionConstructor = new (...args: ConstructorParameters) => LensExtension; @@ -28,7 +24,7 @@ export interface LensExtensionManifest extends PackageJson { export const Disposers = Symbol(); -export class LensExtension { +export class LensExtension { readonly id: LensExtensionId; readonly manifest: LensExtensionManifest; readonly manifestPath: string; @@ -68,11 +64,7 @@ export class LensExtension { return this.manifest.description; } - private readonly dependencies!: LensExtensionDependencies; - - [setLensExtensionDependencies] = (dependencies: LensExtensionDependencies) => { - (this as unknown as { dependencies: LensExtensionDependencies }).dependencies = dependencies; - }; + protected readonly dependencies!: Dependencies; /** * getExtensionFileFolder returns the path to an already created folder. This diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index bda6024522..d12aafdf8d 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -5,27 +5,27 @@ import { LensExtension } from "./lens-extension"; import { WindowManager } from "../main/window-manager"; -import { catalogEntityRegistry } from "../main/catalog"; import type { CatalogEntity } from "../common/catalog"; import type { IObservableArray } from "mobx"; import type { MenuRegistration } from "../main/menu/menu-registration"; import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration"; +import type { LensMainExtensionDependencies } from "./lens-extension-set-dependencies"; -export class LensMainExtension extends LensExtension { +export class LensMainExtension extends LensExtension { appMenus: MenuRegistration[] = []; trayMenus: TrayMenuRegistration[] = []; /** * implement this to modify the shell environment that Lens terminals are opened with. The ShellEnvModifier type has the signature - * + * * (ctx: ShellEnvContext, env: Record) => Record - * + * * @param ctx the shell environment context, specifically the relevant catalog entity for the terminal. This can be used, for example, to get * cluster-specific information that can be made available in the shell environment by the implementation of terminalShellEnvModifier - * + * * @param env the current shell environment that the terminal will be opened with. The implementation should modify this as desired. - * + * * @returns the modified shell environment that the terminal will be opened with. The implementation must return env as passed in, if it * does not modify the shell environment */ @@ -36,10 +36,10 @@ export class LensMainExtension extends LensExtension { } addCatalogSource(id: string, source: IObservableArray) { - catalogEntityRegistry.addObservableSource(`${this.name}:${id}`, source); + this.dependencies.entityRegistry.addObservableSource(`${this.name}:${id}`, source); } removeCatalogSource(id: string) { - catalogEntityRegistry.removeSource(`${this.name}:${id}`); + this.dependencies.entityRegistry.removeSource(`${this.name}:${id}`); } } diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 02329930b1..12fd243cf6 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -7,10 +7,8 @@ import type * as registries from "./registries"; import { Disposers, LensExtension } from "./lens-extension"; import type { CatalogEntity } from "../common/catalog"; import type { Disposer } from "../common/utils"; -import type { EntityFilter } from "../renderer/api/catalog-entity-registry"; -import { catalogEntityRegistry } from "../renderer/api/catalog-entity-registry"; +import type { EntityFilter } from "../renderer/api/catalog/entity/registry"; import type { CategoryFilter } from "../renderer/api/catalog-category-registry"; -import { catalogCategoryRegistry } from "../renderer/api/catalog-category-registry"; import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration"; import type { KubernetesCluster } from "../common/catalog-entities"; import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration"; @@ -23,15 +21,12 @@ import type { StatusBarRegistration } from "../renderer/components/status-bar/st import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration"; import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration"; import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration"; -import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import routesInjectable from "../renderer/routes/routes.injectable"; import { fromPairs, map, matches, toPairs } from "lodash/fp"; -import extensionPageParametersInjectable from "../renderer/routes/extension-page-parameters.injectable"; import { pipeline } from "@ogre-tools/fp"; -import { navigateToRouteInjectionToken } from "../common/front-end-routing/navigate-to-route-injection-token"; import { getExtensionRoutePath } from "../renderer/routes/for-extension"; +import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies"; -export class LensRendererExtension extends LensExtension { +export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; clusterPages: registries.PageRegistration[] = []; clusterPageMenus: registries.ClusterPageMenuRegistration[] = []; @@ -51,9 +46,7 @@ export class LensRendererExtension extends LensExtension { customCategoryViews: CustomCategoryViewRegistration[] = []; async navigate(pageId?: string, params: object = {}) { - const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.renderer); - const navigateToRoute = di.inject(navigateToRouteInjectionToken); - const routes = di.inject(routesInjectable).get(); + const routes = this.dependencies.routes.get(); const targetRegistration = [...this.globalPages, ...this.clusterPages] .find(registration => registration.id === (pageId || undefined)); @@ -68,7 +61,7 @@ export class LensRendererExtension extends LensExtension { return; } - const normalizedParams = di.inject(extensionPageParametersInjectable, { + const normalizedParams = this.dependencies.getExtensionPageParameters({ extension: this, registration: targetRegistration, }); @@ -82,7 +75,7 @@ export class LensRendererExtension extends LensExtension { fromPairs, ); - navigateToRoute(targetRoute, { + this.dependencies.navigateToRoute(targetRoute, { query, }); } @@ -103,7 +96,7 @@ export class LensRendererExtension extends LensExtension { * @returns A function to clean up the filter */ addCatalogFilter(fn: EntityFilter): Disposer { - const dispose = catalogEntityRegistry.addCatalogFilter(fn); + const dispose = this.dependencies.entityRegistry.addCatalogFilter(fn); this[Disposers].push(dispose); @@ -116,7 +109,7 @@ export class LensRendererExtension extends LensExtension { * @returns A function to clean up the filter */ addCatalogCategoryFilter(fn: CategoryFilter): Disposer { - const dispose = catalogCategoryRegistry.addCatalogCategoryFilter(fn); + const dispose = this.dependencies.categoryRegistry.addCatalogCategoryFilter(fn); this[Disposers].push(dispose); diff --git a/src/extensions/renderer-api/catalog.ts b/src/extensions/renderer-api/catalog.ts index 64ee38a20f..87506926e2 100644 --- a/src/extensions/renderer-api/catalog.ts +++ b/src/extensions/renderer-api/catalog.ts @@ -5,24 +5,26 @@ import type { CatalogCategory, CatalogEntity } from "../../common/catalog"; -import { catalogEntityRegistry as registry } from "../../renderer/api/catalog-entity-registry"; -import type { CatalogEntityOnBeforeRun } from "../../renderer/api/catalog-entity-registry"; +import type { CatalogEntityOnBeforeRun } from "../../renderer/api/catalog/entity/registry"; import type { Disposer } from "../../common/utils"; import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; +import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; export const catalogCategories = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable); +const internalEntityRegistry = asLegacyGlobalForExtensionApi(catalogEntityRegistryInjectable); + export class CatalogEntityRegistry { /** * Currently active/visible entity */ get activeEntity() { - return registry.activeEntity; + return internalEntityRegistry.activeEntity; } get entities(): Map { - return registry.entities; + return internalEntityRegistry.entities; } getById(id: string) { @@ -30,11 +32,11 @@ export class CatalogEntityRegistry { } getItemsForApiKind(apiVersion: string, kind: string): T[] { - return registry.getItemsForApiKind(apiVersion, kind); + return internalEntityRegistry.getItemsForApiKind(apiVersion, kind); } getItemsForCategory(category: CatalogCategory): T[] { - return registry.getItemsForCategory(category); + return internalEntityRegistry.getItemsForCategory(category); } /** @@ -46,7 +48,7 @@ export class CatalogEntityRegistry { * @returns A function to remove that hook */ addOnBeforeRun(onBeforeRun: CatalogEntityOnBeforeRun): Disposer { - return registry.addOnBeforeRun(onBeforeRun); + return internalEntityRegistry.addOnBeforeRun(onBeforeRun); } } diff --git a/src/main/extension-loader/create-extension-instance.injectable.ts b/src/main/extension-loader/create-extension-instance.injectable.ts new file mode 100644 index 0000000000..db28e80082 --- /dev/null +++ b/src/main/extension-loader/create-extension-instance.injectable.ts @@ -0,0 +1,30 @@ +/** + * 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 { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token"; +import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import type { LensMainExtensionDependencies } from "../../extensions/lens-extension-set-dependencies"; +import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable"; + +const createExtensionInstanceInjectable = getInjectable({ + id: "create-extension-instance", + instantiate: (di) => { + const deps: LensMainExtensionDependencies = { + fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), + entityRegistry: di.inject(catalogEntityRegistryInjectable), + }; + + return (ExtensionClass, extension) => { + const instance = new ExtensionClass(extension); + + (instance as unknown as { dependencies: LensMainExtensionDependencies }).dependencies = deps; + + return instance; + }; + }, + injectionToken: createExtensionInstanceInjectionToken, +}); + +export default createExtensionInstanceInjectable; diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index a22480a673..a355c7698c 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -20,8 +20,8 @@ import loggerInjectable from "../common/logger.injectable"; import spawnInjectable from "./child-process/spawn.injectable"; import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable"; import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store"; -import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; -import type { FileSystemProvisionerStore } from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; +import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store"; import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; import type { ClusterStore } from "../common/cluster-store/cluster-store"; import type { Cluster } from "../common/cluster/cluster"; diff --git a/src/renderer/api/__tests__/catalog-entity-registry.test.ts b/src/renderer/api/__tests__/catalog-entity-registry.test.ts index 878f4328ca..08dacb8f5a 100644 --- a/src/renderer/api/__tests__/catalog-entity-registry.test.ts +++ b/src/renderer/api/__tests__/catalog-entity-registry.test.ts @@ -3,24 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { CatalogEntityRegistry } from "../catalog-entity-registry"; -import type { CatalogEntityData, CatalogEntityKindData } from "../catalog-entity"; import { CatalogCategory } from "../catalog-entity"; import { KubernetesCluster, WebLink } from "../../../common/catalog-entities"; import { observable } from "mobx"; import type { CatalogCategoryRegistry } from "../../../common/catalog"; import { categoryVersion } from "../../../common/catalog"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import type { CatalogEntityRegistry } from "../catalog/entity/registry"; +import catalogEntityRegistryInjectable from "../catalog/entity/registry.injectable"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; -class TestCatalogEntityRegistry extends CatalogEntityRegistry { - replaceItems(items: Array) { - this.updateItems(items); - } -} class FooBarCategory extends CatalogCategory { - public readonly apiVersion = "catalog.k8slens.dev/v1alpha1"; + public readonly apiVersion = "entityRegistry.k8slens.dev/v1alpha1"; public readonly kind = "CatalogCategory"; public metadata = { name: "FooBars", @@ -81,17 +76,18 @@ const entitykc = new KubernetesCluster({ }); describe("CatalogEntityRegistry", () => { + let entityRegistry: CatalogEntityRegistry; let catalogCategoryRegistry: CatalogCategoryRegistry; beforeEach(() => { const di = getDiForUnitTesting(); + entityRegistry = di.inject(catalogEntityRegistryInjectable); catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable); }); describe("updateItems", () => { it("adds new catalog item", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [{ apiVersion: "entity.k8slens.dev/v1alpha1", kind: "KubernetesCluster", @@ -107,8 +103,8 @@ describe("CatalogEntityRegistry", () => { spec: {}, }]; - catalog.replaceItems(items); - expect(catalog.items.length).toEqual(1); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toEqual(1); items.push({ apiVersion: "entity.k8slens.dev/v1alpha1", @@ -125,12 +121,11 @@ describe("CatalogEntityRegistry", () => { spec: {}, }); - catalog.replaceItems(items); - expect(catalog.items.length).toEqual(2); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toEqual(2); }); it("updates existing items", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [{ apiVersion: "entity.k8slens.dev/v1alpha1", kind: "KubernetesCluster", @@ -146,19 +141,18 @@ describe("CatalogEntityRegistry", () => { spec: {}, }]; - catalog.replaceItems(items); - expect(catalog.items.length).toEqual(1); - expect(catalog.items[0].status.phase).toEqual("disconnected"); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toEqual(1); + expect(entityRegistry.items.get()[0].status.phase).toEqual("disconnected"); items[0].status.phase = "connected"; - catalog.replaceItems(items); - expect(catalog.items.length).toEqual(1); - expect(catalog.items[0].status.phase).toEqual("connected"); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toEqual(1); + expect(entityRegistry.items.get()[0].status.phase).toEqual("connected"); }); it("updates activeEntity", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [{ apiVersion: "entity.k8slens.dev/v1alpha1", kind: "KubernetesCluster", @@ -174,17 +168,16 @@ describe("CatalogEntityRegistry", () => { spec: {}, }]; - catalog.replaceItems(items); - catalog.activeEntity = catalog.items[0]; - expect(catalog.activeEntity.status.phase).toEqual("disconnected"); + entityRegistry.updateItems(items); + entityRegistry.activeEntity = entityRegistry.items.get()[0]; + expect(entityRegistry.activeEntity.status.phase).toEqual("disconnected"); items[0].status.phase = "connected"; - catalog.replaceItems(items); - expect(catalog.activeEntity.status.phase).toEqual("connected"); + entityRegistry.updateItems(items); + expect(entityRegistry.activeEntity.status.phase).toEqual("connected"); }); it("removes deleted items", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [ { apiVersion: "entity.k8slens.dev/v1alpha1", @@ -216,17 +209,16 @@ describe("CatalogEntityRegistry", () => { }, ]; - catalog.replaceItems(items); + entityRegistry.updateItems(items); items.splice(0, 1); - catalog.replaceItems(items); - expect(catalog.items.length).toEqual(1); - expect(catalog.items[0].metadata.uid).toEqual("456"); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toEqual(1); + expect(entityRegistry.items.get()[0].metadata.uid).toEqual("456"); }); }); describe("items", () => { it("does not return items without matching category", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [ { apiVersion: "entity.k8slens.dev/v1alpha1", @@ -258,13 +250,12 @@ describe("CatalogEntityRegistry", () => { }, ]; - catalog.replaceItems(items); - expect(catalog.items.length).toBe(1); + entityRegistry.updateItems(items); + expect(entityRegistry.items.get().length).toBe(1); }); }); it("does return items after matching category is added", () => { - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [ { apiVersion: "entity.k8slens.dev/v1alpha1", @@ -282,29 +273,28 @@ describe("CatalogEntityRegistry", () => { }, ]; - catalog.replaceItems(items); + entityRegistry.updateItems(items); catalogCategoryRegistry.add(new FooBarCategory()); - expect(catalog.items.length).toBe(1); + expect(entityRegistry.items.get().length).toBe(1); }); it("does not return items that are filtered out", () => { const source = observable.array([entity, entity2, entitykc]); - const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); - catalog.replaceItems(source); + entityRegistry.updateItems(source); - expect(catalog.items.length).toBe(3); - expect(catalog.filteredItems.length).toBe(3); + expect(entityRegistry.items.get().length).toBe(3); + expect(entityRegistry.filteredItems.length).toBe(3); - const d = catalog.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind); + const d = entityRegistry.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind); - expect(catalog.items.length).toBe(3); - expect(catalog.filteredItems.length).toBe(1); + expect(entityRegistry.items.get().length).toBe(3); + expect(entityRegistry.filteredItems.length).toBe(1); // Remove filter d(); - expect(catalog.items.length).toBe(3); - expect(catalog.filteredItems.length).toBe(3); + expect(entityRegistry.items.get().length).toBe(3); + expect(entityRegistry.filteredItems.length).toBe(3); }); }); diff --git a/src/renderer/api/catalog-category-registry.ts b/src/renderer/api/catalog-category-registry.ts index 6cdbd8eb71..9023c5a755 100644 --- a/src/renderer/api/catalog-category-registry.ts +++ b/src/renderer/api/catalog-category-registry.ts @@ -3,5 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export { catalogCategoryRegistry } from "../../common/catalog"; +import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable"; +import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; + export type { CategoryFilter } from "../../common/catalog"; + +export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable); diff --git a/src/renderer/api/catalog-entity-registry/catalog-entity-registry.injectable.ts b/src/renderer/api/catalog-entity-registry/catalog-entity-registry.injectable.ts deleted file mode 100644 index 98af561838..0000000000 --- a/src/renderer/api/catalog-entity-registry/catalog-entity-registry.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { catalogEntityRegistry } from "../catalog-entity-registry"; - -const catalogEntityRegistryInjectable = getInjectable({ - id: "catalog-entity-registry", - instantiate: () => catalogEntityRegistry, -}); - -export default catalogEntityRegistryInjectable; diff --git a/src/renderer/api/catalog-entity.ts b/src/renderer/api/catalog-entity.ts index 09148c79a3..c4dea91071 100644 --- a/src/renderer/api/catalog-entity.ts +++ b/src/renderer/api/catalog-entity.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export { catalogEntityRunContext } from "./catalog-entity-registry"; export { CatalogCategory, CatalogEntity } from "../../common/catalog"; export type { CatalogEntityData, diff --git a/src/renderer/api/catalog/entity/active.injectable.ts b/src/renderer/api/catalog/entity/active.injectable.ts new file mode 100644 index 0000000000..1a0be2c457 --- /dev/null +++ b/src/renderer/api/catalog/entity/active.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import catalogEntityRegistryInjectable from "./registry.injectable"; + +const activeEntityInjectable = getInjectable({ + id: "active-entity", + instantiate: (di) => { + const registry = di.inject(catalogEntityRegistryInjectable); + + return computed(() => registry.activeEntity); + }, +}); + +export default activeEntityInjectable; diff --git a/src/renderer/api/catalog/entity/entities.injectable.ts b/src/renderer/api/catalog/entity/entities.injectable.ts new file mode 100644 index 0000000000..f14df295fa --- /dev/null +++ b/src/renderer/api/catalog/entity/entities.injectable.ts @@ -0,0 +1,13 @@ +/** + * 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 catalogEntityRegistryInjectable from "./registry.injectable"; + +const catalogEnitiesInjectable = getInjectable({ + id: "catalog-enities", + instantiate: (di) => di.inject(catalogEntityRegistryInjectable).items, +}); + +export default catalogEnitiesInjectable; diff --git a/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts b/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts new file mode 100644 index 0000000000..d5a2d1f3f8 --- /dev/null +++ b/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import type { Cluster } from "../../../../common/cluster/cluster"; +import catalogEntityRegistryInjectable from "./registry.injectable"; + +export type GetActiveClusterEntity = () => Cluster | undefined; + +const getActiveClusterEntityInjectable = getInjectable({ + id: "get-active-cluster-entity", + instantiate: (di): GetActiveClusterEntity => { + const store = di.inject(clusterStoreInjectable); + const entityRegistry = di.inject(catalogEntityRegistryInjectable); + + return () => store.getById(entityRegistry.activeEntity?.getId()); + }, +}); + +export default getActiveClusterEntityInjectable; diff --git a/src/renderer/api/catalog/entity/legacy-globals.ts b/src/renderer/api/catalog/entity/legacy-globals.ts new file mode 100644 index 0000000000..70a30f92f8 --- /dev/null +++ b/src/renderer/api/catalog/entity/legacy-globals.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { asLegacyGlobalFunctionForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import getActiveClusterEntityInjectable from "./get-active-cluster-entity.injectable"; + +/** + * @deprecated use `di.inject(getActiveClusterEntityInjectable)` instead + */ +export const getActiveClusterEntity = asLegacyGlobalFunctionForExtensionApi(getActiveClusterEntityInjectable); diff --git a/src/renderer/api/catalog/entity/registry.injectable.ts b/src/renderer/api/catalog/entity/registry.injectable.ts new file mode 100644 index 0000000000..03d4edf52e --- /dev/null +++ b/src/renderer/api/catalog/entity/registry.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable"; +import navigateInjectable from "../../../navigation/navigate.injectable"; +import { CatalogEntityRegistry } from "./registry"; + +const catalogEntityRegistryInjectable = getInjectable({ + id: "catalog-entity-registry", + instantiate: (di) => new CatalogEntityRegistry({ + categoryRegistry: di.inject(catalogCategoryRegistryInjectable), + navigate: di.inject(navigateInjectable), + }), +}); + +export default catalogEntityRegistryInjectable; diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog/entity/registry.ts similarity index 74% rename from src/renderer/api/catalog-entity-registry.ts rename to src/renderer/api/catalog/entity/registry.ts index 2b056eee9c..1f53cf3d94 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog/entity/registry.ts @@ -4,57 +4,54 @@ */ import { computed, observable, makeObservable, action } from "mobx"; -import { ipcRendererOn } from "../../common/ipc"; -import type { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogCategoryRegistry, CatalogEntityKindData } from "../../common/catalog"; -import { catalogCategoryRegistry } from "../../common/catalog"; -import "../../common/catalog-entities"; -import type { Cluster } from "../../common/cluster/cluster"; -import { ClusterStore } from "../../common/cluster-store/cluster-store"; -import { iter } from "../utils"; -import type { Disposer } from "../utils"; +import { ipcRendererOn } from "../../../../common/ipc"; +import type { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogCategoryRegistry, CatalogEntityKindData } from "../../../../common/catalog"; +import "../../../../common/catalog-entities"; +import { iter } from "../../../utils"; +import type { Disposer } from "../../../utils"; import { once } from "lodash"; -import logger from "../../common/logger"; -import { CatalogRunEvent } from "../../common/catalog/catalog-run-event"; +import logger from "../../../../common/logger"; +import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event"; import { ipcRenderer } from "electron"; -import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../common/ipc/catalog"; -import { navigate } from "../navigation"; +import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog"; import { isMainFrame } from "process"; +import type { Navigate } from "../../../navigation/navigate.injectable"; export type EntityFilter = (entity: CatalogEntity) => any; export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise; -export const catalogEntityRunContext = { - navigate: (url: string) => navigate(url), - setCommandPaletteContext: (entity?: CatalogEntity) => { - catalogEntityRegistry.activeEntity = entity; - }, -}; +interface Dependencies { + navigate: Navigate; + readonly categoryRegistry: CatalogCategoryRegistry; +} export class CatalogEntityRegistry { - @observable protected activeEntityId: string | undefined = undefined; - protected _entities = observable.map([], { deep: true }); - protected filters = observable.set([], { + protected readonly activeEntityId = observable.box(undefined); + protected readonly _entities = observable.map([], { deep: true }); + protected readonly filters = observable.set([], { deep: false, }); - protected onBeforeRunHooks = observable.set([], { + protected readonly onBeforeRunHooks = observable.set([], { deep: false, }); /** * Buffer for keeping entities that don't yet have CatalogCategory synced */ - protected rawEntities: (CatalogEntityData & CatalogEntityKindData)[] = []; + protected readonly rawEntities: (CatalogEntityData & CatalogEntityKindData)[] = []; - constructor(private categoryRegistry: CatalogCategoryRegistry) { + constructor(protected readonly dependencies: Dependencies) { makeObservable(this); } protected getActiveEntityById() { - if (!this.activeEntityId) { + const activeEntityId = this.activeEntityId.get(); + + if (!activeEntityId) { return undefined; } - return this._entities.get(this.activeEntityId); + return this._entities.get(activeEntityId); } get activeEntity(): CatalogEntity | undefined { @@ -78,9 +75,9 @@ export class CatalogEntityRegistry { ? raw : raw.getId(); - this.activeEntityId = id; + this.activeEntityId.set(id); } else { - this.activeEntityId = undefined; + this.activeEntityId.set(undefined); } } @@ -123,7 +120,7 @@ export class CatalogEntityRegistry { const existing = this._entities.get(item.metadata.uid); if (!existing) { - const entity = this.categoryRegistry.getEntityForData(item); + const entity = this.dependencies.categoryRegistry.getEntityForData(item); if (entity) { this._entities.set(entity.getId(), entity); @@ -147,25 +144,25 @@ export class CatalogEntityRegistry { } } - @computed get items() { + readonly items = computed(() => { this.processRawEntities(); return Array.from(this._entities.values()); - } + }); @computed get filteredItems() { return Array.from( iter.reduce( this.filters, iter.filter, - this.items.values(), + this.items.get().values(), ), ); } @computed get entities(): Map { return new Map( - this.items.map(entity => [entity.getId(), entity]), + this.items.get().map(entity => [entity.getId(), entity]), ); } @@ -175,13 +172,13 @@ export class CatalogEntityRegistry { ); } - getById(id: string) { - return this.entities.get(id) as T; + getById(id: string) { + return this.entities.get(id); } getItemsForApiKind(apiVersion: string, kind: string, { filtered = false } = {}): T[] { const byApiKind = (item: CatalogEntity) => item.apiVersion === apiVersion && item.kind === kind; - const entities = filtered ? this.filteredItems : this.items; + const entities = filtered ? this.filteredItems : this.items.get(); return entities.filter(byApiKind) as T[]; } @@ -189,7 +186,7 @@ export class CatalogEntityRegistry { getItemsForCategory(category: CatalogCategory, { filtered = false } = {}): T[] { const supportedVersions = new Set(category.spec.versions.map((v) => `${category.spec.group}/${v.name}`)); const byApiVersionKind = (item: CatalogEntity) => supportedVersions.has(item.apiVersion) && item.kind === category.spec.names.kind; - const entities = filtered ? this.filteredItems : this.items; + const entities = filtered ? this.filteredItems : this.items.get(); return entities.filter(byApiVersionKind) as T[]; } @@ -249,7 +246,12 @@ export class CatalogEntityRegistry { this.onBeforeRun(entity) .then(doOnRun => { if (doOnRun) { - return entity.onRun?.(catalogEntityRunContext); + return entity.onRun?.({ + navigate: this.dependencies.navigate, + setCommandPaletteContext: (entity) => { + this.activeEntity = entity; + }, + }); } else { logger.debug(`onBeforeRun for ${entity.getId()} returned false`); } @@ -257,9 +259,3 @@ export class CatalogEntityRegistry { .catch(error => logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error)); } } - -export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); - -export function getActiveClusterEntity(): Cluster | undefined { - return ClusterStore.getInstance().getById(catalogEntityRegistry.activeEntity?.getId()); -} diff --git a/src/renderer/api/helpers/watch-for-general-entity-navigation.injectable.ts b/src/renderer/api/helpers/watch-for-general-entity-navigation.injectable.ts index 6c032a07ee..14ae9f9f98 100644 --- a/src/renderer/api/helpers/watch-for-general-entity-navigation.injectable.ts +++ b/src/renderer/api/helpers/watch-for-general-entity-navigation.injectable.ts @@ -9,7 +9,7 @@ import isActiveRouteInjectable from "../../navigation/is-route-active.injectable import observableHistoryInjectable from "../../navigation/observable-history.injectable"; import type { Disposer } from "../../utils"; import { disposer } from "../../utils"; -import catalogEntityRegistryInjectable from "../catalog-entity-registry/catalog-entity-registry.injectable"; +import catalogEntityRegistryInjectable from "../catalog/entity/registry.injectable"; export type WatchForGeneralEntityNavigation = () => Disposer; diff --git a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts index a2bb9279e3..83b564566b 100644 --- a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts +++ b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts @@ -11,8 +11,8 @@ import rendererExtensionsInjectable from "../../../../extensions/renderer-extens import { CatalogCategory } from "../../../api/catalog-entity"; 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 type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable"; +import getCategoryColumnsInjectable from "../columns/get.injectable"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; class TestCategory extends CatalogCategory { diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index 11f2d65291..fb70b06062 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -14,16 +14,22 @@ import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { isDevelopment } from "../../../common/vars"; import { cssNames } from "../../utils"; import { Avatar } from "../avatar"; -import { getLabelBadges } from "./helpers"; +import type { GetLabelBadges } from "./get-label-badges.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import getLabelBadgesInjectable from "./get-label-badges.injectable"; -export interface CatalogEntityDetailsProps { - entity: T; +export interface CatalogEntityDetailsProps { + entity: Entity; hideDetails(): void; onRun: () => void; } +interface Dependencies { + getLabelBadges: GetLabelBadges; +} + @observer -export class CatalogEntityDetails extends Component> { +class NonInjectedCatalogEntityDetails extends Component & Dependencies> { categoryIcon(category: CatalogCategory) { if (Icon.isSvg(category.metadata.icon)) { return ; @@ -32,8 +38,8 @@ export class CatalogEntityDetails extends Component ); const showDefaultDetails = detailItems.find((item) => item.priority ?? 50 > 999) === undefined; @@ -109,3 +115,10 @@ export class CatalogEntityDetails extends Component>(NonInjectedCatalogEntityDetails, { + getProps: (di, props) => ({ + ...props, + getLabelBadges: di.inject(getLabelBadgesInjectable), + }), +}) as (props: CatalogEntityDetailsProps) => React.ReactElement; diff --git a/src/renderer/components/+catalog/catalog-entity-store/catalog-entity-store.injectable.ts b/src/renderer/components/+catalog/catalog-entity-store/catalog-entity-store.injectable.ts index 2e9175ebcb..6e3cb979e4 100644 --- a/src/renderer/components/+catalog/catalog-entity-store/catalog-entity-store.injectable.ts +++ b/src/renderer/components/+catalog/catalog-entity-store/catalog-entity-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { catalogEntityStore } from "./catalog-entity.store"; -import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable"; +import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable"; import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable"; const catalogEntityStoreInjectable = getInjectable({ diff --git a/src/renderer/components/+catalog/catalog-entity-store/catalog-entity.store.tsx b/src/renderer/components/+catalog/catalog-entity-store/catalog-entity.store.tsx index 75e1b263d2..f0562fd736 100644 --- a/src/renderer/components/+catalog/catalog-entity-store/catalog-entity.store.tsx +++ b/src/renderer/components/+catalog/catalog-entity-store/catalog-entity.store.tsx @@ -5,7 +5,7 @@ import type { IComputedValue, IObservableValue } from "mobx"; import { computed, observable, reaction } from "mobx"; -import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry"; +import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry"; import type { CatalogEntity } from "../../../api/catalog-entity"; import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog"; import type { Disposer } from "../../../../common/utils"; diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 61db8b7a5b..c963c8a481 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -9,14 +9,14 @@ import userEvent from "@testing-library/user-event"; import { Catalog } from "./catalog"; import { mockWindow } from "../../../../__mocks__/windowMock"; import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; -import { CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog"; -import { CatalogEntityRegistry } from "../../api/catalog-entity-registry"; +import { CatalogEntity } from "../../../common/catalog"; +import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiContainer } from "@ogre-tools/injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable"; -import catalogEntityRegistryInjectable from "../../api/catalog-entity-registry/catalog-entity-registry.injectable"; +import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; import mockFs from "mock-fs"; @@ -101,10 +101,7 @@ describe("", () => { render = renderFor(di); onRun = jest.fn(); catalogEntityItem = createMockCatalogEntity(onRun); - - const catalogCategoryRegistry = new CatalogCategoryRegistry(); - - catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); + catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry); diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index df7693d036..1441455d56 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -30,8 +30,8 @@ import { Avatar } from "../avatar"; import { withInjectables } from "@ogre-tools/injectable-react"; import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable"; -import type { GetCategoryColumnsParams, CategoryColumns } from "./get-category-columns.injectable"; -import getCategoryColumnsInjectable from "./get-category-columns.injectable"; +import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable"; +import getCategoryColumnsInjectable from "./columns/get.injectable"; import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable"; import customCategoryViewsInjectable from "./custom-views.injectable"; import type { CustomCategoryViewComponents } from "./custom-views"; diff --git a/src/renderer/components/+catalog/columns/browse-all.injectable.tsx b/src/renderer/components/+catalog/columns/browse-all.injectable.tsx new file mode 100644 index 0000000000..b7e1ddddb7 --- /dev/null +++ b/src/renderer/components/+catalog/columns/browse-all.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +import { getInjectable } from "@ogre-tools/injectable"; +import namedCategoryColumnInjectable from "./named-category.injectable"; +import defaultCategoryColumnsInjectable from "./default-category.injectable"; + +const defaultBrowseAllColumns: RegisteredAdditionalCategoryColumn[] = [ + { + id: "kind", + priority: 5, + renderCell: entity => entity.kind, + titleProps: { + id: "kind", + sortBy: "kind", + title: "Kind", + }, + sortCallback: entity => entity.kind, + }, +]; + +const browseAllColumnsInjectable = getInjectable({ + id: "browse-all-columns", + instantiate: (di) => [ + ...defaultBrowseAllColumns, + di.inject(namedCategoryColumnInjectable), + ...di.inject(defaultCategoryColumnsInjectable), + ], +}); + +export default browseAllColumnsInjectable; + diff --git a/src/renderer/components/+catalog/columns/default-category.injectable.tsx b/src/renderer/components/+catalog/columns/default-category.injectable.tsx new file mode 100644 index 0000000000..00df70cdbc --- /dev/null +++ b/src/renderer/components/+catalog/columns/default-category.injectable.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import styles from "../catalog.module.scss"; +import React from "react"; +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { getInjectable } from "@ogre-tools/injectable"; +import getLabelBadgesInjectable from "../get-label-badges.injectable"; + +const defaultCategoryColumnsInjectable = getInjectable({ + id: "default-category-columns", + instantiate: (di): RegisteredAdditionalCategoryColumn[] => { + const getLabelBadges = di.inject(getLabelBadgesInjectable); + + return [ + { + id: "source", + priority: 10, + renderCell: entity => entity.getSource(), + titleProps: { + title: "Source", + className: styles.sourceCell, + id: "source", + sortBy: "source", + }, + sortCallback: entity => entity.getSource(), + searchFilter: entity => `source=${entity.getSource()}`, + }, + { + id: "labels", + priority: 20, + renderCell: getLabelBadges, + titleProps: { + id: "labels", + title: "Labels", + className: `${styles.labelsCell} scrollable`, + }, + searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels), + }, + { + id: "status", + priority: 30, + renderCell: entity => ( + + {entity.status.phase} + + ), + titleProps: { + title: "Status", + className: styles.statusCell, + id: "status", + sortBy: "status", + }, + searchFilter: entity => entity.status.phase, + sortCallback: entity => entity.status.phase, + }, + ]; + }, +}); + +export default defaultCategoryColumnsInjectable; diff --git a/src/renderer/components/+catalog/columns/get.injectable.ts b/src/renderer/components/+catalog/columns/get.injectable.ts new file mode 100644 index 0000000000..03930578c1 --- /dev/null +++ b/src/renderer/components/+catalog/columns/get.injectable.ts @@ -0,0 +1,63 @@ +/** + * 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 { orderBy } from "lodash"; +import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog"; +import type { ItemListLayoutProps } from "../../item-object-list"; +import browseAllColumnsInjectable from "./browse-all.injectable"; +import getColumnsForCategoryInjectable from "./specific-category.injectable"; + +export interface GetCategoryColumnsParams { + activeCategory: CatalogCategory | null | undefined; +} + +export type CategoryColumns = Required, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>; +export type GetCategoryColumns = (params: GetCategoryColumnsParams) => CategoryColumns; + +const getCategoryColumnsInjectable = getInjectable({ + id: "get-category-columns", + + instantiate: (di): GetCategoryColumns => { + const getColumnsForCategory = di.inject(getColumnsForCategoryInjectable); + const browseAllColumns = di.inject(browseAllColumnsInjectable); + + return ({ activeCategory }) => { + const allRegistrations = orderBy( + activeCategory + ? getColumnsForCategory(activeCategory) + : browseAllColumns, + "priority", + "asc", + ); + + const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {}; + const searchFilters: CategoryColumns["searchFilters"] = []; + const renderTableHeader: CategoryColumns["renderTableHeader"] = []; + const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = []; + + for (const registration of allRegistrations) { + if (registration.sortCallback) { + sortingCallbacks[registration.id] = registration.sortCallback; + } + + if (registration.searchFilter) { + searchFilters.push(registration.searchFilter); + } + + tableRowRenderers.push(registration.renderCell); + renderTableHeader.push(registration.titleProps); + } + + return { + sortingCallbacks, + renderTableHeader, + renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)), + searchFilters, + }; + }; + }, +}); + +export default getCategoryColumnsInjectable; diff --git a/src/renderer/components/+catalog/name-category-column.injectable.tsx b/src/renderer/components/+catalog/columns/named-category.injectable.tsx similarity index 74% rename from src/renderer/components/+catalog/name-category-column.injectable.tsx rename to src/renderer/components/+catalog/columns/named-category.injectable.tsx index 0ba9e146d1..1f782d7b6f 100644 --- a/src/renderer/components/+catalog/name-category-column.injectable.tsx +++ b/src/renderer/components/+catalog/columns/named-category.injectable.tsx @@ -3,15 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import styles from "./catalog.module.scss"; -import type { CatalogEntity } from "../../../common/catalog"; -import { prevDefault } from "../../utils"; -import { Avatar } from "../avatar"; -import { Icon } from "../icon"; +import styles from "../catalog.module.scss"; +import type { CatalogEntity } from "../../../../common/catalog"; +import { prevDefault } from "../../../utils"; +import { Avatar } from "../../avatar"; +import { Icon } from "../../icon"; import React from "react"; -import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; -import type { HotbarStore } from "../../../common/hotbars/store"; +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +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); @@ -45,8 +45,7 @@ const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) = ); }; - -const nameCategoryColumnInjectable = getInjectable({ +const namedCategoryColumnInjectable = getInjectable({ id: "name-category-column", instantiate: (di): RegisteredAdditionalCategoryColumn => ({ id: "name", @@ -63,4 +62,4 @@ const nameCategoryColumnInjectable = getInjectable({ }), }); -export default nameCategoryColumnInjectable; +export default namedCategoryColumnInjectable; diff --git a/src/renderer/components/+catalog/columns/specific-category.injectable.ts b/src/renderer/components/+catalog/columns/specific-category.injectable.ts new file mode 100644 index 0000000000..94f0508612 --- /dev/null +++ b/src/renderer/components/+catalog/columns/specific-category.injectable.ts @@ -0,0 +1,43 @@ +/** + * 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 { CatalogCategory } from "../../../api/catalog-entity"; +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +import categoryColumnsInjectable from "../custom-category-columns.injectable"; +import defaultCategoryColumnsInjectable from "./default-category.injectable"; +import namedCategoryColumnInjectable from "./named-category.injectable"; + +export type GetColumnsForCategory = (activeCategory: CatalogCategory) => RegisteredAdditionalCategoryColumn[]; + +const getColumnsForCategoryInjectable = getInjectable({ + id: "get-columns-for-category", + instantiate: (di): GetColumnsForCategory => { + const extensionColumns = di.inject(categoryColumnsInjectable); + const defaultCategoryColumns = di.inject(defaultCategoryColumnsInjectable); + const nameCategoryColumn = di.inject(namedCategoryColumnInjectable); + + return (activeCategory) => { + const fromExtensions = ( + extensionColumns + .get() + .get(activeCategory.spec.group) + ?.get(activeCategory.spec.names.kind) + ?? [] + ); + const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({ + priority, + ...column, + })) ?? defaultCategoryColumns; + + return [ + nameCategoryColumn, + ...fromExtensions, + ...fromCategory, + ]; + }; + }, +}); + +export default getColumnsForCategoryInjectable; diff --git a/src/renderer/components/+catalog/get-category-columns.injectable.ts b/src/renderer/components/+catalog/get-category-columns.injectable.ts deleted file mode 100644 index bf2cee1bb9..0000000000 --- a/src/renderer/components/+catalog/get-category-columns.injectable.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { orderBy } from "lodash"; -import type { IComputedValue } from "mobx"; -import type { CatalogCategory, CatalogEntity } from "../../../common/catalog"; -import type { ItemListLayoutProps } from "../item-object-list"; -import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns"; -import categoryColumnsInjectable from "./custom-category-columns.injectable"; -import { defaultCategoryColumns, browseAllColumns } from "./internal-category-columns"; -import nameCategoryColumnInjectable from "./name-category-column.injectable"; - -interface Dependencies { - extensionColumns: IComputedValue>>; - nameCategoryColumn: RegisteredAdditionalCategoryColumn; -} - -export interface GetCategoryColumnsParams { - activeCategory: CatalogCategory | null | undefined; -} - -export type CategoryColumns = Required, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>; - -function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue>>, nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] { - const fromExtensions = ( - extensionColumns - .get() - .get(activeCategory.spec.group) - ?.get(activeCategory.spec.names.kind) - ?? [] - ); - const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({ - priority, - ...column, - })) ?? defaultCategoryColumns; - - return [ - nameCategoryColumn, - ...fromExtensions, - ...fromCategory, - ]; -} - -function getBrowseAllColumns(nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] { - return [ - ...browseAllColumns, - nameCategoryColumn, - ...defaultCategoryColumns, - ]; -} - -const getCategoryColumns = ({ extensionColumns, nameCategoryColumn }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => { - const allRegistrations = orderBy( - activeCategory - ? getSpecificCategoryColumns(activeCategory, extensionColumns, nameCategoryColumn) - : getBrowseAllColumns(nameCategoryColumn), - "priority", - "asc", - ); - - const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {}; - const searchFilters: CategoryColumns["searchFilters"] = []; - const renderTableHeader: CategoryColumns["renderTableHeader"] = []; - const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = []; - - for (const registration of allRegistrations) { - if (registration.sortCallback) { - sortingCallbacks[registration.id] = registration.sortCallback; - } - - if (registration.searchFilter) { - searchFilters.push(registration.searchFilter); - } - - tableRowRenderers.push(registration.renderCell); - renderTableHeader.push(registration.titleProps); - } - - return { - sortingCallbacks, - renderTableHeader, - renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)), - searchFilters, - }; -}; - -const getCategoryColumnsInjectable = getInjectable({ - id: "get-category-columns", - - instantiate: (di) => getCategoryColumns({ - extensionColumns: di.inject(categoryColumnsInjectable), - nameCategoryColumn: di.inject(nameCategoryColumnInjectable), - }), -}); - -export default getCategoryColumnsInjectable; diff --git a/src/renderer/components/+catalog/get-label-badges.injectable.tsx b/src/renderer/components/+catalog/get-label-badges.injectable.tsx new file mode 100644 index 0000000000..516a192f4c --- /dev/null +++ b/src/renderer/components/+catalog/get-label-badges.injectable.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import styles from "./catalog.module.scss"; +import React from "react"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import searchUrlPageParamInjectable from "../input/search-url-page-param.injectable"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { Badge } from "../badge"; + +export type GetLabelBadges = (entity: CatalogEntity, onClick?: ((evt: React.MouseEvent) => void) | undefined) => JSX.Element[]; + +const getLabelBadgesInjectable = getInjectable({ + id: "get-label-badges", + instantiate: (di): GetLabelBadges => { + const searchUrlParam = di.inject(searchUrlPageParamInjectable); + + return (entity, onClick) => ( + KubeObject.stringifyLabels(entity.metadata.labels) + .map(label => ( + { + searchUrlParam.set(label); + onClick?.(event); + event.stopPropagation(); + }} + expandable={false} + /> + )) + ); + }, +}); + +export default getLabelBadgesInjectable; diff --git a/src/renderer/components/+catalog/helpers.tsx b/src/renderer/components/+catalog/helpers.tsx deleted file mode 100644 index 5ee2a76da5..0000000000 --- a/src/renderer/components/+catalog/helpers.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import styles from "./catalog.module.scss"; -import React from "react"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; -import type { CatalogEntity } from "../../api/catalog-entity"; -import { Badge } from "../badge"; -import { searchUrlParam } from "../input"; - -/** - * @param entity The entity to render badge labels for - */ -export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent) => void) { - return KubeObject.stringifyLabels(entity.metadata.labels) - .map(label => ( - { - searchUrlParam.set(label); - onClick?.(event); - event.stopPropagation(); - }} - expandable={false} - /> - )); -} diff --git a/src/renderer/components/+catalog/internal-category-columns.tsx b/src/renderer/components/+catalog/internal-category-columns.tsx deleted file mode 100644 index 0aba81db0b..0000000000 --- a/src/renderer/components/+catalog/internal-category-columns.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import styles from "./catalog.module.scss"; - -import React from "react"; -import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns"; -import { getLabelBadges } from "./helpers"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; - -export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [ - { - id: "kind", - priority: 5, - renderCell: entity => entity.kind, - titleProps: { - id: "kind", - sortBy: "kind", - title: "Kind", - }, - sortCallback: entity => entity.kind, - }, -]; - -export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [ - { - id: "source", - priority: 10, - renderCell: entity => entity.getSource(), - titleProps: { - title: "Source", - className: styles.sourceCell, - id: "source", - sortBy: "source", - }, - sortCallback: entity => entity.getSource(), - searchFilter: entity => `source=${entity.getSource()}`, - }, - { - id: "labels", - priority: 20, - renderCell: getLabelBadges, - titleProps: { - id: "labels", - title: "Labels", - className: `${styles.labelsCell} scrollable`, - }, - searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels), - }, - { - id: "status", - priority: 30, - renderCell: entity => ( - - {entity.status.phase} - - ), - titleProps: { - title: "Status", - className: styles.statusCell, - id: "status", - sortBy: "status", - }, - searchFilter: entity => entity.status.phase, - sortCallback: entity => entity.status.phase, - }, -]; diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index 20147fb910..f17579e843 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -17,7 +17,7 @@ import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; import type { ClusterOverviewStore } from "./cluster-overview-store/cluster-overview-store"; import { ClusterPieCharts } from "./cluster-pie-charts"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; +import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import type { EventStore } from "../+events/store"; import { withInjectables } from "@ogre-tools/injectable-react"; diff --git a/src/renderer/components/+custom-resources/crd-groups-url-param.injectable.ts b/src/renderer/components/+custom-resources/crd-groups-url-param.injectable.ts new file mode 100644 index 0000000000..a5a8d9017f --- /dev/null +++ b/src/renderer/components/+custom-resources/crd-groups-url-param.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 createPageParamInjectable from "../../navigation/create-page-param.injectable"; + +const crdGroupsUrlParamInjectable = getInjectable({ + id: "crd-groups-url-param", + instantiate: (di) => { + const createPageParam = di.inject(createPageParamInjectable); + + return createPageParam({ + name: "groups", + defaultValue: [], + }); + }, +}); + +export default crdGroupsUrlParamInjectable; diff --git a/src/renderer/components/+custom-resources/crd-list.tsx b/src/renderer/components/+custom-resources/crd-list.tsx index 385c7155a6..e0fc1b67e0 100644 --- a/src/renderer/components/+custom-resources/crd-list.tsx +++ b/src/renderer/components/+custom-resources/crd-list.tsx @@ -13,15 +13,14 @@ import { iter, stopPropagation } from "../../utils"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { customResourceDefinitionStore } from "./legacy-store"; import { Select } from "../select"; -import { createPageParam } from "../../navigation"; import { Icon } from "../icon"; import { KubeObjectAge } from "../kube-object/age"; import { TabLayout } from "../layout/tab-layout-2"; - -export const crdGroupsUrlParam = createPageParam({ - name: "groups", - defaultValue: [], -}); +import type { PageParam } from "../../navigation"; +import type { CustomResourceDefinitionStore } from "./definition.store"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import crdGroupsUrlParamInjectable from "./crd-groups-url-param.injectable"; +import customResourceDefinitionStoreInjectable from "./definition.store.injectable"; enum columnId { kind = "kind", @@ -31,25 +30,30 @@ enum columnId { age = "age", } -@observer -export class CustomResourceDefinitions extends React.Component { - private readonly selectedGroups = observable.set(crdGroupsUrlParam.get()); +interface Dependencies { + crdGroupsUrlParam: PageParam; + customResourceDefinitionStore: CustomResourceDefinitionStore; +} - constructor(props: {}) { +@observer +class NonInjectedCustomResourceDefinitions extends React.Component { + private readonly selectedGroups = observable.set(this.props.crdGroupsUrlParam.get()); + + constructor(props: Dependencies) { super(props); makeObservable(this); } @computed get items() { if (this.selectedGroups.size) { - return customResourceDefinitionStore.items.filter(item => this.selectedGroups.has(item.getGroup())); + return this.props.customResourceDefinitionStore.items.filter(item => this.selectedGroups.has(item.getGroup())); } - return customResourceDefinitionStore.items; // show all by default + return this.props.customResourceDefinitionStore.items; // show all by default } @computed get groupSelectOptions() { - return Object.keys(customResourceDefinitionStore.groups) + return Object.keys(this.props.customResourceDefinitionStore.groups) .map(group => ({ value: group, label: group, @@ -61,7 +65,7 @@ export class CustomResourceDefinitions extends React.Component { const groups = options.map(({ value }) => value); this.selectedGroups.replace(groups); - crdGroupsUrlParam.set(groups); + this.props.crdGroupsUrlParam.set(groups); }; private getPlaceholder() { @@ -159,3 +163,11 @@ export class CustomResourceDefinitions extends React.Component { ); } } + +export const CustomResourceDefinitions = withInjectables(NonInjectedCustomResourceDefinitions, { + getProps: (di, props) => ({ + ...props, + crdGroupsUrlParam: di.inject(crdGroupsUrlParamInjectable), + customResourceDefinitionStore: di.inject(customResourceDefinitionStoreInjectable), + }), +}); diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index 2194490672..366f5758f6 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -9,10 +9,9 @@ import React from "react"; import type { IComputedValue } from "mobx"; import { observable, makeObservable, computed } from "mobx"; import { observer } from "mobx-react"; -import { navigation } from "../../navigation"; import { Tabs, Tab } from "../tabs"; import type { CatalogEntity } from "../../api/catalog-entity"; -import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; +import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import { EntitySettingRegistry } from "../../../extensions/registries"; import { groupBy } from "lodash"; import { SettingLayout } from "../layout/setting-layout"; @@ -20,9 +19,14 @@ import logger from "../../../common/logger"; import { Avatar } from "../avatar"; import { withInjectables } from "@ogre-tools/injectable-react"; import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable"; +import type { ObservableHistory } from "mobx-observable-history"; +import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; +import observableHistoryInjectable from "../../navigation/observable-history.injectable"; interface Dependencies { entityId: IComputedValue; + entityRegistry: CatalogEntityRegistry; + observableHistory: ObservableHistory; } @observer @@ -33,7 +37,7 @@ class NonInjectedEntitySettings extends React.Component { super(props); makeObservable(this); - const { hash } = navigation.location; + const { hash } = this.props.observableHistory.location; if (hash) { const menuId = hash.slice(1); @@ -50,8 +54,8 @@ class NonInjectedEntitySettings extends React.Component { return this.props.entityId.get(); } - get entity(): CatalogEntity { - return catalogEntityRegistry.getById(this.entityId); + get entity() { + return this.props.entityRegistry.getById(this.entityId); } get menuItems() { @@ -70,7 +74,7 @@ class NonInjectedEntitySettings extends React.Component { this.activeTab = tabId; }; - renderNavigation() { + renderNavigation(entity: CatalogEntity) { const groups = Object.entries(groupBy(this.menuItems, (item) => item.group || "Extensions")); groups.sort((a, b) => { @@ -84,14 +88,14 @@ class NonInjectedEntitySettings extends React.Component { <>
- {this.entity.getName()} + {entity.getName()}
{ } render() { - if (!this.entity) { + const { activeSetting, entity } = this; + + if (!entity) { logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId); return null; } - const { activeSetting } = this; - return ( { @@ -138,7 +142,7 @@ class NonInjectedEntitySettings extends React.Component {

{activeSetting.title}

- +
) @@ -148,16 +152,10 @@ class NonInjectedEntitySettings extends React.Component { } } -export const EntitySettings = withInjectables( - NonInjectedEntitySettings, - - { - getProps: (di) => { - const routeParameters = di.inject(entitySettingsRouteParametersInjectable); - - return { - entityId: routeParameters.entityId, - }; - }, - }, -); +export const EntitySettings = withInjectables(NonInjectedEntitySettings, { + getProps: (di) => ({ + ...di.inject(entitySettingsRouteParametersInjectable), + entityRegistry: di.inject(catalogEntityRegistryInjectable), + observableHistory: di.inject(observableHistoryInjectable), + }), +}); diff --git a/src/renderer/components/+namespaces/namespace-details.tsx b/src/renderer/components/+namespaces/namespace-details.tsx index 55f36e757a..12bb7a4208 100644 --- a/src/renderer/components/+namespaces/namespace-details.tsx +++ b/src/renderer/components/+namespaces/namespace-details.tsx @@ -14,25 +14,33 @@ import { getMetricsForNamespace, type PodMetricData, Namespace } from "../../../ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { Link } from "react-router-dom"; import { Spinner } from "../spinner"; -import { resourceQuotaStore } from "../+config-resource-quotas/legacy-store"; import { KubeObjectMeta } from "../kube-object-meta"; -import { limitRangeStore } from "../+config-limit-ranges/legacy-store"; import { ResourceMetrics } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; -import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; import { withInjectables } from "@ogre-tools/injectable-react"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable"; +import type { ResourceQuotaStore } from "../+config-resource-quotas/store"; +import type { LimitRangeStore } from "../+config-limit-ranges/store"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; +import limitRangeStoreInjectable from "../+config-limit-ranges/store.injectable"; +import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable"; export interface NamespaceDetailsProps extends KubeObjectDetailsProps { } interface Dependencies { subscribeStores: SubscribeStores; + getActiveClusterEntity: GetActiveClusterEntity; + getDetailsUrl: GetDetailsUrl; + resourceQuotaStore: ResourceQuotaStore; + limitRangeStore: LimitRangeStore; } @observer @@ -51,8 +59,8 @@ class NonInjectedNamespaceDetails extends React.Component { @@ -74,7 +82,7 @@ class NonInjectedNamespaceDetails extends React.Component {!this.quotas && resourceQuotaStore.isLoading && } - {this.quotas.map(quota => { - return quota.selfLink && ( - - {quota.getName()} - - ); - })} + {this.quotas.map(quota => quota.selfLink && ( + + {quota.getName()} + + ))} {!this.limitranges && limitRangeStore.isLoading && } - {this.limitranges.map(limitrange => { - return limitrange.selfLink && ( - - {limitrange.getName()} - - ); - })} + {this.limitranges.map(limitrange => limitrange.selfLink && ( + + {limitrange.getName()} + + ))} ); @@ -136,6 +140,10 @@ export const NamespaceDetails = withInjectables ({ ...props, subscribeStores: di.inject(subscribeStoresInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + getDetailsUrl: di.inject(getDetailsUrlInjectable), + limitRangeStore: di.inject(limitRangeStoreInjectable), + resourceQuotaStore: di.inject(resourceQuotaStoreInjectable), }), }); diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index b82571c751..ae4c06b6d8 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -17,7 +17,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { IngressCharts } from "./ingress-charts"; import { KubeObjectMeta } from "../kube-object-meta"; import { computeRuleDeclarations, getMetricsForIngress, type IngressMetricData } from "../../../common/k8s-api/endpoints/ingress.api"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; +import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import logger from "../../../common/logger"; diff --git a/src/renderer/components/+nodes/details.tsx b/src/renderer/components/+nodes/details.tsx index 38d89acf86..3377bcb4a5 100644 --- a/src/renderer/components/+nodes/details.tsx +++ b/src/renderer/components/+nodes/details.tsx @@ -19,7 +19,6 @@ import { NodeCharts } from "./node-charts"; import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { NodeDetailsResources } from "./details-resources"; import { DrawerTitle } from "../drawer/drawer-title"; @@ -29,6 +28,8 @@ import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; export interface NodeDetailsProps extends KubeObjectDetailsProps { } @@ -36,6 +37,7 @@ export interface NodeDetailsProps extends KubeObjectDetailsProps { interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -66,7 +68,7 @@ class NonInjectedNodeDetails extends React.Component(NonIn ...props, subscribeStores: di.inject(subscribeStoresInjectable), podStore: di.inject(podStoreInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), }), }); diff --git a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx index acc310d959..cd4bd5e0a6 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -16,7 +16,7 @@ import { ResourceMetrics } from "../resource-metrics"; import { VolumeClaimDiskChart } from "./volume-claim-disk-chart"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { getMetricsForPvc, type PersistentVolumeClaimMetricData, PersistentVolumeClaim } from "../../../common/k8s-api/endpoints"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; +import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { KubeObjectMeta } from "../kube-object-meta"; import { getDetailsUrl } from "../kube-detail-params"; diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx index e4e20a6401..0cc16ff6f6 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx @@ -21,7 +21,6 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import logger from "../../../common/logger"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -29,6 +28,8 @@ import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import daemonSetStoreInjectable from "./store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; export interface DaemonSetDetailsProps extends KubeObjectDetailsProps { } @@ -37,6 +38,7 @@ interface Dependencies { subscribeStores: SubscribeStores; daemonSetStore: DaemonSetStore; podStore: PodStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -66,7 +68,7 @@ class NonInjectedDaemonSetDetails extends React.Component { } @@ -42,6 +43,7 @@ interface Dependencies { podStore: PodStore; replicaSetStore: ReplicaSetStore; deploymentStore: DeploymentStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -73,7 +75,7 @@ class NonInjectedDeploymentDetails extends React.Component { } @@ -41,6 +44,9 @@ interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; jobStore: JobStore; + getActiveClusterEntity: GetActiveClusterEntity; + getDetailsUrl: GetDetailsUrl; + apiManager: ApiManager; } @observer @@ -70,7 +76,7 @@ class NonInjectedJobDetails extends React.Component(NonInje subscribeStores: di.inject(subscribeStoresInjectable), podStore: di.inject(podStoreInjectable), jobStore: di.inject(jobStoreInjectable), + getDetailsUrl: di.inject(getDetailsUrlInjectable), + apiManager: di.inject(apiManagerInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), }), }); diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index e9d2c8653b..0b0abb8eb3 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -17,12 +17,13 @@ import { ResourceMetrics } from "../resource-metrics"; import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api"; import { ContainerCharts } from "./container-charts"; import { LocaleDate } from "../locale-date"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import type { PortForwardStore } from "../../port-forward"; import { disposeOnUnmount, observer } from "mobx-react"; import { withInjectables } from "@ogre-tools/injectable-react"; import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; export interface PodDetailsContainerProps { pod: Pod; @@ -32,6 +33,7 @@ export interface PodDetailsContainerProps { interface Dependencies { portForwardStore: PortForwardStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -82,7 +84,7 @@ class NonInjectedPodDetailsContainer extends React.Component( - NonInjectedPodDetailsContainer, - - { - getProps: (di, props) => ({ - portForwardStore: di.inject(portForwardStoreInjectable), - ...props, - }), - }, -); +export const PodDetailsContainer = withInjectables(NonInjectedPodDetailsContainer, { + getProps: (di, props) => ({ + ...props, + portForwardStore: di.inject(portForwardStoreInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + }), +}); diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index 5a4da6cefa..eb9bd2429d 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -24,7 +24,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { getItemMetrics } from "../../../common/k8s-api/endpoints/metrics.api"; import { PodCharts, podMetricTabs } from "./pod-charts"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; +import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; diff --git a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx index b4d35c1617..6f239f3dbb 100644 --- a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx +++ b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx @@ -19,7 +19,6 @@ import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import logger from "../../../common/logger"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -29,6 +28,8 @@ import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import type { ReplicaSetStore } from "./store"; import replicaSetStoreInjectable from "./store.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; export interface ReplicaSetDetailsProps extends KubeObjectDetailsProps { } @@ -37,6 +38,7 @@ interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; replicaSetStore: ReplicaSetStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -67,7 +69,7 @@ class NonInjectedReplicaSetDetails extends React.Component { } @@ -38,6 +39,7 @@ interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; statefulSetStore: StatefulSetStore; + getActiveClusterEntity: GetActiveClusterEntity; } @observer @@ -68,7 +70,7 @@ class NonInjectedStatefulSetDetails extends React.Component; } -const NonInjectedActivateEntityCommand = observer(({ closeCommandOverlay, entities }: Dependencies) => ( +const NonInjectedActivateEntityCommand = observer(({ + closeCommandOverlay, + entities, +}: Dependencies) => (