diff --git a/src/common/catalog/catalog-category-registry.ts b/src/common/catalog/catalog-category-registry.ts index ed0bddd4a8..66e69dbaab 100644 --- a/src/common/catalog/catalog-category-registry.ts +++ b/src/common/catalog/catalog-category-registry.ts @@ -4,15 +4,16 @@ */ import { action, computed, observable, makeObservable } from "mobx"; -import { Disposer, ExtendedMap, iter } from "../utils"; -import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; +import type { Disposer } from "../utils"; +import { strictSet, iter, getOrInsertMap } from "../utils"; import { once } from "lodash"; +import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; export type CategoryFilter = (category: CatalogCategory) => any; export class CatalogCategoryRegistry { protected categories = observable.set(); - protected groupKinds = new ExtendedMap>(); + protected groupKinds = new Map>(); protected filters = observable.set([], { deep: false, }); @@ -22,14 +23,14 @@ export class CatalogCategoryRegistry { } @action add(category: CatalogCategory): Disposer { + const byGroup = getOrInsertMap(this.groupKinds, category.spec.group); + this.categories.add(category); - this.groupKinds - .getOrInsert(category.spec.group, ExtendedMap.new) - .strictSet(category.spec.names.kind, category); + strictSet(byGroup, category.spec.names.kind, category); return () => { this.categories.delete(category); - this.groupKinds.get(category.spec.group).delete(category.spec.names.kind); + byGroup.delete(category.spec.names.kind); }; } diff --git a/src/common/utils/collection-functions.ts b/src/common/utils/collection-functions.ts index 267668ce81..0329a52379 100644 --- a/src/common/utils/collection-functions.ts +++ b/src/common/utils/collection-functions.ts @@ -21,8 +21,8 @@ export function getOrInsert(map: Map, key: K, value: V): V { } /** - * Like `getOrInsert` but specifically for when `V` is `Map` so that - * the typings are inferred. + * Like `getOrInsert` but specifically for when `V` is `Map` so that + * the typings are inferred correctly. */ export function getOrInsertMap(map: Map>, key: K): Map { return getOrInsert(map, key, new Map()); @@ -37,11 +37,39 @@ export function getOrInsertSet(map: Map>, key: K): Set { } /** - * Like `getOrInsert` but with delayed creation of the item + * Like `getOrInsert` but with delayed creation of the item. Which is useful + * if it is very expensive to create the initial value. */ -export function getOrInsertWith(map: Map, key: K, value: () => V): V { +export function getOrInsertWith(map: Map, key: K, builder: () => V): V { if (!map.has(key)) { - map.set(key, value()); + map.set(key, builder()); + } + + return map.get(key); +} + +/** + * Set the value associated with `key` iff there was not a previous value + * @param map The map to interact with + * @throws if `key` already in map + * @returns `this` so that `strictSet` can be chained + */ +export function strictSet(map: Map, key: K, val: V): typeof map { + if (map.has(key)) { + throw new TypeError("Duplicate key in map"); + } + + return map.set(key, val); +} + +/** + * Get the value associated with `key` + * @param map The map to interact with + * @throws if `key` did not a value associated with it + */ +export function strictGet(map: Map, key: K): V { + if (!map.has(key)) { + throw new TypeError("key not in map"); } return map.get(key); diff --git a/src/common/utils/extended-map.ts b/src/common/utils/extended-map.ts deleted file mode 100644 index 5db6bffec1..0000000000 --- a/src/common/utils/extended-map.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { action, ObservableMap, runInAction } from "mobx"; - -export function multiSet(map: Map, newEntries: [T, V][]): void { - runInAction(() => { - for (const [key, val] of newEntries) { - map.set(key, val); - } - }); -} - -export class ExtendedMap extends Map { - static new(entries?: readonly (readonly [K, V])[] | null): ExtendedMap { - return new ExtendedMap(entries); - } - - /** - * Get the value behind `key`. If it was not present, first insert the value returned by `getVal` - * @param key The key to insert into the map with - * @param getVal A function that returns a new instance of `V`. - * @returns The value in the map - */ - getOrInsert(key: K, getVal: () => V): V { - if (this.has(key)) { - return this.get(key); - } - - return this.set(key, getVal()).get(key); - } - - /** - * Set the value associated with `key` iff there was not a previous value - * @throws if `key` already in map - * @returns `this` so that `strictSet` can be chained - */ - strictSet(key: K, val: V): this { - if (this.has(key)) { - throw new TypeError("Duplicate key in map"); - } - - return this.set(key, val); - } - - /** - * Get the value associated with `key` - * @throws if `key` did not a value associated with it - */ - strictGet(key: K): V { - if (!this.has(key)) { - throw new TypeError("key not in map"); - } - - return this.get(key); - } -} - -export class ExtendedObservableMap extends ObservableMap { - @action - getOrInsert(key: K, getVal: () => V): V { - if (this.has(key)) { - return this.get(key); - } - - return this.set(key, getVal()).get(key); - } -} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index d653214c68..8b213228f0 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -25,7 +25,6 @@ export * from "./delay"; export * from "./disposer"; export * from "./downloadFile"; export * from "./escapeRegExp"; -export * from "./extended-map"; export * from "./formatDuration"; export * from "./getRandId"; export * from "./hash-set"; diff --git a/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts b/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts index 9f5b5834af..52b73cc541 100644 --- a/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts +++ b/src/main/catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.ts @@ -10,7 +10,7 @@ import { FSWatcher, watch } from "chokidar"; import fs from "fs"; import path from "path"; import type stream from "stream"; -import { bytesToUnits, Disposer, ExtendedObservableMap, iter, noop } from "../../../common/utils"; +import { bytesToUnits, Disposer, getOrInsertWith, iter, noop } from "../../../common/utils"; import logger from "../../logger"; import type { KubeConfig } from "@kubernetes/client-node"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; @@ -294,7 +294,7 @@ const diffChangedConfigFor = (dependencies: Dependencies) => ({ filePath, source }; const watchFileChanges = (filePath: string, dependencies: Dependencies): [IComputedValue, Disposer] => { - const rootSource = new ExtendedObservableMap>(); + const rootSource = observable.map>(); const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1])))); let watcher: FSWatcher; @@ -335,7 +335,7 @@ const watchFileChanges = (filePath: string, dependencies: Dependencies): [ICompu cleanup(); cleanupFns.set(childFilePath, diffChangedConfig({ filePath: childFilePath, - source: rootSource.getOrInsert(childFilePath, observable.map), + source: getOrInsertWith(rootSource, childFilePath, observable.map), stats, maxAllowedFileReadSize, })); @@ -353,7 +353,7 @@ const watchFileChanges = (filePath: string, dependencies: Dependencies): [ICompu cleanupFns.set(childFilePath, diffChangedConfig({ filePath: childFilePath, - source: rootSource.getOrInsert(childFilePath, observable.map), + source: getOrInsertWith(rootSource, childFilePath, observable.map), stats, maxAllowedFileReadSize, })); diff --git a/src/main/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts b/src/main/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts index d1d6303e32..bafd073c14 100644 --- a/src/main/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts +++ b/src/main/proxy-functions/shell-api-request/shell-request-authenticator/shell-request-authenticator.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { ExtendedMap } from "../../../../common/utils"; +import { getOrInsertMap } from "../../../../common/utils"; import type { ClusterId } from "../../../../common/cluster-types"; import { ipcMainHandle } from "../../../../common/ipc"; import crypto from "crypto"; @@ -11,15 +11,14 @@ import { promisify } from "util"; const randomBytes = promisify(crypto.randomBytes); export class ShellRequestAuthenticator { - private tokens = new ExtendedMap>(); + private tokens = new Map>(); init() { ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => { const authToken = Uint8Array.from(await randomBytes(128)); + const forCluster = getOrInsertMap(this.tokens, clusterId); - this.tokens - .getOrInsert(clusterId, () => new Map()) - .set(tabId, authToken); + forCluster.set(tabId, authToken); return authToken; }); diff --git a/src/migrations/helpers.ts b/src/migrations/helpers.ts index 799b670fb5..4e97f7f681 100644 --- a/src/migrations/helpers.ts +++ b/src/migrations/helpers.ts @@ -5,7 +5,7 @@ import type Conf from "conf"; import type { Migrations } from "conf/dist/source/types"; -import { ExtendedMap, iter } from "../common/utils"; +import { getOrInsert, iter } from "../common/utils"; import { isTestEnv } from "../common/vars"; export function migrationLog(...args: any[]) { @@ -20,10 +20,10 @@ export interface MigrationDeclaration { } export function joinMigrations(...declarations: MigrationDeclaration[]): Migrations { - const migrations = new ExtendedMap) => void)[]>(); + const migrations = new Map) => void)[]>(); for (const decl of declarations) { - migrations.getOrInsert(decl.version, () => []).push(decl.run); + getOrInsert(migrations, decl.version, []).push(decl.run); } return Object.fromEntries( diff --git a/src/renderer/components/+config-secrets/secret-details.tsx b/src/renderer/components/+config-secrets/secret-details.tsx index fce67d0abc..bce3022450 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -27,7 +27,7 @@ interface Props extends KubeObjectDetailsProps { export class SecretDetails extends React.Component { @observable isSaving = false; @observable data: { [name: string]: string } = {}; - revealSecret = new Set(); + @observable revealSecret = observable.set(); constructor(props: Props) { super(props); diff --git a/src/renderer/components/+preferences/kubeconfig-syncs.tsx b/src/renderer/components/+preferences/kubeconfig-syncs.tsx index bbe4438226..b153d8fe28 100644 --- a/src/renderer/components/+preferences/kubeconfig-syncs.tsx +++ b/src/renderer/components/+preferences/kubeconfig-syncs.tsx @@ -11,7 +11,7 @@ import { Notice } from "../+extensions/notice"; import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store"; import { isWindows } from "../../../common/vars"; import logger from "../../../main/logger"; -import { iter, multiSet } from "../../utils"; +import { iter } from "../../utils"; import { SubTitle } from "../layout/sub-title"; import { PathPicker } from "../path-picker/path-picker"; import { Spinner } from "../spinner"; @@ -93,7 +93,9 @@ export class KubeconfigSyncs extends React.Component { return Array.from(this.syncs.entries(), ([filePath, value]) => ({ filePath, ...value })); } - onPick = async (filePaths: string[]) => multiSet(this.syncs, await getAllEntries(filePaths)); + onPick = async (filePaths: string[]) => { + this.syncs.merge(await getAllEntries(filePaths)); + }; getIconName(entry: Entry) { switch (entry.info.type) { diff --git a/src/renderer/initializers/catalog-category-registry.tsx b/src/renderer/initializers/catalog-category-registry.tsx index 577a3ecaed..fc41f31f36 100644 --- a/src/renderer/initializers/catalog-category-registry.tsx +++ b/src/renderer/initializers/catalog-category-registry.tsx @@ -6,21 +6,15 @@ import React from "react"; import { kubernetesClusterCategory } from "../../common/catalog-entities"; import { addClusterURL, kubernetesURL } from "../../common/routes"; -import { multiSet } from "../utils"; import { UserStore } from "../../common/user-store"; import { getAllEntries } from "../components/+preferences/kubeconfig-syncs"; -import { runInAction } from "mobx"; import { isLinux, isWindows } from "../../common/vars"; import { PathPicker } from "../components/path-picker"; import { Notifications } from "../components/notifications"; import { Link } from "react-router-dom"; async function addSyncEntries(filePaths: string[]) { - const entries = await getAllEntries(filePaths); - - runInAction(() => { - multiSet(UserStore.getInstance().syncKubeconfigEntries, entries); - }); + UserStore.getInstance().syncKubeconfigEntries.merge(await getAllEntries(filePaths)); Notifications.ok(