From d908b56d3b884e64a1b9f08645b14f55e78cb529 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 10 Jun 2022 11:05:57 -0400 Subject: [PATCH] Release v5.5.4 (#5589) --- package.json | 2 +- src/common/k8s-api/endpoints/cluster.api.ts | 8 +++ src/common/terminal/channels.ts | 31 +++++++++ src/main/context-handler/context-handler.ts | 8 +-- src/main/helm/helm-chart-manager.ts | 7 +-- .../node-shell-session/node-shell-session.ts | 2 +- src/main/shell-session/shell-session.ts | 17 +++-- src/renderer/api/terminal-api.ts | 32 ++-------- src/renderer/api/websocket-api.ts | 21 ++++--- .../dock/terminal/send-command.injectable.ts | 2 +- .../components/dock/terminal/terminal.ts | 2 +- .../components/drawer/drawer-title.tsx | 7 ++- .../__snapshots__/tooltip.test.tsx.snap | 50 +++++++++++++++ .../components/tooltip/tooltip.test.tsx | 63 +++++++++++++++++++ src/renderer/components/tooltip/tooltip.tsx | 15 ++--- 15 files changed, 197 insertions(+), 70 deletions(-) create mode 100644 src/common/terminal/channels.ts create mode 100644 src/renderer/components/tooltip/__snapshots__/tooltip.test.tsx.snap create mode 100644 src/renderer/components/tooltip/tooltip.test.tsx diff --git a/package.json b/package.json index 8d7a471821..e958e7429f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.5.3", + "version": "5.5.4", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", diff --git a/src/common/k8s-api/endpoints/cluster.api.ts b/src/common/k8s-api/endpoints/cluster.api.ts index 377fb47207..f32058364c 100644 --- a/src/common/k8s-api/endpoints/cluster.api.ts +++ b/src/common/k8s-api/endpoints/cluster.api.ts @@ -10,7 +10,14 @@ import { KubeApi } from "../kube-api"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; export class ClusterApi extends KubeApi { + /** + * @deprecated This field is legacy and never used. + */ static kind = "Cluster"; + + /** + * @deprecated This field is legacy and never used. + */ static namespaced = true; } @@ -98,6 +105,7 @@ export interface Cluster { export class Cluster extends KubeObject { static kind = "Cluster"; static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters"; + static namespaced = true; getStatus() { if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING; diff --git a/src/common/terminal/channels.ts b/src/common/terminal/channels.ts new file mode 100644 index 0000000000..f958c9c696 --- /dev/null +++ b/src/common/terminal/channels.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + + +export enum TerminalChannels { + STDIN = "stdin", + STDOUT = "stdout", + CONNECTED = "connected", + RESIZE = "resize", + PING = "ping", +} + +export type TerminalMessage = { + type: TerminalChannels.STDIN; + data: string; +} | { + type: TerminalChannels.STDOUT; + data: string; +} | { + type: TerminalChannels.CONNECTED; +} | { + type: TerminalChannels.RESIZE; + data: { + width: number; + height: number; + }; +} | { + type: TerminalChannels.PING; +}; diff --git a/src/main/context-handler/context-handler.ts b/src/main/context-handler/context-handler.ts index 46e100c07e..fad22b5bde 100644 --- a/src/main/context-handler/context-handler.ts +++ b/src/main/context-handler/context-handler.ts @@ -141,17 +141,13 @@ export class ContextHandler { protected async newApiTarget(timeout: number): Promise { await this.ensureServer(); - const ca = this.dependencies.authProxyCa; - const clusterPath = this.clusterUrl.path !== "/" ? this.clusterUrl.path : ""; - const apiPrefix = `${this.kubeAuthProxy.apiPrefix}${clusterPath}`; - return { target: { protocol: "https:", host: "127.0.0.1", port: this.kubeAuthProxy.port, - path: apiPrefix, - ca, + path: this.kubeAuthProxy.apiPrefix, + ca: this.dependencies.authProxyCa, }, changeOrigin: true, timeout, diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index 5c15bfc6b8..9159c6868a 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -4,7 +4,6 @@ */ import fs from "fs"; -import v8 from "v8"; import * as yaml from "js-yaml"; import type { HelmRepo } from "./helm-repo-manager"; import logger from "../logger"; @@ -13,7 +12,7 @@ import { iter, sortCharts } from "../../common/utils"; import { execHelm } from "./exec"; interface ChartCacheEntry { - data: Buffer; + data: string; // serialized JSON mtimeMs: number; } @@ -77,7 +76,7 @@ export class HelmChartManager { const normalized = normalizeHelmCharts(this.repo.name, data.entries); HelmChartManager.#cache.set(this.repo.name, { - data: v8.serialize(normalized), + data: JSON.stringify(normalized), mtimeMs: cacheFileStats.mtimeMs, }); } @@ -94,7 +93,7 @@ export class HelmChartManager { } } - return v8.deserialize(HelmChartManager.#cache.get(this.repo.name).data); + return JSON.parse(HelmChartManager.#cache.get(this.repo.name).data); } } diff --git a/src/main/shell-session/node-shell-session/node-shell-session.ts b/src/main/shell-session/node-shell-session/node-shell-session.ts index 8c5861bff5..7b929865b9 100644 --- a/src/main/shell-session/node-shell-session/node-shell-session.ts +++ b/src/main/shell-session/node-shell-session/node-shell-session.ts @@ -13,8 +13,8 @@ import { get } from "lodash"; import { Node, NodesApi } from "../../../common/k8s-api/endpoints"; import { KubeJsonApi } from "../../../common/k8s-api/kube-json-api"; import logger from "../../logger"; -import { TerminalChannels } from "../../../renderer/api/terminal-api"; import type { Kubectl } from "../../kubectl/kubectl"; +import { TerminalChannels } from "../../../common/terminal/channels"; export class NodeShellSession extends ShellSession { ShellType = "node-shell"; diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 1cc8cd8cce..64bf9a973b 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -16,10 +16,8 @@ import { UserStore } from "../../common/user-store"; import * as pty from "node-pty"; import { appEventBus } from "../../common/app-event-bus/event-bus"; import logger from "../logger"; -import type { TerminalMessage } from "../../renderer/api/terminal-api"; -import { TerminalChannels } from "../../renderer/api/terminal-api"; -import { deserialize, serialize } from "v8"; import { stat } from "fs/promises"; +import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels"; export class ShellOpenError extends Error { constructor(message: string, public cause: Error) { @@ -163,7 +161,7 @@ export abstract class ShellSession { } protected send(message: TerminalMessage): void { - this.websocket.send(serialize(message)); + this.websocket.send(JSON.stringify(message)); } protected async getCwd(env: Record): Promise { @@ -234,17 +232,13 @@ export abstract class ShellSession { }); this.websocket - .on("message", (data: string | Uint8Array) => { + .on("message", (data: unknown): void => { if (!this.running) { return void logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`); } - if (typeof data === "string") { - return void logger.silly(`[SHELL-SESSION]: Received message from ${this.terminalId}`, { data }); - } - try { - const message: TerminalMessage = deserialize(data); + const message: TerminalMessage = JSON.parse(String(data)); switch (message.type) { case TerminalChannels.STDIN: @@ -253,6 +247,9 @@ export abstract class ShellSession { case TerminalChannels.RESIZE: shellProcess.resize(message.data.width, message.data.height); break; + case TerminalChannels.PING: + logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`); + break; default: logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message); break; diff --git a/src/renderer/api/terminal-api.ts b/src/renderer/api/terminal-api.ts index 1e8c86a77c..022f42bf84 100644 --- a/src/renderer/api/terminal-api.ts +++ b/src/renderer/api/terminal-api.ts @@ -11,31 +11,8 @@ import url from "url"; import { makeObservable, observable } from "mobx"; import { ipcRenderer } from "electron"; import logger from "../../common/logger"; -import { deserialize, serialize } from "v8"; import { once } from "lodash"; - -export enum TerminalChannels { - STDIN = "stdin", - STDOUT = "stdout", - CONNECTED = "connected", - RESIZE = "resize", -} - -export type TerminalMessage = { - type: TerminalChannels.STDIN; - data: string; -} | { - type: TerminalChannels.STDOUT; - data: string; -} | { - type: TerminalChannels.CONNECTED; -} | { - type: TerminalChannels.RESIZE; - data: { - width: number; - height: number; - }; -}; +import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels"; enum TerminalColor { RED = "\u001b[31m", @@ -126,11 +103,10 @@ export class TerminalApi extends WebSocketApi { this.prependListener("connected", onReady); super.connect(socketUrl); - this.socket.binaryType = "arraybuffer"; } sendMessage(message: TerminalMessage) { - return this.send(serialize(message)); + return this.send(JSON.stringify(message)); } sendTerminalSize(cols: number, rows: number) { @@ -145,9 +121,9 @@ export class TerminalApi extends WebSocketApi { } } - protected _onMessage({ data, ...evt }: MessageEvent): void { + protected _onMessage({ data, ...evt }: MessageEvent): void { try { - const message: TerminalMessage = deserialize(new Uint8Array(data)); + const message = JSON.parse(data) as TerminalMessage; switch (message.type) { case TerminalChannels.STDOUT: diff --git a/src/renderer/api/websocket-api.ts b/src/renderer/api/websocket-api.ts index 72ebc2d059..e4d51858f7 100644 --- a/src/renderer/api/websocket-api.ts +++ b/src/renderer/api/websocket-api.ts @@ -8,6 +8,7 @@ import EventEmitter from "events"; import type TypedEventEmitter from "typed-emitter"; import type { Arguments } from "typed-emitter"; import { isDevelopment } from "../../common/vars"; +import { TerminalChannels, type TerminalMessage } from "../../common/terminal/channels"; interface WebsocketApiParams { /** @@ -29,9 +30,9 @@ interface WebsocketApiParams { /** * The message for pinging the websocket * - * @default "PING" + * @default "{type: \"ping\"}" */ - pingMessage?: string | ArrayBufferLike | Blob | ArrayBufferView; + pingMessage?: string; /** * If set to a number > 0, then the API will ping the socket on that interval. @@ -65,10 +66,10 @@ export interface WebSocketEvents { type Defaulted = Required> & Omit; export class WebSocketApi extends (EventEmitter as { new(): TypedEventEmitter }) { - protected socket?: WebSocket | null; - protected pendingCommands: (string | ArrayBufferLike | Blob | ArrayBufferView)[] = []; - protected reconnectTimer?: any; - protected pingTimer?: any; + protected socket: WebSocket | null = null; + protected pendingCommands: string[] = []; + protected reconnectTimer?: number; + protected pingTimer?: number; protected params: Defaulted; @observable readyState = WebSocketApiState.PENDING; @@ -77,7 +78,7 @@ export class WebSocketApi extends (EventEmitter logging: isDevelopment, reconnectDelay: 10, flushOnOpen: true, - pingMessage: "PING", + pingMessage: JSON.stringify({ type: TerminalChannels.PING } as TerminalMessage), }; constructor(params: WebsocketApiParams) { @@ -87,7 +88,7 @@ export class WebSocketApi extends (EventEmitter const { pingInterval } = this.params; if (pingInterval) { - this.pingTimer = setInterval(() => this.ping(), pingInterval * 1000); + this.pingTimer = window.setInterval(() => this.ping(), pingInterval * 1000); } } @@ -143,7 +144,7 @@ export class WebSocketApi extends (EventEmitter } } - send(command: string | ArrayBufferLike | Blob | ArrayBufferView) { + send(command: string) { if (this.isConnected) { this.socket.send(command); } else { @@ -186,7 +187,7 @@ export class WebSocketApi extends (EventEmitter this.writeLog("will reconnect in", `${reconnectDelay}s`); - this.reconnectTimer = setTimeout(() => this.connect(url), reconnectDelay * 1000); + this.reconnectTimer = window.setTimeout(() => this.connect(url), reconnectDelay * 1000); this.readyState = WebSocketApiState.RECONNECTING; } } else { diff --git a/src/renderer/components/dock/terminal/send-command.injectable.ts b/src/renderer/components/dock/terminal/send-command.injectable.ts index edc0a2b7ed..f220b90255 100644 --- a/src/renderer/components/dock/terminal/send-command.injectable.ts +++ b/src/renderer/components/dock/terminal/send-command.injectable.ts @@ -4,8 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { when } from "mobx"; +import { TerminalChannels } from "../../../../common/terminal/channels"; import type { TerminalApi } from "../../../api/terminal-api"; -import { TerminalChannels } from "../../../api/terminal-api"; import { noop } from "../../../utils"; import { Notifications } from "../../notifications"; import selectDockTabInjectable from "../dock/select-dock-tab.injectable"; diff --git a/src/renderer/components/dock/terminal/terminal.ts b/src/renderer/components/dock/terminal/terminal.ts index d6f1701028..3742b9de3e 100644 --- a/src/renderer/components/dock/terminal/terminal.ts +++ b/src/renderer/components/dock/terminal/terminal.ts @@ -9,7 +9,6 @@ import { Terminal as XTerm } from "xterm"; import { FitAddon } from "xterm-addon-fit"; import type { TabId } from "../dock/store"; import type { TerminalApi } from "../../../api/terminal-api"; -import { TerminalChannels } from "../../../api/terminal-api"; import { ThemeStore } from "../../../theme.store"; import { disposer } from "../../../utils"; import { isMac } from "../../../../common/vars"; @@ -18,6 +17,7 @@ import { UserStore } from "../../../../common/user-store"; import { clipboard } from "electron"; import logger from "../../../../common/logger"; import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers"; +import { TerminalChannels } from "../../../../common/terminal/channels"; export class Terminal { private terminalConfig: TerminalConfig = UserStore.getInstance().terminalConfig; diff --git a/src/renderer/components/drawer/drawer-title.tsx b/src/renderer/components/drawer/drawer-title.tsx index 81be36f5ac..521ce78f5f 100644 --- a/src/renderer/components/drawer/drawer-title.tsx +++ b/src/renderer/components/drawer/drawer-title.tsx @@ -9,7 +9,12 @@ import { cssNames } from "../../utils"; export interface DrawerTitleProps { className?: string; - children: React.ReactNode; + children?: React.ReactNode; + + /** + * @deprecated Prefer passing the value as `children` + */ + title?: React.ReactNode; /** * Specifies how large this title is diff --git a/src/renderer/components/tooltip/__snapshots__/tooltip.test.tsx.snap b/src/renderer/components/tooltip/__snapshots__/tooltip.test.tsx.snap new file mode 100644 index 0000000000..253a79f941 --- /dev/null +++ b/src/renderer/components/tooltip/__snapshots__/tooltip.test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` does not render to DOM if not visibile 1`] = ` + +
+
+ Target Text +
+
+ +`; + +exports[` renders to DOM when forced to by visibile prop 1`] = ` + +
+ +
+ Target Text +
+
+ +`; + +exports[` renders to DOM when hovering over target 1`] = ` + +
+ +
+ Target Text +
+
+ +`; diff --git a/src/renderer/components/tooltip/tooltip.test.tsx b/src/renderer/components/tooltip/tooltip.test.tsx new file mode 100644 index 0000000000..0f51e5b2f1 --- /dev/null +++ b/src/renderer/components/tooltip/tooltip.test.tsx @@ -0,0 +1,63 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import assert from "assert"; +import React from "react"; +import { Tooltip } from "./tooltip"; + +describe("", () => { + it("does not render to DOM if not visibile", () => { + const result = render(( + <> + I am a tooltip +
Target Text
+ + )); + + expect(result.baseElement).toMatchSnapshot(); + }); + + it("renders to DOM when hovering over target", () => { + const result = render(( + <> + + I am a tooltip + +
Target Text
+ + )); + + const target = result.baseElement.querySelector("#my-target"); + + assert(target); + + userEvent.hover(target); + expect(result.baseElement).toMatchSnapshot(); + }); + + it("renders to DOM when forced to by visibile prop", () => { + const result = render(( + <> + + I am a tooltip + +
Target Text
+ + )); + + expect(result.baseElement).toMatchSnapshot(); + }); +}); diff --git a/src/renderer/components/tooltip/tooltip.tsx b/src/renderer/components/tooltip/tooltip.tsx index c244a09497..8c4686d672 100644 --- a/src/renderer/components/tooltip/tooltip.tsx +++ b/src/renderer/components/tooltip/tooltip.tsx @@ -53,9 +53,9 @@ const defaultProps: Partial = { export class Tooltip extends React.Component { static defaultProps = defaultProps as object; - @observable.ref elem: HTMLElement; - @observable activePosition: TooltipPosition; - @observable isVisible = this.props.visible ?? false; + @observable.ref elem: HTMLDivElement | null = null; + @observable activePosition?: TooltipPosition; + @observable isVisible = false; @observable isContentVisible = false; // animation manager constructor(props: TooltipProps) { @@ -219,18 +219,19 @@ export class Tooltip extends React.Component { }; } - bindRef(elem: HTMLElement) { + bindRef(elem: HTMLDivElement) { this.elem = elem; } render() { - if (!this.isVisible) { + const { style, formatters, usePortal, children, visible = this.isVisible } = this.props; + + if (!visible) { return null; } - const { style, formatters, usePortal, children } = this.props; const className = cssNames("Tooltip", this.props.className, formatters, this.activePosition, { - visible: this.isContentVisible, + visible: this.isContentVisible || this.props.visible, formatter: !!formatters, }); const tooltip = (