From 8055ba261b5a341189336c654462e53384240b2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 08:59:18 -0500 Subject: [PATCH 01/17] Bump @types/url-parse from 1.4.4 to 1.4.5 (#4494) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sebastian Malton --- package.json | 2 +- src/common/protocol-handler/error.ts | 4 ++-- src/common/protocol-handler/router.ts | 10 +++++----- src/main/protocol-handler/router.ts | 8 ++++---- yarn.lock | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 2d24504590..c7778e8d37 100644 --- a/package.json +++ b/package.json @@ -312,7 +312,7 @@ "@types/tcp-port-used": "^1.0.0", "@types/tempy": "^0.3.0", "@types/triple-beam": "^1.3.2", - "@types/url-parse": "^1.4.4", + "@types/url-parse": "^1.4.5", "@types/uuid": "^8.3.3", "@types/webpack": "^4.41.31", "@types/webpack-dev-server": "^3.11.6", diff --git a/src/common/protocol-handler/error.ts b/src/common/protocol-handler/error.ts index c08d867766..263d5bfe2f 100644 --- a/src/common/protocol-handler/error.ts +++ b/src/common/protocol-handler/error.ts @@ -30,13 +30,13 @@ export enum RoutingErrorType { MISSING_EXTENSION = "missing-ext", } -export class RoutingError extends Error { +export class RoutingError extends Error { /** * Will be set if the routing error originated in an extension route table */ public extensionName?: string; - constructor(public type: RoutingErrorType, public url: Url) { + constructor(public type: RoutingErrorType, public url: Url) { super("routing error"); } diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index c387b18a55..6aea2037d1 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -91,7 +91,7 @@ export abstract class LensProtocolRouter extends Singleton { * @param url the parsed URL that initiated the `lens://` protocol * @returns true if a route has been found */ - protected _routeToInternal(url: Url): RouteAttempt { + protected _routeToInternal(url: Url>): RouteAttempt { return this._route(this.internalRoutes.entries(), url); } @@ -101,7 +101,7 @@ export abstract class LensProtocolRouter extends Singleton { * @param routes the array of path schemas, handler pairs to match against * @param url the url (in its current state) */ - protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url): null | [match>, RouteHandler] { + protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url>): null | [match>, RouteHandler] { const matches: [match>, RouteHandler][] = []; for (const [schema, handler] of routes) { @@ -128,7 +128,7 @@ export abstract class LensProtocolRouter extends Singleton { * @param routes the array of (path schemas, handler) pairs to match against * @param url the url (in its current state) */ - protected _route(routes: Iterable<[string, RouteHandler]>, url: Url, extensionName?: string): RouteAttempt { + protected _route(routes: Iterable<[string, RouteHandler]>, url: Url>, extensionName?: string): RouteAttempt { const route = this._findMatchingRoute(routes, url); if (!route) { @@ -166,7 +166,7 @@ export abstract class LensProtocolRouter extends Singleton { * @param url the protocol request URI that was "open"-ed * @returns either the found name or the instance of `LensExtension` */ - protected async _findMatchingExtensionByName(url: Url): Promise { + protected async _findMatchingExtensionByName(url: Url>): Promise { interface ExtensionUrlMatch { [EXTENSION_PUBLISHER_MATCH]: string; [EXTENSION_NAME_MATCH]: string; @@ -222,7 +222,7 @@ export abstract class LensProtocolRouter extends Singleton { * Note: this function modifies its argument, do not reuse * @param url the protocol request URI that was "open"-ed */ - protected async _routeToExtension(url: Url): Promise { + protected async _routeToExtension(url: Url>): Promise { const extension = await this._findMatchingExtensionByName(url); if (typeof extension === "string") { diff --git a/src/main/protocol-handler/router.ts b/src/main/protocol-handler/router.ts index fe178705e7..ffcfd49d3e 100644 --- a/src/main/protocol-handler/router.ts +++ b/src/main/protocol-handler/router.ts @@ -39,7 +39,7 @@ export interface FallbackHandler { * @returns `true` if it should be routed internally to Lens, `false` if to an extension * @throws if `host` is not valid */ -function checkHost(url: URLParse): boolean { +function checkHost(url: URLParse): boolean { switch (url.host) { case "app": return true; @@ -112,7 +112,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { return false; } - protected async _findMatchingExtensionByName(url: URLParse): Promise { + protected async _findMatchingExtensionByName(url: URLParse>): Promise { const firstAttempt = await super._findMatchingExtensionByName(url); if (typeof firstAttempt !== "string") { @@ -126,7 +126,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { return ""; } - protected _routeToInternal(url: URLParse): RouteAttempt { + protected _routeToInternal(url: URLParse>): RouteAttempt { const rawUrl = url.toString(); // for sending to renderer const attempt = super._routeToInternal(url); @@ -135,7 +135,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { return attempt; } - protected async _routeToExtension(url: URLParse): Promise { + protected async _routeToExtension(url: URLParse>): Promise { const rawUrl = url.toString(); // for sending to renderer /** diff --git a/yarn.lock b/yarn.lock index d59b4b2f37..97bda7e5cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1951,10 +1951,10 @@ resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec" integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw== -"@types/url-parse@^1.4.4": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.4.tgz#ebeb0ec8b581318739cf73e9f9b186f610764255" - integrity sha512-KtQLad12+4T/NfSxpoDhmr22+fig3T7/08QCgmutYA6QSznSRmEtuL95GrhVV40/0otTEdFc+etRcCTqhh1q5Q== +"@types/url-parse@^1.4.5": + version "1.4.5" + resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.5.tgz#bda0b813cc8f79d767dc8ca645fdabc238c6ae77" + integrity sha512-8Wje3itJpk/FX+QItca9vjNLjGx5jlEYBw/CpMi03Fphk2DSVeZDUqWTE81BeCI5Bl6Z+zmA1O9L/8e3ZUSeLg== "@types/uuid@^8.3.3": version "8.3.3" From 39955b623f7a9d38de68e1e7c2153b4a24769e5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 08:59:25 -0500 Subject: [PATCH 02/17] Bump sharp from 0.29.2 to 0.29.3 (#4495) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 56 +++++++++++++++++++++------------------------------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index c7778e8d37..a968cd0292 100644 --- a/package.json +++ b/package.json @@ -369,7 +369,7 @@ "react-window": "^1.8.6", "sass": "^1.43.4", "sass-loader": "^8.0.2", - "sharp": "^0.29.2", + "sharp": "^0.29.3", "style-loader": "^2.0.0", "tailwindcss": "^2.2.19", "ts-jest": "26.5.6", diff --git a/yarn.lock b/yarn.lock index 97bda7e5cf..cda69963a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4462,13 +4462,6 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -9383,11 +9376,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -9701,12 +9689,12 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -node-abi@^2.21.0: - version "2.30.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b" - integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg== +node-abi@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248" + integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw== dependencies: - semver "^5.4.1" + semver "^7.3.5" node-addon-api@^1.6.3: version "1.7.2" @@ -11057,10 +11045,10 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== -prebuild-install@^6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== +prebuild-install@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" + integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -11068,11 +11056,11 @@ prebuild-install@^6.1.4: minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.21.0" + node-abi "^3.3.0" npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" - simple-get "^3.0.3" + simple-get "^4.0.0" tar-fs "^2.0.0" tunnel-agent "^0.6.0" @@ -12407,17 +12395,17 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -sharp@^0.29.2: - version "0.29.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.2.tgz#e8c003cd9cb321585b32dbda6eed3baa7d6f2308" - integrity sha512-XWRdiYLIJ3tDUejRyG24KERnJzMfIoyiJBntd2S6/uj3NEeNgRFRLgiBlvPxMa8aml14dKKD98yHinSNKp1xzQ== +sharp@^0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: color "^4.0.1" detect-libc "^1.0.3" node-addon-api "^4.2.0" - prebuild-install "^6.1.4" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^3.1.0" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -12496,12 +12484,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== dependencies: - decompress-response "^4.2.0" + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" From d73df7fe0d2c12454e9870dea6cea0ec630ec05d Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 3 Dec 2021 17:41:08 +0300 Subject: [PATCH 03/17] Show active hotbar on bottom (#4476) - Extends extension API to support choosing sides for bottom bar entries --- .../registries/status-bar-registry.ts | 6 + src/renderer/bootstrap.tsx | 7 +- .../cluster-manager/active-hotbar-name.tsx | 40 ++++++ ...{bottom-bar.scss => bottom-bar.module.css} | 30 +++-- .../cluster-manager/bottom-bar.test.tsx | 114 +++++++++++++++++- .../components/cluster-manager/bottom-bar.tsx | 21 +++- .../cluster-manager/cluster-manager.scss | 2 +- .../components/hotbar/hotbar-selector.scss | 3 +- src/renderer/initializers/index.ts | 1 + .../initializers/status-bar-registry.tsx | 35 ++++++ 10 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 src/renderer/components/cluster-manager/active-hotbar-name.tsx rename src/renderer/components/cluster-manager/{bottom-bar.scss => bottom-bar.module.css} (80%) create mode 100644 src/renderer/initializers/status-bar-registry.tsx diff --git a/src/extensions/registries/status-bar-registry.ts b/src/extensions/registries/status-bar-registry.ts index 2afdee76a6..e4864b5173 100644 --- a/src/extensions/registries/status-bar-registry.ts +++ b/src/extensions/registries/status-bar-registry.ts @@ -26,6 +26,12 @@ import { BaseRegistry } from "./base-registry"; interface StatusBarComponents { Item?: React.ComponentType; + /** + * The side of the bottom bar to place this component. + * + * @default "right" + */ + position?: "left" | "right"; } interface StatusBarRegistrationV2 { diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index edc4ff90f7..f42a671de6 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -104,13 +104,13 @@ export async function bootstrap(comp: () => Promise, di: Dependenc logger.info(`${logPrefix} initializing WelcomeMenuRegistry`); initializers.initWelcomeMenuRegistry(); - logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegist`); + logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`); initializers.initWorkloadsOverviewDetailRegistry(); logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`); initializers.initCatalogEntityDetailRegistry(); - logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntrie`); + logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`); initializers.initCatalogCategoryRegistryEntries(); logger.info(`${logPrefix} initializing Catalog`); @@ -119,6 +119,9 @@ export async function bootstrap(comp: () => Promise, di: Dependenc logger.info(`${logPrefix} initializing IpcRendererListeners`); initializers.initIpcRendererListeners(); + logger.info(`${logPrefix} initializing StatusBarRegistry`); + initializers.initStatusBarRegistry(); + ExtensionLoader.createInstance().init(); ExtensionDiscovery.createInstance().init(); diff --git a/src/renderer/components/cluster-manager/active-hotbar-name.tsx b/src/renderer/components/cluster-manager/active-hotbar-name.tsx new file mode 100644 index 0000000000..63f8b583b9 --- /dev/null +++ b/src/renderer/components/cluster-manager/active-hotbar-name.tsx @@ -0,0 +1,40 @@ +/** + * 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. + */ + +import React from "react"; +import { observer } from "mobx-react"; +import { Icon } from "../icon"; +import { HotbarStore } from "../../../common/hotbar-store"; +import { CommandOverlay } from "../command-palette"; +import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command"; + +export const ActiveHotbarName = observer(() => { + return ( +
CommandOverlay.open()} + > + + {HotbarStore.getInstance().getActive()?.name} +
+ ); +}); diff --git a/src/renderer/components/cluster-manager/bottom-bar.scss b/src/renderer/components/cluster-manager/bottom-bar.module.css similarity index 80% rename from src/renderer/components/cluster-manager/bottom-bar.scss rename to src/renderer/components/cluster-manager/bottom-bar.module.css index aca8272489..7eeb1c5ef4 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.scss +++ b/src/renderer/components/cluster-manager/bottom-bar.module.css @@ -20,22 +20,26 @@ */ .BottomBar { - $spacing: $padding * 0.5; - --flex-gap: #{$spacing}; + @apply flex px-2 text-white; + grid-area: bottom-bar; background-color: var(--blue); - padding: 0 2px; height: var(--bottom-bar-height); + font-size: var(--font-size-small); +} - .extensions { - font-size: var(--font-size-small); - color: white; - .item { - padding: $padding * 0.25 $padding * 0.5; - &:hover { - background-color: #ffffff33; - cursor: pointer; - } - } +.item { + @apply flex items-center mr-2 h-full px-2 last:mr-0; + + &:hover { + @apply cursor-pointer; + + background-color: #ffffff33; } } + +.onLeft + .onRight { + @apply ml-auto; +} + +.onRight {} \ No newline at end of file diff --git a/src/renderer/components/cluster-manager/bottom-bar.test.tsx b/src/renderer/components/cluster-manager/bottom-bar.test.tsx index d9f683fe66..4390175889 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.test.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.test.tsx @@ -20,25 +20,58 @@ */ import React from "react"; -import { render } from "@testing-library/react"; +import mockFs from "mock-fs"; +import { render, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; +import { BottomBar } from "./bottom-bar"; +import { StatusBarRegistry } from "../../../extensions/registries"; +import { HotbarStore } from "../../../common/hotbar-store"; +import { AppPaths } from "../../../common/app-paths"; +import { CommandOverlay } from "../command-palette"; +import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command"; +import { ActiveHotbarName } from "./active-hotbar-name"; -jest.mock("electron", () => ({ - app: { - getPath: () => "/foo", +jest.mock("../command-palette", () => ({ + CommandOverlay: { + open: jest.fn(), }, })); -import { BottomBar } from "./bottom-bar"; -import { StatusBarRegistry } from "../../../extensions/registries"; +AppPaths.init(); + +jest.mock("electron", () => ({ + app: { + getName: () => "lens", + setName: jest.fn(), + setPath: jest.fn(), + getPath: () => "tmp", + }, + ipcMain: { + handle: jest.fn(), + on: jest.fn(), + removeAllListeners: jest.fn(), + off: jest.fn(), + send: jest.fn(), + }, +})); describe("", () => { beforeEach(() => { + const mockOpts = { + "tmp": { + "test-store.json": JSON.stringify({}), + }, + }; + + mockFs(mockOpts); StatusBarRegistry.createInstance(); + HotbarStore.createInstance(); }); afterEach(() => { StatusBarRegistry.resetInstance(); + HotbarStore.resetInstance(); + mockFs.restore(); }); it("renders w/o errors", () => { @@ -87,4 +120,73 @@ describe("", () => { expect(await getByTestId(testId)).toHaveTextContent(text); }); + + it("show default hotbar name", () => { + StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + { item: () => }, + ]); + const { getByTestId } = render(); + + expect(getByTestId("current-hotbar-name")).toHaveTextContent("default"); + }); + + it("show active hotbar name", () => { + StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + { item: () => }, + ]); + const { getByTestId } = render(); + + HotbarStore.getInstance().add({ + id: "new", + name: "new", + }, { setActive: true }); + + expect(getByTestId("current-hotbar-name")).toHaveTextContent("new"); + }); + + it("opens command palette on click", () => { + StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + { item: () => }, + ]); + const { getByTestId } = render(); + const activeHotbar = getByTestId("current-hotbar-name"); + + fireEvent.click(activeHotbar); + + expect(CommandOverlay.open).toHaveBeenCalledWith(); + }); + + it("sort positioned items properly", () => { + StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + { + components: { + Item: () =>
right
, + }, + }, + { + components: { + Item: () =>
right
, + position: "right", + }, + }, + { + components: { + Item: () =>
left
, + position: "left", + }, + }, + { + components: { + Item: () =>
left
, + position: "left", + }, + }, + ]); + + const { getAllByTestId } = render(); + const elems = getAllByTestId("sortedElem"); + const positions = elems.map(elem => elem.textContent); + + expect(positions).toEqual(["left", "left", "right", "right"]); + }); }); diff --git a/src/renderer/components/cluster-manager/bottom-bar.tsx b/src/renderer/components/cluster-manager/bottom-bar.tsx index 0f61a03833..27b5a10f3f 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.tsx @@ -19,11 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./bottom-bar.scss"; +import styles from "./bottom-bar.module.css"; import React from "react"; import { observer } from "mobx-react"; import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries"; +import { cssNames } from "../../utils"; @observer export class BottomBar extends React.Component { @@ -44,26 +45,36 @@ export class BottomBar extends React.Component { return null; } + items.sort(function sortLeftPositionFirst(a, b) { + return a.components?.position?.localeCompare(b.components?.position); + }); + return ( -
+ <> {items.map((registration, index) => { if (!registration?.item && !registration?.components?.Item) { return null; } return ( -
+
{this.renderRegisteredItem(registration)}
); })} -
+ ); } render() { return ( -
+
{this.renderRegisteredItems()}
); diff --git a/src/renderer/components/cluster-manager/cluster-manager.scss b/src/renderer/components/cluster-manager/cluster-manager.scss index 3b9e74c1ac..8cdce19d82 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.scss +++ b/src/renderer/components/cluster-manager/cluster-manager.scss @@ -20,7 +20,7 @@ */ .ClusterManager { - --bottom-bar-height: 22px; + --bottom-bar-height: 21px; --hotbar-width: 75px; display: grid; diff --git a/src/renderer/components/hotbar/hotbar-selector.scss b/src/renderer/components/hotbar/hotbar-selector.scss index d0fe3fa57c..5eaeb33692 100644 --- a/src/renderer/components/hotbar/hotbar-selector.scss +++ b/src/renderer/components/hotbar/hotbar-selector.scss @@ -43,7 +43,8 @@ .Icon { --size: 16px; - padding: 0 4px; + padding: 0 4px 0 0px; + margin: 0px 4px; &:hover { box-shadow: none; diff --git a/src/renderer/initializers/index.ts b/src/renderer/initializers/index.ts index 27e6ffef9c..55d1bbbb1b 100644 --- a/src/renderer/initializers/index.ts +++ b/src/renderer/initializers/index.ts @@ -30,3 +30,4 @@ export * from "./registries"; export * from "./welcome-menu-registry"; export * from "./workloads-overview-detail-registry"; export * from "./catalog-category-registry"; +export * from "./status-bar-registry"; diff --git a/src/renderer/initializers/status-bar-registry.tsx b/src/renderer/initializers/status-bar-registry.tsx new file mode 100644 index 0000000000..e2c7bc561e --- /dev/null +++ b/src/renderer/initializers/status-bar-registry.tsx @@ -0,0 +1,35 @@ +/** + * 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. + */ + +import React from "react"; +import { StatusBarRegistry } from "../../extensions/registries"; +import { ActiveHotbarName } from "../components/cluster-manager/active-hotbar-name"; + +export function initStatusBarRegistry() { + StatusBarRegistry.getInstance().add([ + { + components: { + Item: () => , + position: "left", + }, + }, + ]); +} From 122f14f32b6cbc4628eb9a52bcfcfd6292bb3c71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 11:49:57 -0500 Subject: [PATCH 04/17] Bump react-material-ui-carousel from 2.3.5 to 2.3.8 (#4491) 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 a968cd0292..649ffb06ad 100644 --- a/package.json +++ b/package.json @@ -238,7 +238,7 @@ "proper-lockfile": "^4.1.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-material-ui-carousel": "^2.3.5", + "react-material-ui-carousel": "^2.3.8", "react-router": "^5.2.0", "react-virtualized-auto-sizer": "^1.0.6", "readable-stream": "^3.6.0", diff --git a/yarn.lock b/yarn.lock index cda69963a0..98ebbff043 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11462,10 +11462,10 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== -react-material-ui-carousel@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.5.tgz#c6b4514333564ff4d49d2447f6d67f6c7651e813" - integrity sha512-laiysIaA47rX0/lIVjprX5MHrDyjLxgzAsyBnG8OKgO2ad5o3Pg88PAmu6RlMy/L/H6PKFYQ8H5bAqpeZETeYw== +react-material-ui-carousel@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.8.tgz#6d6c088be22301fa91863383f35795d4e917a2a5" + integrity sha512-0qAWiKBQI4GCjuCWZANdDuHPW8k8N3qZo3tCn9OxpVNO+M9lq7Xzak63AyPwmRDRw93l97LYhbwQIMohvpaMeg== dependencies: auto-bind "^2.1.1" react-swipeable "^6.1.0" From 4f291ad077d8e000007cb6f29894da8e55d2c4ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 11:50:05 -0500 Subject: [PATCH 05/17] Bump moment-timezone from 0.5.33 to 0.5.34 (#4489) 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 649ffb06ad..2abe2b7d29 100644 --- a/package.json +++ b/package.json @@ -227,7 +227,7 @@ "mobx-react": "^7.2.1", "mock-fs": "^4.14.0", "moment": "^2.29.1", - "moment-timezone": "^0.5.33", + "moment-timezone": "^0.5.34", "monaco-editor": "^0.29.1", "monaco-editor-webpack-plugin": "^5.0.0", "node-fetch": "lensapp/node-fetch#2.x", diff --git a/yarn.lock b/yarn.lock index 98ebbff043..434c6a2ac1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9537,10 +9537,10 @@ modern-normalize@^1.1.0: resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== -moment-timezone@^0.5.33: - version "0.5.33" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" - integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== +moment-timezone@^0.5.34: + version "0.5.34" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" + integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== dependencies: moment ">= 2.9.0" From 7dfd4d586cb87c11642adee1438bb8534fed96d7 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 3 Dec 2021 12:49:42 -0500 Subject: [PATCH 06/17] Fix error notifications not being displayed for CreateResource tab (#4502) --- src/renderer/components/dock/create-resource.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index b7a94c868a..513b7134bd 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -35,7 +35,7 @@ import { InfoPanel } from "./info-panel"; import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api"; import { Notifications } from "../notifications"; import logger from "../../../common/logger"; -import type { KubeJsonApiData, KubeJsonApiError } from "../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api"; import { getDetailsUrl } from "../kube-detail-params"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { prevDefault } from "../../utils"; @@ -130,9 +130,7 @@ export class CreateResource extends React.Component {

, ); } catch (error) { - const failureReason = (error as KubeJsonApiError).reason.toString(); - - Notifications.error(failureReason); + Notifications.error(error?.toString() ?? "Unknown error occured"); } }); From 9aadb721c93a3ff35260c104e57c25e360f23231 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 12:49:52 -0500 Subject: [PATCH 07/17] Bump webpack-dev-server from 3.11.2 to 3.11.3 (#4498) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 2abe2b7d29..401c5f7c35 100644 --- a/package.json +++ b/package.json @@ -385,7 +385,7 @@ "url-loader": "^4.1.1", "webpack": "^4.46.0", "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.2", + "webpack-dev-server": "^3.11.3", "webpack-node-externals": "^1.7.2", "xterm": "^4.14.1", "xterm-addon-fit": "^0.5.0" diff --git a/yarn.lock b/yarn.lock index 434c6a2ac1..dd8a009b30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2447,7 +2447,12 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" -ansi-html@0.0.7, ansi-html@^0.0.7: +ansi-html-community@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-html@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= @@ -14219,12 +14224,12 @@ webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.11.2: - version "3.11.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" - integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== +webpack-dev-server@^3.11.3: + version "3.11.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz#8c86b9d2812bf135d3c9bce6f07b718e30f7c3d3" + integrity sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA== dependencies: - ansi-html "0.0.7" + ansi-html-community "0.0.8" bonjour "^3.5.0" chokidar "^2.1.8" compression "^1.7.4" From 7973f4bce2189c7d121802ab597cf6e97d5f3c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Dec 2021 14:33:05 -0500 Subject: [PATCH 08/17] Bump @types/webpack from 4.41.31 to 4.41.32 (#4499) 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 401c5f7c35..96d99fa47c 100644 --- a/package.json +++ b/package.json @@ -314,7 +314,7 @@ "@types/triple-beam": "^1.3.2", "@types/url-parse": "^1.4.5", "@types/uuid": "^8.3.3", - "@types/webpack": "^4.41.31", + "@types/webpack": "^4.41.32", "@types/webpack-dev-server": "^3.11.6", "@types/webpack-env": "^1.16.3", "@types/webpack-node-externals": "^1.7.1", diff --git a/yarn.lock b/yarn.lock index dd8a009b30..0a0084cea4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1998,10 +1998,10 @@ "@types/source-list-map" "*" source-map "^0.7.3" -"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.31", "@types/webpack@^4.41.8": - version "4.41.31" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.31.tgz#c35f252a3559ddf9c85c0d8b0b42019025e581aa" - integrity sha512-/i0J7sepXFIp1ZT7FjUGi1eXMCg8HCCzLJEQkKsOtbJFontsJLolBcDC+3qxn5pPwiCt1G0ZdRmYRzNBtvpuGQ== +"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.32", "@types/webpack@^4.41.8": + version "4.41.32" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212" + integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== dependencies: "@types/node" "*" "@types/tapable" "^1" From 304941d397b27cd3c78dbf8fc64949d908829354 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 3 Dec 2021 15:19:01 -0500 Subject: [PATCH 09/17] Fix IngressApi being registered with a wrong new apiBase (#4485) --- src/common/k8s-api/__tests__/kube-api.test.ts | 100 +++++++++++++++++- src/common/k8s-api/kube-api-parse.ts | 19 ++-- src/common/k8s-api/kube-api.ts | 85 +++++++++------ .../+custom-resources/crd-resources.tsx | 4 +- .../components/+custom-resources/crd.store.ts | 17 ++- 5 files changed, 174 insertions(+), 51 deletions(-) diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index 10fd82b696..322423d683 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -26,6 +26,12 @@ import { KubeObject } from "../kube-object"; import AbortController from "abort-controller"; import { delay } from "../../utils/delay"; import { PassThrough } from "stream"; +import { ApiManager, apiManager } from "../api-manager"; +import { Ingress, Pod } from "../endpoints"; + +jest.mock("../api-manager"); + +const mockApiManager = apiManager as jest.Mocked; class TestKubeObject extends KubeObject { static kind = "Pod"; @@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject { static apiBase = "/api/v1/pods"; } -class TestKubeApi extends KubeApi { } +class TestKubeApi extends KubeApi { + public async checkPreferredVersion() { + return super.checkPreferredVersion(); + } +} describe("forRemoteCluster", () => { it("builds api client for KubeObject", async () => { @@ -184,6 +194,94 @@ describe("KubeApi", () => { expect(kubeApi.apiGroup).toEqual("extensions"); }); + describe("checkPreferredVersion", () => { + it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => { + expect.hasAssertions(); + + const api = new TestKubeApi({ + objectConstructor: Ingress, + checkPreferredVersion: true, + fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"], + request: { + get: jest.fn() + .mockImplementationOnce((path: string) => { + expect(path).toBe("/apis/networking.k8s.io/v1"); + + throw new Error("no"); + }) + .mockImplementationOnce((path: string) => { + expect(path).toBe("/apis/extensions/v1beta1"); + + return { + resources: [ + { + name: "ingresses", + }, + ], + }; + }) + .mockImplementationOnce((path: string) => { + expect(path).toBe("/apis/extensions"); + + return { + preferredVersion: { + version: "v1beta1", + }, + }; + }), + } as any, + }); + + await api.checkPreferredVersion(); + + expect(api.apiVersionPreferred).toBe("v1beta1"); + expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything()); + }); + + it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => { + expect.hasAssertions(); + + const api = new TestKubeApi({ + objectConstructor: Pod, + checkPreferredVersion: true, + fallbackApiBases: ["/api/v1beta1/pods"], + request: { + get: jest.fn() + .mockImplementationOnce((path: string) => { + expect(path).toBe("/api/v1"); + + throw new Error("no"); + }) + .mockImplementationOnce((path: string) => { + expect(path).toBe("/api/v1beta1"); + + return { + resources: [ + { + name: "pods", + }, + ], + }; + }) + .mockImplementationOnce((path: string) => { + expect(path).toBe("/api"); + + return { + preferredVersion: { + version: "v1beta1", + }, + }; + }), + } as any, + }); + + await api.checkPreferredVersion(); + + expect(api.apiVersionPreferred).toBe("v1beta1"); + expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything()); + }); + }); + describe("patch", () => { let api: TestKubeApi; diff --git a/src/common/k8s-api/kube-api-parse.ts b/src/common/k8s-api/kube-api-parse.ts index 57c835ed6b..a5d137be02 100644 --- a/src/common/k8s-api/kube-api-parse.ts +++ b/src/common/k8s-api/kube-api-parse.ts @@ -37,7 +37,7 @@ export interface IKubeApiLinkRef { apiPrefix?: string; apiVersion: string; resource: string; - name: string; + name?: string; namespace?: string; } @@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed { }; } -export function createKubeApiURL(ref: IKubeApiLinkRef): string { - const { apiPrefix = "/apis", resource, apiVersion, name } = ref; - let { namespace } = ref; +export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string { + const parts = [apiPrefix, apiVersion]; if (namespace) { - namespace = `namespaces/${namespace}`; + parts.push("namespaces", namespace); } - return [apiPrefix, apiVersion, namespace, resource, name] - .filter(v => v) - .join("/"); + parts.push(resource); + + if (name) { + parts.push(name); + } + + return parts.join("/"); } diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index a8b19d81b5..0f31758ab2 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -38,9 +38,14 @@ import AbortController from "abort-controller"; import { Agent, AgentOptions } from "https"; import type { Patch } from "rfc6902"; +/** + * The options used for creating a `KubeApi` + */ export interface IKubeApiOptions { /** * base api-path for listing all resources, e.g. "/api/v1/pods" + * + * If not specified then will be the one on the `objectConstructor` */ apiBase?: string; @@ -52,11 +57,33 @@ export interface IKubeApiOptions { */ fallbackApiBases?: string[]; - objectConstructor: KubeObjectConstructor; - request?: KubeJsonApi; - isNamespaced?: boolean; - kind?: string; + /** + * If `true` then will check all declared apiBases against the kube api server + * for the first accepted one. + */ checkPreferredVersion?: boolean; + + /** + * The constructor for the kube objects returned from the API + */ + objectConstructor: KubeObjectConstructor; + + /** + * The api instance to use for making requests + * + * @default apiKube + */ + request?: KubeJsonApi; + + /** + * @deprecated should be specified by `objectConstructor` + */ + isNamespaced?: boolean; + + /** + * @deprecated should be specified by `objectConstructor` + */ + kind?: string; } export interface IKubeApiQueryParams { @@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor { export class KubeApi { readonly kind: string; - readonly apiBase: string; - readonly apiPrefix: string; - readonly apiGroup: string; readonly apiVersion: string; - readonly apiVersionPreferred?: string; + apiBase: string; + apiPrefix: string; + apiGroup: string; + apiVersionPreferred?: string; readonly apiResource: string; readonly isNamespaced: boolean; @@ -264,23 +291,18 @@ export class KubeApi { private watchId = 1; constructor(protected options: IKubeApiOptions) { - const { - objectConstructor, - request = apiKube, - kind = options.objectConstructor?.kind, - isNamespaced = options.objectConstructor?.namespaced, - } = options || {}; - + const { objectConstructor, request, kind, isNamespaced } = options; const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase); - this.kind = kind; - this.isNamespaced = isNamespaced; + this.options = options; + this.kind = kind ?? objectConstructor.kind; + this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false; this.apiBase = apiBase; this.apiPrefix = apiPrefix; this.apiGroup = apiGroup; this.apiVersion = apiVersion; this.apiResource = resource; - this.request = request; + this.request = request ?? apiKube; this.objectConstructor = objectConstructor; this.parseResponse = this.parseResponse.bind(this); @@ -353,21 +375,16 @@ export class KubeApi { const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup(); // The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them - Object.defineProperty(this, "apiPrefix", { - value: apiPrefix, - }); - Object.defineProperty(this, "apiGroup", { - value: apiGroup, - }); + this.apiPrefix = apiPrefix; + this.apiGroup = apiGroup; - const res = await this.request.get(`${this.apiPrefix}/${this.apiGroup}`); + const url = [apiPrefix, apiGroup].filter(Boolean).join("/"); + const res = await this.request.get(url); - Object.defineProperty(this, "apiVersionPreferred", { - value: res?.preferredVersion?.version ?? null, - }); + this.apiVersionPreferred = res?.preferredVersion?.version ?? null; if (this.apiVersionPreferred) { - Object.defineProperty(this, "apiBase", { value: this.getUrl() }); + this.apiBase = this.computeApiBase(); apiManager.registerApi(this.apiBase, this); } } @@ -385,7 +402,15 @@ export class KubeApi { return this.list(params, { limit: 1 }); } - getUrl({ name, namespace = "default" }: Partial = {}, query?: Partial) { + private computeApiBase(): string { + return createKubeApiURL({ + apiPrefix: this.apiPrefix, + apiVersion: this.apiVersionWithGroup, + resource: this.apiResource, + }); + } + + getUrl({ name, namespace }: Partial = {}, query?: Partial) { const resourcePath = createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, diff --git a/src/renderer/components/+custom-resources/crd-resources.tsx b/src/renderer/components/+custom-resources/crd-resources.tsx index d0eb4779c7..1c8f55aaab 100644 --- a/src/renderer/components/+custom-resources/crd-resources.tsx +++ b/src/renderer/components/+custom-resources/crd-resources.tsx @@ -57,9 +57,7 @@ export class CrdResources extends React.Component { } @computed get store() { - if (!this.crd) return null; - - return apiManager.getStore(this.crd.getResourceApiBase()); + return apiManager.getStore(this.crd?.getResourceApiBase()); } render() { diff --git a/src/renderer/components/+custom-resources/crd.store.ts b/src/renderer/components/+custom-resources/crd.store.ts index 83c8b5cdb6..2cdcbee55b 100644 --- a/src/renderer/components/+custom-resources/crd.store.ts +++ b/src/renderer/components/+custom-resources/crd.store.ts @@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store"; import { KubeObject } from "../../../common/k8s-api/kube-object"; function initStore(crd: CustomResourceDefinition) { - const apiBase = crd.getResourceApiBase(); - const kind = crd.getResourceKind(); - const isNamespaced = crd.isNamespaced(); - const api = apiManager.getApi(apiBase) ?? new KubeApi({ - objectConstructor: KubeObject, - apiBase, - kind, - isNamespaced, - }); + const objectConstructor = class extends KubeObject { + static readonly kind = crd.getResourceKind(); + static readonly namespaced = crd.isNamespaced(); + static readonly apiBase = crd.getResourceApiBase(); + }; + + const api = apiManager.getApi(objectConstructor.apiBase) + ?? new KubeApi({ objectConstructor }); if (!apiManager.getStore(api)) { apiManager.registerStore(new CRDResourceStore(api)); From 7ed50f83fc547c8a0f421a3db7ba1d5616e0981a Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 6 Dec 2021 20:05:05 +0200 Subject: [PATCH 10/17] Add breadcrumbs to removal of kube objects, but also discuss about general improvements (#4282) Co-authored-by: Mikko Aspiala Co-authored-by: Janne Savolainen Co-authored-by: Janne Savolainen Co-authored-by: Mikko Aspiala --- .idea/jsLibraryMappings.xml | 6 + .idea/lens.iml | 1 + .idea/libraries/ogre_tools.xml | 16 + package.json | 12 +- .../confirm-dialog/confirm-dialog.tsx | 2 + src/renderer/components/dialog/dialog.tsx | 4 +- .../kube-object-menu.test.tsx.snap | 371 ++++++++++++++++++ .../dependencies/api-manager.injectable.ts | 32 ++ .../dependencies/cluster-name.injectable.ts | 41 ++ .../dependencies/cluster.injectable.ts | 33 ++ .../edit-resource-tab.injectable.ts | 31 ++ .../dependencies/hide-details.injectable.ts | 31 ++ .../get-kube-object-menu-items.ts | 43 ++ .../kube-object-menu-items.injectable.ts | 44 +++ .../kube-object-menu-registry.injectable.ts | 31 ++ .../components/kube-object-menu/index.ts | 3 +- .../kube-object-menu-container.tsx | 24 ++ .../kube-object-menu.injectable.tsx | 60 +++ .../kube-object-menu.test.tsx | 284 ++++++++++++++ .../kube-object-menu/kube-object-menu.tsx | 65 +-- src/renderer/components/menu/menu-actions.tsx | 52 ++- .../components/test-utils/renderFor.tsx | 42 ++ yarn.lock | 59 ++- 23 files changed, 1214 insertions(+), 73 deletions(-) create mode 100644 .idea/jsLibraryMappings.xml create mode 100644 .idea/libraries/ogre_tools.xml create mode 100644 src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap create mode 100644 src/renderer/components/kube-object-menu/dependencies/api-manager.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/cluster-name.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/edit-resource-tab.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/hide-details.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/get-kube-object-menu-items.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-items.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registry.injectable.ts create mode 100644 src/renderer/components/kube-object-menu/kube-object-menu-container.tsx create mode 100644 src/renderer/components/kube-object-menu/kube-object-menu.injectable.tsx create mode 100644 src/renderer/components/kube-object-menu/kube-object-menu.test.tsx create mode 100644 src/renderer/components/test-utils/renderFor.tsx diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000000..0034429ff9 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/lens.iml b/.idea/lens.iml index 079203f10b..fe491887dd 100644 --- a/.idea/lens.iml +++ b/.idea/lens.iml @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/.idea/libraries/ogre_tools.xml b/.idea/libraries/ogre_tools.xml new file mode 100644 index 0000000000..6deeb167ce --- /dev/null +++ b/.idea/libraries/ogre_tools.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 96d99fa47c..5653d9c5b8 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,12 @@ "setupFiles": [ "/src/jest.setup.ts", "jest-canvas-mock" - ] + ], + "globals": { + "ts-jest": { + "isolatedModules": true + } + } }, "build": { "generateUpdatesFilesForAllChannels": true, @@ -193,8 +198,8 @@ "@kubernetes/client-node": "^0.16.1", "@sentry/electron": "^2.5.4", "@sentry/integrations": "^6.15.0", - "@ogre-tools/injectable": "^1.2.1", - "@ogre-tools/injectable-react": "^1.2.1", + "@ogre-tools/injectable": "^1.3.0", + "@ogre-tools/injectable-react": "^1.3.1", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", "autobind-decorator": "^2.4.0", @@ -260,6 +265,7 @@ "ws": "^7.5.5" }, "devDependencies": { + "@async-fn/jest": "^1.5.0", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/src/renderer/components/confirm-dialog/confirm-dialog.tsx b/src/renderer/components/confirm-dialog/confirm-dialog.tsx index 3704dd96dc..e8450cc232 100644 --- a/src/renderer/components/confirm-dialog/confirm-dialog.tsx +++ b/src/renderer/components/confirm-dialog/confirm-dialog.tsx @@ -140,6 +140,7 @@ export class ConfirmDialog extends React.Component { isOpen={dialogState.isOpen} onClose={this.onClose} close={this.close} + {...(dialogState.isOpen ? { "data-testid":"confirmation-dialog" } : {})} >
{icon} {message} @@ -158,6 +159,7 @@ export class ConfirmDialog extends React.Component { label={labelOk} onClick={prevDefault(this.ok)} waiting={this.isSaving} + data-testid="confirm" {...okButtonProps} />
diff --git a/src/renderer/components/dialog/dialog.tsx b/src/renderer/components/dialog/dialog.tsx index be28789eff..83a49608cb 100644 --- a/src/renderer/components/dialog/dialog.tsx +++ b/src/renderer/components/dialog/dialog.tsx @@ -41,6 +41,7 @@ export interface DialogProps { modal?: boolean; pinned?: boolean; animated?: boolean; + "data-testid"?: string; } interface DialogState { @@ -149,7 +150,7 @@ export class Dialog extends React.PureComponent { }; render() { - const { modal, animated, pinned } = this.props; + const { modal, animated, pinned, "data-testid": testId } = this.props; let { className } = this.props; className = cssNames("Dialog flex center", className, { modal, pinned }); @@ -158,6 +159,7 @@ export class Dialog extends React.PureComponent { className={className} onClick={stopPropagation} ref={this.ref} + data-testid={testId} >
this.contentElem = e}> {this.props.children} diff --git a/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap new file mode 100644 index 0000000000..c7b3a53142 --- /dev/null +++ b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap @@ -0,0 +1,371 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`kube-object-menu given kube object renders 1`] = ` + +
+
+ +
+
+