From 5fd2d5501c1bb0adc2b0fe1b390b5d5e9b195da5 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Jun 2021 08:28:38 -0400 Subject: [PATCH 1/6] Add notification in renderer if lens protocol handler fails to find any routes (#2787) - Add notifications on missing lens:// handlers, and invalid URIs - add notifications on unknown entity IDs Signed-off-by: Sebastian Malton --- src/common/protocol-handler/router.ts | 61 ++++++--- src/main/index.ts | 26 ++-- .../protocol-handler/__test__/router.test.ts | 56 +++++---- src/main/protocol-handler/router.ts | 38 +++--- .../notifications/notifications.tsx | 11 +- .../{app-handlers.ts => app-handlers.tsx} | 31 ++++- src/renderer/protocol-handler/router.ts | 61 --------- src/renderer/protocol-handler/router.tsx | 119 ++++++++++++++++++ 8 files changed, 273 insertions(+), 130 deletions(-) rename src/renderer/protocol-handler/{app-handlers.ts => app-handlers.tsx} (83%) delete mode 100644 src/renderer/protocol-handler/router.ts create mode 100644 src/renderer/protocol-handler/router.tsx diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index 59101b5aa7..ee0d4b3799 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -21,7 +21,7 @@ import { match, matchPath } from "react-router"; import { countBy } from "lodash"; -import { Singleton } from "../utils"; +import { iter, Singleton } from "../utils"; import { pathToRegexp } from "path-to-regexp"; import logger from "../../main/logger"; import type Url from "url-parse"; @@ -35,7 +35,8 @@ import type { RouteHandler, RouteParams } from "../../extensions/registries/prot export const ProtocolHandlerIpcPrefix = "protocol-handler"; export const ProtocolHandlerInternal = `${ProtocolHandlerIpcPrefix}:internal`; -export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`; +export const ProtocolHandlerExtension = `${ProtocolHandlerIpcPrefix}:extension`; +export const ProtocolHandlerInvalid = `${ProtocolHandlerIpcPrefix}:invalid`; /** * These two names are long and cumbersome by design so as to decrease the chances @@ -47,6 +48,34 @@ export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`; export const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH"; export const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH"; +/** + * Returned from routing attempts + */ +export enum RouteAttempt { + /** + * A handler was found in the set of registered routes + */ + MATCHED = "matched", + /** + * A handler was not found within the set of registered routes + */ + MISSING = "missing", + /** + * The extension that was matched in the route was not activated + */ + MISSING_EXTENSION = "no-extension", +} + +export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: RouteAttempt): RouteAttempt { + switch (mainAttempt) { + case RouteAttempt.MATCHED: + return RouteAttempt.MATCHED; + case RouteAttempt.MISSING: + case RouteAttempt.MISSING_EXTENSION: + return rendererAttempt; + } +} + export abstract class LensProtocolRouter extends Singleton { // Map between path schemas and the handlers protected internalRoutes = new Map(); @@ -56,11 +85,12 @@ export abstract class LensProtocolRouter extends Singleton { static readonly ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}(\@[A-Za-z0-9_]+)?/:${EXTENSION_NAME_MATCH}`; /** - * + * Attempts to route the given URL to all internal routes that have been registered * @param url the parsed URL that initiated the `lens://` protocol + * @returns true if a route has been found */ - protected _routeToInternal(url: Url): void { - this._route(Array.from(this.internalRoutes.entries()), url); + protected _routeToInternal(url: Url): RouteAttempt { + return this._route(this.internalRoutes.entries(), url); } /** @@ -69,7 +99,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: [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) { @@ -96,7 +126,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: [string, RouteHandler][], url: Url, extensionName?: string): void { + protected _route(routes: Iterable<[string, RouteHandler]>, url: Url, extensionName?: string): RouteAttempt { const route = this._findMatchingRoute(routes, url); if (!route) { @@ -106,7 +136,9 @@ export abstract class LensProtocolRouter extends Singleton { data.extensionName = extensionName; } - return void logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data); + logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data); + + return RouteAttempt.MISSING; } const [match, handler] = route; @@ -121,6 +153,8 @@ export abstract class LensProtocolRouter extends Singleton { } handler(params); + + return RouteAttempt.MATCHED; } /** @@ -174,23 +208,22 @@ 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") { // failed to find an extension, it returned its name - return; + return RouteAttempt.MISSING_EXTENSION; } // remove the extension name from the path name so we don't need to match on it anymore url.set("pathname", url.pathname.slice(extension.name.length + 1)); - const handlers = extension - .protocolHandlers - .map<[string, RouteHandler]>(({ pathSchema, handler }) => [pathSchema, handler]); try { - this._route(handlers, url, extension.name); + const handlers = iter.map(extension.protocolHandlers, ({ pathSchema, handler }) => [pathSchema, handler] as [string, RouteHandler]); + + return this._route(handlers, url, extension.name); } catch (error) { if (error instanceof RoutingError) { error.extensionName = extension.name; diff --git a/src/main/index.ts b/src/main/index.ts index 858c903881..0396f840a1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -93,8 +93,11 @@ if (!app.requestSingleInstanceLock()) { for (const arg of process.argv) { if (arg.toLowerCase().startsWith("lens://")) { - lprm.route(arg) - .catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg })); + try { + lprm.route(arg); + } catch (error) { + logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }); + } } } } @@ -104,8 +107,11 @@ app.on("second-instance", (event, argv) => { for (const arg of argv) { if (arg.toLowerCase().startsWith("lens://")) { - lprm.route(arg) - .catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg })); + try { + lprm.route(arg); + } catch (error) { + logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }); + } } } @@ -252,9 +258,10 @@ autoUpdater.on("before-quit-for-update", () => blockQuit = false); app.on("will-quit", (event) => { // Quit app on Cmd+Q (MacOS) logger.info("APP:QUIT"); - appEventBus.emit({name: "app", action: "close"}); + appEventBus.emit({ name: "app", action: "close" }); ClusterManager.getInstance(false)?.stop(); // close cluster connections KubeconfigSyncManager.getInstance(false)?.stopSync(); + LensProtocolRouterMain.getInstance(false)?.cleanup(); cleanup(); if (blockQuit) { @@ -268,10 +275,11 @@ app.on("open-url", (event, rawUrl) => { // lens:// protocol handler event.preventDefault(); - LensProtocolRouterMain - .getInstance() - .route(rawUrl) - .catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl })); + try { + LensProtocolRouterMain.getInstance().route(rawUrl); + } catch (error) { + logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }); + } }); /** diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index 5e6d6612c4..597be0c124 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -23,7 +23,7 @@ import * as uuid from "uuid"; import { broadcastMessage } from "../../../common/ipc"; import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler"; -import { noop } from "../../../common/utils"; +import { delay, noop } from "../../../common/utils"; import { LensExtension } from "../../../extensions/main-api"; import { ExtensionLoader } from "../../../extensions/extension-loader"; import { ExtensionsStore } from "../../../extensions/extensions-store"; @@ -56,27 +56,27 @@ describe("protocol router tests", () => { LensProtocolRouterMain.resetInstance(); }); - it("should throw on non-lens URLS", async () => { + it("should throw on non-lens URLS", () => { try { const lpr = LensProtocolRouterMain.getInstance(); - expect(await lpr.route("https://google.ca")).toBeUndefined(); + expect(lpr.route("https://google.ca")).toBeUndefined(); } catch (error) { expect(error).toBeInstanceOf(Error); } }); - it("should throw when host not internal or extension", async () => { + it("should throw when host not internal or extension", () => { try { const lpr = LensProtocolRouterMain.getInstance(); - expect(await lpr.route("lens://foobar")).toBeUndefined(); + expect(lpr.route("lens://foobar")).toBeUndefined(); } catch (error) { expect(error).toBeInstanceOf(Error); } }); - it.only("should not throw when has valid host", async () => { + it("should not throw when has valid host", async () => { const extId = uuid.v4(); const ext = new LensExtension({ id: extId, @@ -102,38 +102,39 @@ describe("protocol router tests", () => { lpr.addInternalHandler("/", noop); try { - expect(await lpr.route("lens://app")).toBeUndefined(); + expect(lpr.route("lens://app")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } try { - expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined(); + expect(lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } - expect(broadcastMessage).toHaveBeenNthCalledWith(1, ProtocolHandlerInternal, "lens://app/"); - expect(broadcastMessage).toHaveBeenNthCalledWith(2, ProtocolHandlerExtension, "lens://extension/@mirantis/minikube"); + await delay(50); + expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app/", "matched"); + expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched"); }); - it("should call handler if matches", async () => { + it("should call handler if matches", () => { const lpr = LensProtocolRouterMain.getInstance(); let called = false; lpr.addInternalHandler("/page", () => { called = true; }); try { - expect(await lpr.route("lens://app/page")).toBeUndefined(); + expect(lpr.route("lens://app/page")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } expect(called).toBe(true); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched"); }); - it("should call most exact handler", async () => { + it("should call most exact handler", () => { const lpr = LensProtocolRouterMain.getInstance(); let called: any = 0; @@ -141,13 +142,13 @@ describe("protocol router tests", () => { lpr.addInternalHandler("/page/:id", params => { called = params.pathname.id; }); try { - expect(await lpr.route("lens://app/page/foo")).toBeUndefined(); + expect(lpr.route("lens://app/page/foo")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } expect(called).toBe("foo"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched"); }); it("should call most exact handler for an extension", async () => { @@ -180,13 +181,14 @@ describe("protocol router tests", () => { (ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" }); try { - expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined(); + expect(lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } + await delay(50); expect(called).toBe("foob"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched"); }); it("should work with non-org extensions", async () => { @@ -245,13 +247,15 @@ describe("protocol router tests", () => { (ExtensionsStore.getInstance() as any).state.set("icecream", { enabled: true, name: "icecream" }); try { - expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined(); + expect(lpr.route("lens://extension/icecream/page")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } + await delay(50); + expect(called).toBe(1); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched"); }); it("should throw if urlSchema is invalid", () => { @@ -260,7 +264,7 @@ describe("protocol router tests", () => { expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError(); }); - it("should call most exact handler with 3 found handlers", async () => { + it("should call most exact handler with 3 found handlers", () => { const lpr = LensProtocolRouterMain.getInstance(); let called: any = 0; @@ -270,16 +274,16 @@ describe("protocol router tests", () => { lpr.addInternalHandler("/page/bar", () => { called = 4; }); try { - expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined(); + expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } expect(called).toBe(3); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); }); - it("should call most exact handler with 2 found handlers", async () => { + it("should call most exact handler with 2 found handlers", () => { const lpr = LensProtocolRouterMain.getInstance(); let called: any = 0; @@ -288,12 +292,12 @@ describe("protocol router tests", () => { lpr.addInternalHandler("/page/bar", () => { called = 4; }); try { - expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined(); + expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } expect(called).toBe(1); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat"); + expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); }); }); diff --git a/src/main/protocol-handler/router.ts b/src/main/protocol-handler/router.ts index 087ab3fd88..25c7bbefba 100644 --- a/src/main/protocol-handler/router.ts +++ b/src/main/protocol-handler/router.ts @@ -25,6 +25,8 @@ import Url from "url-parse"; import type { LensExtension } from "../../extensions/lens-extension"; import { broadcastMessage } from "../../common/ipc"; import { observable, when, makeObservable } from "mobx"; +import { ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler"; +import { disposer } from "../../common/utils"; export interface FallbackHandler { (name: string): Promise; @@ -36,19 +38,25 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { @observable rendererLoaded = false; @observable extensionsLoaded = false; + protected disposers = disposer(); + constructor() { super(); makeObservable(this); } + public cleanup() { + this.disposers(); + } + /** * Find the most specific registered handler, if it exists, and invoke it. * * This will send an IPC message to the renderer router to do the same * in the renderer. */ - public async route(rawUrl: string): Promise { + public route(rawUrl: string) { try { const url = new Url(rawUrl, true); @@ -60,16 +68,18 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { switch (url.host) { case "app": - return this._routeToInternal(url); + this._routeToInternal(url); + break; case "extension": - await when(() => this.extensionsLoaded); - - return this._routeToExtension(url); + this.disposers.push(when(() => this.extensionsLoaded, () => this._routeToExtension(url))); + break; default: throw new proto.RoutingError(proto.RoutingErrorType.INVALID_HOST, url); } } catch (error) { + broadcastMessage(ProtocolHandlerInvalid, error.toString(), rawUrl); + if (error instanceof proto.RoutingError) { logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url }); } else { @@ -102,17 +112,16 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { return ""; } - protected async _routeToInternal(url: Url): Promise { + protected _routeToInternal(url: Url): RouteAttempt { const rawUrl = url.toString(); // for sending to renderer + const attempt = super._routeToInternal(url); - super._routeToInternal(url); + this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt))); - await when(() => this.rendererLoaded); - - return broadcastMessage(proto.ProtocolHandlerInternal, rawUrl); + return attempt; } - protected async _routeToExtension(url: Url): Promise { + protected async _routeToExtension(url: Url): Promise { const rawUrl = url.toString(); // for sending to renderer /** @@ -122,10 +131,11 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { * Note: this needs to clone the url because _routeToExtension modifies its * argument. */ - await super._routeToExtension(new Url(url.toString(), true)); - await when(() => this.rendererLoaded); + const attempt = await super._routeToExtension(new Url(url.toString(), true)); - return broadcastMessage(proto.ProtocolHandlerExtension, rawUrl); + this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt))); + + return attempt; } /** diff --git a/src/renderer/components/notifications/notifications.tsx b/src/renderer/components/notifications/notifications.tsx index b3c0f4cc25..c940ec469a 100644 --- a/src/renderer/components/notifications/notifications.tsx +++ b/src/renderer/components/notifications/notifications.tsx @@ -37,7 +37,7 @@ export class Notifications extends React.Component { static ok(message: NotificationMessage) { notificationsStore.add({ message, - timeout: 2500, + timeout: 2_500, status: NotificationStatus.OK }); } @@ -45,12 +45,19 @@ export class Notifications extends React.Component { static error(message: NotificationMessage, customOpts: Partial = {}) { notificationsStore.add({ message, - timeout: 10000, + timeout: 10_000, status: NotificationStatus.ERROR, ...customOpts }); } + static shortInfo(message: NotificationMessage, customOpts: Partial = {}) { + this.info(message, { + timeout: 5_000, + ...customOpts + }); + } + static info(message: NotificationMessage, customOpts: Partial = {}) { return notificationsStore.add({ status: NotificationStatus.INFO, diff --git a/src/renderer/protocol-handler/app-handlers.ts b/src/renderer/protocol-handler/app-handlers.tsx similarity index 83% rename from src/renderer/protocol-handler/app-handlers.ts rename to src/renderer/protocol-handler/app-handlers.tsx index bbd91ab19b..515acec7f5 100644 --- a/src/renderer/protocol-handler/app-handlers.ts +++ b/src/renderer/protocol-handler/app-handlers.tsx @@ -19,6 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import React from "react"; import { addClusterURL } from "../components/+add-cluster"; import { catalogURL } from "../components/+catalog"; import { attemptInstallByInfo, extensionsURL } from "../components/+extensions"; @@ -30,6 +31,7 @@ import { entitySettingsURL } from "../components/+entity-settings"; import { catalogEntityRegistry } from "../api/catalog-entity-registry"; import { ClusterStore } from "../../common/cluster-store"; import { EXTENSION_NAME_MATCH, EXTENSION_PUBLISHER_MATCH, LensProtocolRouter } from "../../common/protocol-handler"; +import { Notifications } from "../components/notifications"; export function bindProtocolAddRouteHandlers() { LensProtocolRouterRenderer @@ -37,7 +39,16 @@ export function bindProtocolAddRouteHandlers() { .addInternalHandler("/preferences", ({ search: { highlight }}) => { navigate(preferencesURL({ fragment: highlight })); }) - .addInternalHandler("/", () => { + .addInternalHandler("/", ({ tail }) => { + if (tail) { + Notifications.shortInfo( +

+ Unknown Action for lens://app/{tail}.{" "} + Are you on the latest version? +

+ ); + } + navigate(catalogURL()); }) .addInternalHandler("/landing", () => { @@ -59,7 +70,11 @@ export function bindProtocolAddRouteHandlers() { if (entity) { navigate(entitySettingsURL({ params: { entityId } })); } else { - console.log("[APP-HANDLER]: catalog entity with given ID does not exist", { entityId }); + Notifications.shortInfo( +

+ Unknown catalog entity {entityId}. +

+ ); } }) // Handlers below are deprecated and only kept for backward compact purposes @@ -69,7 +84,11 @@ export function bindProtocolAddRouteHandlers() { if (cluster) { navigate(clusterViewURL({ params: { clusterId } })); } else { - console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId }); + Notifications.shortInfo( +

+ Unknown catalog entity {clusterId}. +

+ ); } }) .addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => { @@ -78,7 +97,11 @@ export function bindProtocolAddRouteHandlers() { if (cluster) { navigate(entitySettingsURL({ params: { entityId: clusterId } })); } else { - console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId }); + Notifications.shortInfo( +

+ Unknown catalog entity {clusterId}. +

+ ); } }) .addInternalHandler("/extensions", () => { diff --git a/src/renderer/protocol-handler/router.ts b/src/renderer/protocol-handler/router.ts deleted file mode 100644 index 08aadd00d6..0000000000 --- a/src/renderer/protocol-handler/router.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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 { ipcRenderer } from "electron"; -import * as proto from "../../common/protocol-handler"; -import logger from "../../main/logger"; -import Url from "url-parse"; -import { boundMethod } from "../utils"; - -export class LensProtocolRouterRenderer extends proto.LensProtocolRouter { - /** - * This function is needed to be called early on in the renderers lifetime. - */ - public init(): void { - ipcRenderer - .on(proto.ProtocolHandlerInternal, this.ipcInternalHandler) - .on(proto.ProtocolHandlerExtension, this.ipcExtensionHandler); - } - - @boundMethod - private ipcInternalHandler(event: Electron.IpcRendererEvent, ...args: any[]): void { - if (args.length !== 1) { - return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args }); - } - - const [rawUrl] = args; - const url = new Url(rawUrl, true); - - this._routeToInternal(url); - } - - @boundMethod - private ipcExtensionHandler(event: Electron.IpcRendererEvent, ...args: any[]): void { - if (args.length !== 1) { - return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args }); - } - - const [rawUrl] = args; - const url = new Url(rawUrl, true); - - this._routeToExtension(url); - } -} diff --git a/src/renderer/protocol-handler/router.tsx b/src/renderer/protocol-handler/router.tsx new file mode 100644 index 0000000000..9e36a50561 --- /dev/null +++ b/src/renderer/protocol-handler/router.tsx @@ -0,0 +1,119 @@ +/** + * 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 { ipcRenderer } from "electron"; +import * as proto from "../../common/protocol-handler"; +import Url from "url-parse"; +import { onCorrect } from "../../common/ipc"; +import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler"; +import { Notifications } from "../components/notifications"; + +function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] { + if (args.length !== 2) { + return false; + } + + if (typeof args[0] !== "string") { + return false; + } + + switch (args[1]) { + case RouteAttempt.MATCHED: + case RouteAttempt.MISSING: + case RouteAttempt.MISSING_EXTENSION: + return true; + default: + return false; + } +} + +export class LensProtocolRouterRenderer extends proto.LensProtocolRouter { + /** + * This function is needed to be called early on in the renderers lifetime. + */ + public init(): void { + onCorrect({ + channel: proto.ProtocolHandlerInternal, + source: ipcRenderer, + verifier: verifyIpcArgs, + listener: (event, rawUrl, mainAttemptResult) => { + const rendererAttempt = this._routeToInternal(new Url(rawUrl, true)); + + if (foldAttemptResults(mainAttemptResult, rendererAttempt) === RouteAttempt.MISSING) { + Notifications.shortInfo( +

+ Unknown action {rawUrl}.{" "} + Are you on the latest version? +

+ ); + } + } + }); + onCorrect({ + channel: proto.ProtocolHandlerExtension, + source: ipcRenderer, + verifier: verifyIpcArgs, + listener: async (event, rawUrl, mainAttemptResult) => { + const rendererAttempt = await this._routeToExtension(new Url(rawUrl, true)); + + switch (foldAttemptResults(mainAttemptResult, rendererAttempt)) { + case RouteAttempt.MISSING: + Notifications.shortInfo( +

+ Unknown action {rawUrl}.{" "} + Are you on the latest version of the extension? +

+ ); + break; + case RouteAttempt.MISSING_EXTENSION: + Notifications.shortInfo( +

+ Missing extension for action {rawUrl}.{" "} + Not able to find extension in our known list.{" "} + Try installing it manually. +

+ ); + break; + } + } + }); + onCorrect({ + channel: ProtocolHandlerInvalid, + source: ipcRenderer, + listener: (event, error, rawUrl) => { + Notifications.error(( + <> +

+ Failed to route {rawUrl}. +

+

+ Error: {error} +

+ + )); + }, + verifier: (args): args is [string, string] => { + return args.length === 2 && typeof args[0] === "string"; + } + }); + } +} From e1be10b74d072940ff6a1fd6f379c8917498ada0 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Tue, 1 Jun 2021 16:05:41 +0300 Subject: [PATCH 2/6] Expose NamespaceSelectFilter component to Extensions API (#2934) Signed-off-by: Lauri Nevala --- src/extensions/renderer-api/components.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extensions/renderer-api/components.ts b/src/extensions/renderer-api/components.ts index 110b40bbb2..49b3ef933c 100644 --- a/src/extensions/renderer-api/components.ts +++ b/src/extensions/renderer-api/components.ts @@ -53,6 +53,7 @@ export * from "../../renderer/components/stepper"; export * from "../../renderer/components/wizard"; export * from "../../renderer/components/+workloads-pods/pod-details-list"; export * from "../../renderer/components/+namespaces/namespace-select"; +export * from "../../renderer/components/+namespaces/namespace-select-filter"; export * from "../../renderer/components/layout/sub-title"; export * from "../../renderer/components/input/search-input"; export * from "../../renderer/components/chart/bar-chart"; From 06612409ef7835db55c357ccc1d191a86345eee3 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Tue, 1 Jun 2021 16:32:42 +0300 Subject: [PATCH 3/6] Fix ClusterView bugs (#2928) Signed-off-by: Jari Kolehmainen --- .../cluster-manager/cluster-view.tsx | 29 +++++++++++-------- .../components/cluster-manager/lens-views.ts | 9 ++++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/renderer/components/cluster-manager/cluster-view.tsx b/src/renderer/components/cluster-manager/cluster-view.tsx index af719107e0..67f0088e69 100644 --- a/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/src/renderer/components/cluster-manager/cluster-view.tsx @@ -30,20 +30,26 @@ import { ClusterStore } from "../../../common/cluster-store"; import { requestMain } from "../../../common/ipc"; import { clusterActivateHandler } from "../../../common/cluster-ipc"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; -import { getMatchedClusterId, navigate } from "../../navigation"; +import { navigate } from "../../navigation"; import { catalogURL } from "../+catalog/catalog.route"; +import type { RouteComponentProps } from "react-router-dom"; +import type { IClusterViewRouteParams } from "./cluster-view.route"; + +interface Props extends RouteComponentProps { +} + @observer -export class ClusterView extends React.Component { +export class ClusterView extends React.Component { private store = ClusterStore.getInstance(); - constructor(props: {}) { + constructor(props: Props) { super(props); makeObservable(this); } - get clusterId() { - return getMatchedClusterId(); + @computed get clusterId() { + return this.props.match.params.clusterId; } @computed get cluster(): Cluster | undefined { @@ -71,14 +77,13 @@ export class ClusterView extends React.Component { fireImmediately: true, }), - reaction(() => this.isReady, (ready) => { - if (ready) { - refreshViews(this.clusterId); // show cluster's view (iframe) - } else if (hasLoadedView(this.clusterId)) { + reaction(() => [this.cluster?.ready, this.cluster?.disconnected], (values) => { + const disconnected = values[1]; + + if (hasLoadedView(this.clusterId) && disconnected) { + refreshViews(); navigate(catalogURL()); // redirect to catalog when active cluster get disconnected/not available } - }, { - fireImmediately: true, }), ]); } @@ -87,7 +92,7 @@ export class ClusterView extends React.Component { const { clusterId, cluster, isReady } = this; if (cluster && !isReady) { - return ; + return ; } return null; diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 1ec47a48fc..e6d636c318 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -56,8 +56,13 @@ export async function initView(clusterId: ClusterId) { parentElem.appendChild(iframe); logger.info(`[LENS-VIEW]: waiting cluster to be ready, clusterId=${clusterId}`); - await cluster.whenReady; - await autoCleanOnRemove(clusterId, iframe); + + try { + await when(() => cluster.ready, { timeout: 5_000 }); // we cannot wait forever because cleanup would be blocked for broken cluster connections + logger.info(`[LENS-VIEW]: cluster is ready, clusterId=${clusterId}`); + } finally { + await autoCleanOnRemove(clusterId, iframe); + } } export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) { From 043f411ad1c8ab5a46e43877410058f9bcfcc54c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Jun 2021 11:53:24 -0400 Subject: [PATCH 4/6] Fix getReleases returning inconsistent data types (#2924) --- src/main/helm/helm-release-manager.ts | 16 ++++++++-------- src/renderer/api/endpoints/helm-releases.api.ts | 11 +++++------ src/renderer/api/kube-object.ts | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 492474c151..9ec1237c07 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -22,7 +22,7 @@ import * as tempy from "tempy"; import fse from "fs-extra"; import * as yaml from "js-yaml"; -import { promiseExec} from "../promise-exec"; +import { promiseExec } from "../promise-exec"; import { helmCli } from "./helm-cli"; import type { Cluster } from "../cluster"; import { toCamelCase } from "../../common/utils/camelCase"; @@ -39,7 +39,7 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string) return output; } output.forEach((release: any, index: number) => { - output[index] = toCamelCase(release); + output[index] = toCamelCase(release); }); return output; @@ -49,9 +49,9 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string) } -export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string){ +export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string) { const helm = await helmCli.binaryPath(); - const fileName = tempy.file({name: "values.yaml"}); + const fileName = tempy.file({ name: "values.yaml" }); await fse.writeFile(fileName, yaml.safeDump(values)); @@ -81,7 +81,7 @@ export async function installChart(chart: string, values: any, name: string | un export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) { const helm = await helmCli.binaryPath(); - const fileName = tempy.file({name: "values.yaml"}); + const fileName = tempy.file({ name: "values.yaml" }); await fse.writeFile(fileName, yaml.safeDump(values)); @@ -119,7 +119,7 @@ export async function getRelease(name: string, namespace: string, cluster: Clust export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) { try { const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); + const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return stdout; } catch ({ stderr }) { @@ -173,8 +173,8 @@ async function getResources(name: string, namespace: string, cluster: Cluster) { const pathToKubeconfig = await cluster.getProxyKubeconfigPath(); const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`); - return stdout; + return JSON.parse(stdout).items; } catch { - return { stdout: JSON.stringify({ items: [] }) }; + return []; } } diff --git a/src/renderer/api/endpoints/helm-releases.api.ts b/src/renderer/api/endpoints/helm-releases.api.ts index 1eb5356465..9a4a05875c 100644 --- a/src/renderer/api/endpoints/helm-releases.api.ts +++ b/src/renderer/api/endpoints/helm-releases.api.ts @@ -28,6 +28,7 @@ import type { ItemObject } from "../../item.store"; import { KubeObject } from "../kube-object"; import type { JsonApiData } from "../json-api"; import { buildURLPositional } from "../../../common/utils/buildUrl"; +import type { KubeJsonApiData } from "../kube-json-api"; interface IReleasePayload { name: string; @@ -46,7 +47,7 @@ interface IReleasePayload { } interface IReleaseRawDetails extends IReleasePayload { - resources: string; + resources: KubeJsonApiData[]; } export interface IReleaseDetails extends IReleasePayload { @@ -102,10 +103,8 @@ export async function listReleases(namespace?: string): Promise { export async function getRelease(name: string, namespace: string): Promise { const path = endpoint({ name, namespace }); - - const details = await apiBase.get(path); - const items: KubeObject[] = JSON.parse(details.resources).items; - const resources = items.map(item => KubeObject.create(item)); + const { resources: rawResources, ...details } = await apiBase.get(path); + const resources = rawResources.map(KubeObject.create); return { ...details, @@ -194,7 +193,7 @@ export class HelmRelease implements ItemObject { getChart(withVersion = false) { let chart = this.chart; - if(!withVersion && this.getVersion() != "" ) { + if (!withVersion && this.getVersion() != "") { const search = new RegExp(`-${this.getVersion()}`); chart = chart.replace(search, ""); diff --git a/src/renderer/api/kube-object.ts b/src/renderer/api/kube-object.ts index 7b5b51cffc..b72beb9383 100644 --- a/src/renderer/api/kube-object.ts +++ b/src/renderer/api/kube-object.ts @@ -97,7 +97,7 @@ export class KubeObject(object: unknown, verifyItem:(val: unknown) => val is T): object is KubeJsonApiDataList { + static isJsonApiDataList(object: unknown, verifyItem: (val: unknown) => val is T): object is KubeJsonApiDataList { return ( isObject(object) && hasTypedProperty(object, "kind", isString) From 9e24ac69ec9fabb207e45ece757e4d638643a64f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 07:54:37 -0400 Subject: [PATCH 5/6] Bump mini-css-extract-plugin from 0.9.0 to 1.6.0 (#2925) Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 0.9.0 to 1.6.0. - [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v0.9.0...v1.6.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 40 +++++++--------------------------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 200f45defe..3a03167332 100644 --- a/package.json +++ b/package.json @@ -334,7 +334,7 @@ "jest-fetch-mock": "^3.0.3", "jest-mock-extended": "^1.0.10", "make-plural": "^6.2.2", - "mini-css-extract-plugin": "^0.9.0", + "mini-css-extract-plugin": "^1.6.0", "node-loader": "^0.6.0", "node-sass": "^4.14.1", "nodemon": "^2.0.4", diff --git a/yarn.lock b/yarn.lock index 37b5240b14..17da6aeb9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9711,14 +9711,13 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.5.5" tiny-warning "^1.0.3" -mini-css-extract-plugin@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== +mini-css-extract-plugin@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz#b4db2525af2624899ed64a23b0016e0036411893" + integrity sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw== dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" + loader-utils "^2.0.0" + schema-utils "^3.0.0" webpack-sources "^1.1.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: @@ -10266,16 +10265,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - normalize-url@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" @@ -11593,7 +11582,7 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.0, prepend-http@^1.0.1: +prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= @@ -11876,14 +11865,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -13244,13 +13225,6 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" From e7086a66d1e6c1fc2314cb660d92713f1c06bc7a Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Wed, 2 Jun 2021 21:15:30 +0300 Subject: [PATCH 6/6] update react-select types (#2930) Signed-off-by: Jari Kolehmainen --- package.json | 4 ++-- .../+namespaces/namespace-select-filter.tsx | 2 +- src/renderer/components/select/select.tsx | 6 +++--- yarn.lock | 16 ++++++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 3a03167332..af1d38edc8 100644 --- a/package.json +++ b/package.json @@ -282,7 +282,7 @@ "@types/react-beautiful-dnd": "^13.0.0", "@types/react-dom": "^17.0.0", "@types/react-router-dom": "^5.1.6", - "@types/react-select": "^3.0.13", + "@types/react-select": "3.1.2", "@types/react-table": "^7.7.0", "@types/react-window": "^1.8.2", "@types/readable-stream": "^2.3.9", @@ -349,7 +349,7 @@ "react-beautiful-dnd": "^13.1.0", "react-refresh": "^0.9.0", "react-router-dom": "^5.2.0", - "react-select": "^3.1.0", + "react-select": "3.1.1", "react-select-event": "^5.1.0", "react-table": "^7.7.0", "react-window": "^1.8.5", diff --git a/src/renderer/components/+namespaces/namespace-select-filter.tsx b/src/renderer/components/+namespaces/namespace-select-filter.tsx index ad2c1fe87b..25b14ae9e6 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.tsx +++ b/src/renderer/components/+namespaces/namespace-select-filter.tsx @@ -33,7 +33,7 @@ import { namespaceStore } from "./namespace.store"; import type { SelectOption, SelectProps } from "../select"; -const Placeholder = observer((props: PlaceholderProps) => { +const Placeholder = observer((props: PlaceholderProps) => { const getPlaceholder = (): React.ReactNode => { const namespaces = namespaceStore.contextNamespaces; diff --git a/src/renderer/components/select/select.tsx b/src/renderer/components/select/select.tsx index 3e5a55cef9..9dd5a61798 100644 --- a/src/renderer/components/select/select.tsx +++ b/src/renderer/components/select/select.tsx @@ -27,7 +27,7 @@ import React, { ReactNode } from "react"; import { computed, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { boundMethod, cssNames } from "../../utils"; -import ReactSelect, { ActionMeta, components, Props as ReactSelectProps, Styles } from "react-select"; +import ReactSelect, { ActionMeta, components, OptionTypeBase, Props as ReactSelectProps, Styles } from "react-select"; import Creatable, { CreatableProps } from "react-select/creatable"; import { ThemeStore } from "../../theme.store"; @@ -43,7 +43,7 @@ export interface SelectOption { label?: React.ReactNode; } -export interface SelectProps extends ReactSelectProps, CreatableProps { +export interface SelectProps extends ReactSelectProps, CreatableProps { value?: T; themeName?: "dark" | "light" | "outlined" | "lens"; menuClass?: string; @@ -69,7 +69,7 @@ export class Select extends React.Component { return this.props.themeName || ThemeStore.getInstance().activeTheme.type; } - private styles: Styles = { + private styles: Styles = { menuPortal: styles => ({ ...styles, zIndex: "auto" diff --git a/yarn.lock b/yarn.lock index 17da6aeb9e..534b2ed30a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1624,10 +1624,10 @@ "@types/history" "*" "@types/react" "*" -"@types/react-select@^3.0.13": - version "3.0.13" - resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.13.tgz#b1a05eae0f65fb4f899b4db1f89b8420cb9f3656" - integrity sha512-JxmSArGgzAOtb37+Jz2+3av8rVmp/3s3DGwlcP+g59/a3owkiuuU4/Jajd+qA32beDPHy4gJR2kkxagPY3j9kg== +"@types/react-select@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.1.2.tgz#38627df4b49be9b28f800ed72b35d830369a624b" + integrity sha512-ygvR/2FL87R2OLObEWFootYzkvm67LRA+URYEAcBuvKk7IXmdsnIwSGm60cVXGaqkJQHozb2Cy1t94tCYb6rJA== dependencies: "@types/react" "*" "@types/react-dom" "*" @@ -12059,10 +12059,10 @@ react-select-event@^5.1.0: dependencies: "@testing-library/dom" ">=7" -react-select@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27" - integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g== +react-select@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.1.tgz#156a5b4a6c22b1e3d62a919cb1fd827adb4060bc" + integrity sha512-HjC6jT2BhUxbIbxMZWqVcDibrEpdUJCfGicN0MMV+BQyKtCaPTgFekKWiOizSCy4jdsLMGjLqcFGJMhVGWB0Dg== dependencies: "@babel/runtime" "^7.4.4" "@emotion/cache" "^10.0.9"