From c8e2f7ca2df054d9bba7ed7f4e81d8f3cb762358 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 31 May 2021 08:09:16 +0300 Subject: [PATCH 01/31] Fix main->renderer catalog entity sync issue (#2902) * fix main->renderer catalog entity sync issue Signed-off-by: Jari Kolehmainen * refactor Signed-off-by: Jari Kolehmainen --- src/main/catalog-pusher.ts | 9 ++- .../__tests__/catalog-entity-registry.test.ts | 77 ++++++++++++++++++- src/renderer/api/catalog-entity-registry.ts | 70 ++++++++++++++--- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/src/main/catalog-pusher.ts b/src/main/catalog-pusher.ts index e97dc07038..5c9a41b702 100644 --- a/src/main/catalog-pusher.ts +++ b/src/main/catalog-pusher.ts @@ -24,10 +24,17 @@ import { broadcastMessage } from "../common/ipc"; import type { CatalogEntityRegistry } from "./catalog"; import "../common/catalog-entities/kubernetes-cluster"; import { toJS } from "../common/utils"; +import { debounce } from "lodash"; +import type { CatalogEntity } from "../common/catalog"; + + +const broadcaster = debounce((items: CatalogEntity[]) => { + broadcastMessage("catalog:items", items); +}, 1_000, { trailing: true }); export function pushCatalogToRenderer(catalog: CatalogEntityRegistry) { return reaction(() => toJS(catalog.items), (items) => { - broadcastMessage("catalog:items", items); + broadcaster(items); }, { fireImmediately: true, }); diff --git a/src/renderer/api/__tests__/catalog-entity-registry.test.ts b/src/renderer/api/__tests__/catalog-entity-registry.test.ts index 8a26aabc00..4c3bcb48c3 100644 --- a/src/renderer/api/__tests__/catalog-entity-registry.test.ts +++ b/src/renderer/api/__tests__/catalog-entity-registry.test.ts @@ -22,14 +22,36 @@ import { CatalogEntityRegistry } from "../catalog-entity-registry"; import "../../../common/catalog-entities"; import { catalogCategoryRegistry } from "../../../common/catalog/catalog-category-registry"; -import type { CatalogEntityData, CatalogEntityKindData } from "../catalog-entity"; +import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "../catalog-entity"; +import { WebLink } from "../../../common/catalog-entities"; class TestCatalogEntityRegistry extends CatalogEntityRegistry { replaceItems(items: Array) { - this.rawItems.replace(items); + this.updateItems(items); } } +class FooBarCategory extends CatalogCategory { + public readonly apiVersion = "catalog.k8slens.dev/v1alpha1"; + public readonly kind = "CatalogCategory"; + public metadata = { + name: "FooBars", + icon: "broken" + }; + public spec = { + group: "entity.k8slens.dev", + versions: [ + { + name: "v1alpha1", + entityClass: WebLink + } + ], + names: { + kind: "FooBar" + } + }; +} + describe("CatalogEntityRegistry", () => { describe("updateItems", () => { it("adds new catalog item", () => { @@ -99,6 +121,32 @@ describe("CatalogEntityRegistry", () => { expect(catalog.items[0].status.phase).toEqual("connected"); }); + it("updates activeEntity", () => { + const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); + const items = [{ + apiVersion: "entity.k8slens.dev/v1alpha1", + kind: "KubernetesCluster", + metadata: { + uid: "123", + name: "foobar", + source: "test", + labels: {} + }, + status: { + phase: "disconnected" + }, + spec: {} + }]; + + catalog.replaceItems(items); + catalog.activeEntity = catalog.items[0]; + expect(catalog.activeEntity.status.phase).toEqual("disconnected"); + + items[0].status.phase = "connected"; + catalog.replaceItems(items); + expect(catalog.activeEntity.status.phase).toEqual("connected"); + }); + it("removes deleted items", () => { const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); const items = [ @@ -175,8 +223,31 @@ describe("CatalogEntityRegistry", () => { ]; catalog.replaceItems(items); - expect(catalog.items.length).toBe(1); }); }); + + it("does return items after matching category is added", () => { + const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry); + const items = [ + { + apiVersion: "entity.k8slens.dev/v1alpha1", + kind: "FooBar", + metadata: { + uid: "456", + name: "barbaz", + source: "test", + labels: {} + }, + status: { + phase: "disconnected" + }, + spec: {} + } + ]; + + catalog.replaceItems(items); + catalogCategoryRegistry.add(new FooBarCategory()); + expect(catalog.items.length).toBe(1); + }); }); diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index e2f83058b8..0a30db31a2 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -19,17 +19,21 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { computed, makeObservable, observable } from "mobx"; +import { computed, observable, makeObservable, action } from "mobx"; import { subscribeToBroadcast } from "../../common/ipc"; import { CatalogCategory, catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../../common/catalog"; import "../../common/catalog-entities"; -import { iter } from "../utils"; import type { Cluster } from "../../main/cluster"; import { ClusterStore } from "../../common/cluster-store"; export class CatalogEntityRegistry { - protected rawItems = observable.array(); - @observable.ref activeEntity?: CatalogEntity; + @observable.ref activeEntity: CatalogEntity; + protected _entities = observable.map([], { deep: true }); + + /** + * Buffer for keeping entities that don't yet have CatalogCategory synced + */ + protected rawEntities: (CatalogEntityData & CatalogEntityKindData)[] = []; constructor(private categoryRegistry: CatalogCategoryRegistry) { makeObservable(this); @@ -37,20 +41,68 @@ export class CatalogEntityRegistry { init() { subscribeToBroadcast("catalog:items", (ev, items: (CatalogEntityData & CatalogEntityKindData)[]) => { - this.rawItems.replace(items); + this.updateItems(items); }); } + @action updateItems(items: (CatalogEntityData & CatalogEntityKindData)[]) { + this.rawEntities.length = 0; + + const newIds = new Set(items.map((item) => item.metadata.uid)); + + for (const uid of this._entities.keys()) { + if (!newIds.has(uid)) { + this._entities.delete(uid); + } + } + + for (const item of items) { + this.updateItem(item); + } + } + + @action protected updateItem(item: (CatalogEntityData & CatalogEntityKindData)) { + const existing = this._entities.get(item.metadata.uid); + + if (!existing) { + const entity = this.categoryRegistry.getEntityForData(item); + + if (entity) { + this._entities.set(entity.metadata.uid, entity); + } else { + this.rawEntities.push(item); + } + } else { + existing.metadata = item.metadata; + existing.spec = item.spec; + existing.status = item.status; + } + } + + protected processRawEntities() { + const items = [...this.rawEntities]; + + this.rawEntities.length = 0; + + for (const item of items) { + this.updateItem(item); + } + } + @computed get items() { - return Array.from(iter.filterMap(this.rawItems, rawItem => this.categoryRegistry.getEntityForData(rawItem))); + this.processRawEntities(); + + return Array.from(this._entities.values()); } @computed get entities(): Map { - return new Map(this.items.map(item => [item.metadata.uid, item])); + this.processRawEntities(); + + return this._entities; } - getById(id: string) { - return this.entities.get(id); + getById(id: string) { + return this.entities.get(id) as T; } getItemsForApiKind(apiVersion: string, kind: string): T[] { From cd96842750d1b4e674c5358fb21c5a013f26e47e Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 31 May 2021 08:09:57 +0300 Subject: [PATCH 02/31] Typed catalog category events (#2812) * typesafe catalog category events Signed-off-by: Jari Kolehmainen * use emit Signed-off-by: Jari Kolehmainen * rename events Signed-off-by: Jari Kolehmainen --- package.json | 1 + src/common/catalog-entities/kubernetes-cluster.ts | 2 +- src/common/catalog/catalog-entity.ts | 11 +++++++++-- .../components/+catalog/catalog-add-button.tsx | 2 +- .../components/+catalog/catalog-entity.store.ts | 11 +++++++++-- yarn.lock | 5 +++++ 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 25efb9f650..824cceb505 100644 --- a/package.json +++ b/package.json @@ -361,6 +361,7 @@ "ts-loader": "^7.0.5", "ts-node": "^8.10.2", "type-fest": "^1.0.2", + "typed-emitter": "^1.3.1", "typedoc": "0.17.0-3", "typedoc-plugin-markdown": "^2.4.0", "typeface-roboto": "^0.0.75", diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 9375bb06e4..dc6d36d023 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -165,7 +165,7 @@ export class KubernetesClusterCategory extends CatalogCategory { constructor() { super(); - this.on("onCatalogAddMenu", (ctx: CatalogEntityAddMenuContext) => { + this.on("catalogAddMenu", (ctx: CatalogEntityAddMenuContext) => { ctx.menuItems.push({ icon: "text_snippet", title: "Add from kubeconfig", diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index f30a392464..afc15d4c64 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -19,7 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { EventEmitter } from "events"; +import EventEmitter from "events"; +import type TypedEmitter from "typed-emitter"; import { observable, makeObservable } from "mobx"; type ExtractEntityMetadataType = Entity extends CatalogEntity ? Metadata : never; @@ -47,7 +48,13 @@ export interface CatalogCategorySpec { }; } -export abstract class CatalogCategory extends EventEmitter { +export interface CatalogCategoryEvents { + load: () => void; + catalogAddMenu: (context: CatalogEntityAddMenuContext) => void; + contextMenuOpen: (entity: CatalogEntity, context: CatalogEntityContextMenuContext) => void; +} + +export abstract class CatalogCategory extends (EventEmitter as new () => TypedEmitter) { abstract readonly apiVersion: string; abstract readonly kind: string; abstract metadata: { diff --git a/src/renderer/components/+catalog/catalog-add-button.tsx b/src/renderer/components/+catalog/catalog-add-button.tsx index c839f8dc26..25cd43239f 100644 --- a/src/renderer/components/+catalog/catalog-add-button.tsx +++ b/src/renderer/components/+catalog/catalog-add-button.tsx @@ -55,7 +55,7 @@ export class CatalogAddButton extends React.Component { menuItems: this.menuItems }; - category.emit("onCatalogAddMenu", context); + category.emit("catalogAddMenu", context); } }, { fireImmediately: true }) ]); diff --git a/src/renderer/components/+catalog/catalog-entity.store.ts b/src/renderer/components/+catalog/catalog-entity.store.ts index c681338bf9..c11374ede2 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.ts +++ b/src/renderer/components/+catalog/catalog-entity.store.ts @@ -23,9 +23,8 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity"; import { ItemObject, ItemStore } from "../../item.store"; -import { CatalogCategory } from "../../../common/catalog"; +import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog"; import { autoBind } from "../../../common/utils"; - export class CatalogEntityItem implements ItemObject { constructor(public entity: CatalogEntity) {} @@ -111,6 +110,14 @@ export class CatalogEntityStore extends ItemStore { } loadAll() { + if (this.activeCategory) { + this.activeCategory.emit("load"); + } else { + for (const category of catalogCategoryRegistry.items) { + category.emit("load"); + } + } + return this.loadItems(() => this.entities); } } diff --git a/yarn.lock b/yarn.lock index 871ab6fdb3..d19d8b3208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14413,6 +14413,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-emitter@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-1.3.1.tgz#c98d71551a99d5f08ba9085ee44b8fc9b2357502" + integrity sha512-2h7utWyXgd2R2u2IuL8B4yu1gqMxbgUj2VS/MGVbFhEVQNJKXoQQoS5CBMh+eW31zFeSmDfEQ3qQf4xy5SlPVQ== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" From 18e38223d4819c0ab7ef68abf0b5c39dbe529922 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 31 May 2021 15:36:24 +0300 Subject: [PATCH 03/31] Use in-tree extension tgz package only in production builds (#2915) Signed-off-by: Jari Kolehmainen --- src/extensions/extension-discovery.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index 8a0929d016..829e8a617f 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -34,6 +34,7 @@ import { extensionInstaller, PackageJson } from "./extension-installer"; import { ExtensionsStore } from "./extensions-store"; import { ExtensionLoader } from "./extension-loader"; import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"; +import { isProduction } from "../common/vars"; export interface InstalledExtension { id: LensExtensionId; @@ -353,7 +354,7 @@ export class ExtensionDiscovery extends Singleton { const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath); const extensionDir = path.dirname(manifestPath); const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); - const absolutePath = (await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; + const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; return { id: installedManifestPath, From e8233f4aeeeffa6a1c3185a406afe17052d7288d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 09:41:21 -0400 Subject: [PATCH 04/31] Bump dns-packet from 1.3.1 to 1.3.4 (#2901) Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4. - [Release notes](https://github.com/mafintosh/dns-packet/releases) - [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md) - [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index d19d8b3208..e0af3a94b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4900,9 +4900,9 @@ dns-equal@^1.0.0: integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" From 2c0e8b651090b89436e719fd34f39a1a86cad99e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 09:50:49 -0400 Subject: [PATCH 05/31] Bump file-loader from 6.0.0 to 6.2.0 (#2621) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 35 +++++++++++++++-------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 824cceb505..2c299ebd2d 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "eslint-plugin-react": "^7.21.5", "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-unused-imports": "^1.0.1", - "file-loader": "^6.0.0", + "file-loader": "^6.2.0", "flex.box": "^3.4.4", "fork-ts-checker-webpack-plugin": "^5.0.0", "hoist-non-react-statics": "^3.3.2", diff --git a/yarn.lock b/yarn.lock index e0af3a94b5..ecbb51f2a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1401,12 +1401,7 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== -"@types/json-schema@^7.0.4": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" - integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== - -"@types/json-schema@^7.0.5": +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -2333,7 +2328,7 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.4: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -6044,13 +6039,13 @@ file-js@0.3.0: minimatch "^3.0.3" proper-lockfile "^1.2.0" -file-loader@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== dependencies: loader-utils "^2.0.0" - schema-utils "^2.6.5" + schema-utils "^3.0.0" file-uri-to-path@1.0.0: version "1.0.0" @@ -12834,7 +12829,7 @@ schema-utils@1.0.0, schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.1, schema-utils@^2.7.0: +schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.7.0: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -12843,14 +12838,14 @@ schema-utils@^2.6.1, schema-utils@^2.7.0: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^2.6.5: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" scss-tokenizer@^0.2.3: version "0.2.3" From 780cf4ec004970f072346b08ab8426231a9424dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 09:50:56 -0400 Subject: [PATCH 06/31] Bump ws from 7.4.0 to 7.4.6 in /extensions/node-menu (#2907) Bumps [ws](https://github.com/websockets/ws) from 7.4.0 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.4.0...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/node-menu/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/node-menu/package-lock.json b/extensions/node-menu/package-lock.json index 070fca27d6..245b548fdc 100644 --- a/extensions/node-menu/package-lock.json +++ b/extensions/node-menu/package-lock.json @@ -7534,9 +7534,9 @@ } }, "ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true }, "xml-name-validator": { From 7280adce9dbf56ca0dcfce48d972913f6b0d5432 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 09:51:03 -0400 Subject: [PATCH 07/31] Bump ws from 7.4.0 to 7.4.6 in /extensions/pod-menu (#2906) Bumps [ws](https://github.com/websockets/ws) from 7.4.0 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.4.0...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/pod-menu/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/pod-menu/package-lock.json b/extensions/pod-menu/package-lock.json index dc05608117..e0cc8c93c0 100644 --- a/extensions/pod-menu/package-lock.json +++ b/extensions/pod-menu/package-lock.json @@ -7487,9 +7487,9 @@ } }, "ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true }, "xml-name-validator": { From b6555e71a91db1d9226cd929628dff7d13a00296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 09:51:19 -0400 Subject: [PATCH 08/31] Bump ws from 7.3.0 to 7.4.6 (#2905) Bumps [ws](https://github.com/websockets/ws) from 7.3.0 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.3.0...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2c299ebd2d..14a910a416 100644 --- a/package.json +++ b/package.json @@ -239,7 +239,7 @@ "win-ca": "^3.2.0", "winston": "^3.2.1", "winston-transport-browserconsole": "^1.0.5", - "ws": "^7.3.0" + "ws": "^7.4.6" }, "devDependencies": { "@emeraldpay/hashicon-react": "^0.4.0", diff --git a/yarn.lock b/yarn.lock index ecbb51f2a9..37b5240b14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15330,10 +15330,10 @@ ws@^6.1.0, ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.3, ws@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" - integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== +ws@^7.2.3, ws@^7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xdg-basedir@^3.0.0: version "3.0.0" From 59fb722508d936f993c6c55c21902ebc5400d0fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 10:03:15 -0400 Subject: [PATCH 09/31] Bump ws from 7.4.5 to 7.4.6 in /extensions/metrics-cluster-feature (#2908) Bumps [ws](https://github.com/websockets/ws) from 7.4.5 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.4.5...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/metrics-cluster-feature/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/metrics-cluster-feature/package-lock.json b/extensions/metrics-cluster-feature/package-lock.json index 1e8d9cad01..f6a43b3f72 100644 --- a/extensions/metrics-cluster-feature/package-lock.json +++ b/extensions/metrics-cluster-feature/package-lock.json @@ -7551,9 +7551,9 @@ } }, "ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true }, "xml-name-validator": { From 79f251b4d74b1cc22e59055c4d03dc6086ff7a54 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 31 May 2021 21:22:52 +0300 Subject: [PATCH 10/31] Add homepage to package.json (#2920) Signed-off-by: Jari Kolehmainen --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 14a910a416..200f45defe 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "open-lens", "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", + "homepage": "https://github.com/lensapp/lens", "version": "5.0.0-beta.6", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", From 36e8888ecb5de9fc8551669596fb975916d451c1 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 31 May 2021 16:43:08 -0400 Subject: [PATCH 11/31] Improve loading animation when switching release details (#2367) Signed-off-by: Sebastian Malton --- src/common/utils/buildUrl.ts | 8 ++ src/main/helm/helm-release-manager.ts | 10 ++- src/main/helm/helm-service.ts | 12 ++- src/main/routes/helm-route.ts | 8 +- src/main/routes/utils/index.ts | 21 +++++ src/main/routes/utils/parse-query.ts | 34 ++++++++ .../api/endpoints/helm-releases.api.ts | 41 +++++---- .../+apps-releases/release-details.tsx | 84 ++++++++++--------- .../components/ace-editor/ace-editor.scss | 15 +++- .../components/ace-editor/ace-editor.tsx | 3 +- 10 files changed, 166 insertions(+), 70 deletions(-) create mode 100644 src/main/routes/utils/index.ts create mode 100644 src/main/routes/utils/parse-query.ts diff --git a/src/common/utils/buildUrl.ts b/src/common/utils/buildUrl.ts index d373aa634d..8ad88945df 100644 --- a/src/common/utils/buildUrl.ts +++ b/src/common/utils/buildUrl.ts @@ -41,3 +41,11 @@ export function buildURL

(path: str return parts.filter(Boolean).join(""); }; } + +export function buildURLPositional

(path: string | any) { + const builder = buildURL(path); + + return function(params?: P, query?: Q, fragment?: string): string { + return builder({ params, query, fragment }); + }; +} diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 852c649ede..492474c151 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -127,10 +127,16 @@ export async function deleteRelease(name: string, namespace: string, pathToKubec } } -export async function getValues(name: string, namespace: string, all: boolean, pathToKubeconfig: string) { +interface GetValuesOptions { + namespace: string; + all?: boolean; + pathToKubeconfig: string; +} + +export async function getValues(name: string, { namespace, all = false, pathToKubeconfig }: GetValuesOptions) { try { const helm = await helmCli.binaryPath(); - const { stdout, } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all": ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); + const { stdout } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all" : ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return stdout; } catch ({ stderr }) { diff --git a/src/main/helm/helm-service.ts b/src/main/helm/helm-service.ts index 73e93e5ac1..01262b6ef8 100644 --- a/src/main/helm/helm-service.ts +++ b/src/main/helm/helm-service.ts @@ -27,6 +27,12 @@ import { HelmChartManager } from "./helm-chart-manager"; import type { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api"; import { deleteRelease, getHistory, getRelease, getValues, installChart, listReleases, rollback, upgradeRelease } from "./helm-release-manager"; +interface GetReleaseValuesArgs { + cluster: Cluster; + namespace: string; + all: boolean; +} + class HelmService { public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) { const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); @@ -86,12 +92,12 @@ class HelmService { return getRelease(releaseName, namespace, cluster); } - public async getReleaseValues(cluster: Cluster, releaseName: string, namespace: string, all: boolean) { - const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); + public async getReleaseValues(releaseName: string, { cluster, namespace, all }: GetReleaseValuesArgs) { + const pathToKubeconfig = await cluster.getProxyKubeconfigPath(); logger.debug("Fetch release values"); - return getValues(releaseName, namespace, all, proxyKubeconfig); + return getValues(releaseName, { namespace, all, pathToKubeconfig }); } public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) { diff --git a/src/main/routes/helm-route.ts b/src/main/routes/helm-route.ts index 7a4033d4f0..5e104f6a11 100644 --- a/src/main/routes/helm-route.ts +++ b/src/main/routes/helm-route.ts @@ -21,8 +21,9 @@ import type { LensApiRequest } from "../router"; import { helmService } from "../helm/helm-service"; -import { respondJson, respondText } from "../utils/http-responses"; import logger from "../logger"; +import { respondJson, respondText } from "../utils/http-responses"; +import { getBoolean } from "./utils/parse-query"; export class HelmApiRoute { static async listCharts(request: LensApiRequest) { @@ -122,10 +123,11 @@ export class HelmApiRoute { } static async getReleaseValues(request: LensApiRequest) { - const { cluster, params, response, query } = request; + const { cluster, params: { namespace, release }, response, query } = request; + const all = getBoolean(query, "all"); try { - const result = await helmService.getReleaseValues(cluster, params.release, params.namespace, query.has("all")); + const result = await helmService.getReleaseValues(release, { cluster, namespace, all }); respondText(response, result); } catch (error) { diff --git a/src/main/routes/utils/index.ts b/src/main/routes/utils/index.ts new file mode 100644 index 0000000000..4a7de80225 --- /dev/null +++ b/src/main/routes/utils/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +export * from "./parse-query"; diff --git a/src/main/routes/utils/parse-query.ts b/src/main/routes/utils/parse-query.ts new file mode 100644 index 0000000000..b426f40c28 --- /dev/null +++ b/src/main/routes/utils/parse-query.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +export function getBoolean(query: URLSearchParams, key: string): boolean { + const value = query.get(key); + + switch (value?.toLowerCase()) { + case "false": + case "f": + case "0": + case null: + case undefined: + return false; + default: + return true; + } +} diff --git a/src/renderer/api/endpoints/helm-releases.api.ts b/src/renderer/api/endpoints/helm-releases.api.ts index 0e62027dbe..1eb5356465 100644 --- a/src/renderer/api/endpoints/helm-releases.api.ts +++ b/src/renderer/api/endpoints/helm-releases.api.ts @@ -20,7 +20,6 @@ */ import jsYaml from "js-yaml"; -import { compile } from "path-to-regexp"; import { autoBind, formatDuration } from "../../utils"; import capitalize from "lodash/capitalize"; import { apiBase } from "../index"; @@ -28,6 +27,7 @@ import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.st import type { ItemObject } from "../../item.store"; import { KubeObject } from "../kube-object"; import type { JsonApiData } from "../json-api"; +import { buildURLPositional } from "../../../common/utils/buildUrl"; interface IReleasePayload { name: string; @@ -83,12 +83,16 @@ export interface IReleaseRevision { description: string; } -const endpoint = compile(`/v2/releases/:namespace?/:name?`) as ( - params?: { - namespace?: string; - name?: string; - } -) => string; +type EndpointParams = {} + | { namespace: string } + | { namespace: string, name: string } + | { namespace: string, name: string, route: string }; + +interface EndpointQuery { + all?: boolean; +} + +const endpoint = buildURLPositional("/v2/releases/:namespace?/:name?/:route?"); export async function listReleases(namespace?: string): Promise { const releases = await apiBase.get(endpoint({ namespace })); @@ -134,25 +138,25 @@ export async function deleteRelease(name: string, namespace: string): Promise { - const path = `${endpoint({ name, namespace })}/values${all? "?all": ""}`; + const route = "values"; + const path = endpoint({ name, namespace, route }, { all }); return apiBase.get(path); } export async function getReleaseHistory(name: string, namespace: string): Promise { - const path = `${endpoint({ name, namespace })}/history`; + const route = "history"; + const path = endpoint({ name, namespace, route }); return apiBase.get(path); } export async function rollbackRelease(name: string, namespace: string, revision: number): Promise { - const path = `${endpoint({ name, namespace })}/rollback`; + const route = "rollback"; + const path = endpoint({ name, namespace, route }); + const data = { revision }; - return apiBase.put(path, { - data: { - revision - } - }); + return apiBase.put(path, { data }); } export interface HelmRelease { @@ -210,12 +214,7 @@ export class HelmRelease implements ItemObject { getVersion() { const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/); - if (versions) { - return versions[0]; - } - else { - return ""; - } + return versions?.[0] ?? ""; } getUpdated(humanize = true, compact = true) { diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx index 2e7eca5e1a..d7b6b76279 100644 --- a/src/renderer/components/+apps-releases/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details.tsx @@ -58,32 +58,35 @@ export class ReleaseDetails extends Component { @observable details: IReleaseDetails; @observable values = ""; @observable valuesLoading = false; - @observable userSuppliedOnly = false; + @observable showOnlyUserSuppliedValues = false; @observable saving = false; @observable releaseSecret: Secret; - @disposeOnUnmount - releaseSelector = reaction(() => this.props.release, release => { - if (!release) return; - this.loadDetails(); - this.loadValues(); - this.releaseSecret = null; + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.release, release => { + if (!release) return; + this.loadDetails(); + this.loadValues(); + this.releaseSecret = null; + }), + reaction(() => secretsStore.getItems(), () => { + if (!this.props.release) return; + const { getReleaseSecret } = releaseStore; + const { release } = this.props; + const secret = getReleaseSecret(release); + + if (this.releaseSecret) { + if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) return; + this.loadDetails(); + } + this.releaseSecret = secret; + }), + reaction(() => this.showOnlyUserSuppliedValues, () => { + this.loadValues(); + }), + ]); } - ); - - @disposeOnUnmount - secretWatcher = reaction(() => secretsStore.getItems(), () => { - if (!this.props.release) return; - const { getReleaseSecret } = releaseStore; - const { release } = this.props; - const secret = getReleaseSecret(release); - - if (this.releaseSecret) { - if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) return; - this.loadDetails(); - } - this.releaseSecret = secret; - }); constructor(props: Props) { super(props); @@ -100,10 +103,15 @@ export class ReleaseDetails extends Component { async loadValues() { const { release } = this.props; - this.values = ""; - this.valuesLoading = true; - this.values = (await getReleaseValues(release.getName(), release.getNs(), !this.userSuppliedOnly)) ?? ""; - this.valuesLoading = false; + try { + this.valuesLoading = true; + this.values = (await getReleaseValues(release.getName(), release.getNs(), !this.showOnlyUserSuppliedValues)) ?? ""; + } catch (error) { + Notifications.error(`Failed to load values for ${release.getName()}: ${error}`); + this.values = ""; + } finally { + this.valuesLoading = false; + } } updateValues = async () => { @@ -146,21 +154,19 @@ export class ReleaseDetails extends Component {

{ - this.userSuppliedOnly = values; - this.loadValues(); - }} + value={this.showOnlyUserSuppliedValues} + onChange={value => this.showOnlyUserSuppliedValues = value} disabled={valuesLoading} /> - {valuesLoading - ? - : this.values = values} - /> - } + this.values = text} + className={cssNames({ loading: valuesLoading })} + readOnly={valuesLoading || this.showOnlyUserSuppliedValues} + > + {valuesLoading && } +