From d9a8d8fc8a58494276f3af29c1f16964c89277f8 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 26 Jan 2022 10:00:22 -0500 Subject: [PATCH] Fix build Signed-off-by: Sebastian Malton --- package.json | 1 + ...ct-for-extension-api-with-modifications.ts | 11 +- .../__test__/log-resource-selector.test.tsx | 29 ++-- .../dock/logs/__test__/log-search.test.tsx | 6 + .../dock/logs/__test__/log-tab.store.test.ts | 149 ------------------ .../components/dock/logs/controls.tsx | 19 ++- src/renderer/components/dock/logs/list.tsx | 4 +- src/renderer/components/dock/logs/search.tsx | 34 ++-- src/renderer/components/dock/logs/view.tsx | 12 +- src/renderer/utils/save-file.injectable.ts | 13 ++ webpack.extensions.ts | 4 + yarn.lock | 5 + 12 files changed, 93 insertions(+), 194 deletions(-) create mode 100644 src/renderer/components/dock/logs/__test__/log-search.test.tsx delete mode 100644 src/renderer/components/dock/logs/__test__/log-tab.store.test.ts create mode 100644 src/renderer/utils/save-file.injectable.ts diff --git a/package.json b/package.json index 03ff28fa93..14da4ac0c9 100644 --- a/package.json +++ b/package.json @@ -351,6 +351,7 @@ "fork-ts-checker-webpack-plugin": "^5.2.1", "hoist-non-react-statics": "^3.3.2", "html-webpack-plugin": "^4.5.2", + "ignore-loader": "^0.1.2", "include-media": "^1.4.9", "jest": "26.6.3", "jest-canvas-mock": "^2.3.1", diff --git a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts index 4d79cb9b42..c58152716f 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts @@ -7,12 +7,17 @@ import { getLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extensi type TentativeTuple = T extends object ? [T] : [undefined?]; +type MapInjectables = { + [Key in keyof T]: T[Key] extends () => infer Res ? Res : never; +}; + export const asLegacyGlobalObjectForExtensionApiWithModifications = < TInjectable extends Injectable, TInstantiationParameter, + OtherFields extends Record any>, >( injectableKey: TInjectable, - otherFields: Record any>, + otherFields: OtherFields, ...instantiationParameter: TentativeTuple ) => new Proxy( @@ -28,7 +33,7 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = < ...instantiationParameter, ); - const propertyValue = instance[propertyName] ?? otherFields[propertyName]?.(); + const propertyValue = instance[propertyName] ?? otherFields[propertyName as any]; if (typeof propertyValue === "function") { return function (...args: any[]) { @@ -39,4 +44,4 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = < return propertyValue; }, }, - ) as ReturnType; + ) as ReturnType & MapInjectables; diff --git a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx index b4ca553a81..762e915a63 100644 --- a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx +++ b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx @@ -19,6 +19,7 @@ import directoryForUserDataInjectable from "../../../../../common/app-paths/dire import callForLogsInjectable from "../call-for-logs.injectable"; import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model"; import type { TabId } from "../../dock/store"; +import userEvent from "@testing-library/user-event"; jest.mock("electron", () => ({ app: { @@ -57,7 +58,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial { +const getOnePodViewModel = (tabId: TabId, deps: Partial = {}): LogTabViewModel => { const selectedPod = new Pod(dockerPod); return mockLogTabViewModel(tabId, { @@ -75,10 +76,11 @@ const getOnePodViewModel = (tabId: TabId): LogTabViewModel => { return undefined; }, + ...deps, }); }; -const getFewPodsTabData = (tabId: TabId): LogTabViewModel => { +const getFewPodsTabData = (tabId: TabId, deps: Partial = {}): LogTabViewModel => { const selectedPod = new Pod(deploymentPod1); const anotherPod = new Pod(deploymentPod2); @@ -109,6 +111,7 @@ const getFewPodsTabData = (tabId: TabId): LogTabViewModel => { return []; }, + ...deps, }); }; @@ -167,18 +170,15 @@ describe("", () => { const { container, findByText } = render(getComponent(model)); selectEvent.openMenu(container.querySelector(".pod-selector")); - - expect(await findByText("dockerExporter", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument(); + expect(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument(); expect(await findByText("deploymentPod1", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument(); }); it("renders sibling containers in dropdown", async () => { const model = getFewPodsTabData("foobar"); const { findByText, container } = render(getComponent(model)); - const containerSelector: HTMLElement = container.querySelector(".container-selector"); - - selectEvent.openMenu(containerSelector); + selectEvent.openMenu(container.querySelector(".container-selector")); expect(await findByText("node-exporter-1")).toBeInTheDocument(); expect(await findByText("init-node-exporter")).toBeInTheDocument(); expect(await findByText("init-node-exporter-1")).toBeInTheDocument(); @@ -187,10 +187,19 @@ describe("", () => { it("renders pod owner as dropdown title", async () => { const model = getFewPodsTabData("foobar"); const { findByText, container } = render(getComponent(model)); - const podSelector: HTMLElement = container.querySelector(".pod-selector"); - - selectEvent.openMenu(podSelector); + selectEvent.openMenu(container.querySelector(".pod-selector")); expect(await findByText("super-deployment")).toBeInTheDocument(); }); + + it("updates tab name if selected pod changes", async () => { + const updateTabName = jest.fn(); + const model = getFewPodsTabData("foobar", { updateTabName }); + const { findByText, container } = render(getComponent(model)); + + selectEvent.openMenu(container.querySelector(".pod-selector")); + + userEvent.click(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" })); + expect(updateTabName).toBeCalled(); + }); }); diff --git a/src/renderer/components/dock/logs/__test__/log-search.test.tsx b/src/renderer/components/dock/logs/__test__/log-search.test.tsx new file mode 100644 index 0000000000..a72859300e --- /dev/null +++ b/src/renderer/components/dock/logs/__test__/log-search.test.tsx @@ -0,0 +1,6 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export {}; diff --git a/src/renderer/components/dock/logs/__test__/log-tab.store.test.ts b/src/renderer/components/dock/logs/__test__/log-tab.store.test.ts deleted file mode 100644 index d493e667a2..0000000000 --- a/src/renderer/components/dock/logs/__test__/log-tab.store.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { podsStore } from "../../../+workloads-pods/pods.store"; -import { UserStore } from "../../../../../common/user-store"; -import { Pod } from "../../../../../common/k8s-api/endpoints"; -import { ThemeStore } from "../../../../theme.store"; -import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock"; -import { mockWindow } from "../../../../../../__mocks__/windowMock"; -import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; -import logTabStoreInjectable from "../tab-store.injectable"; -import type { LogTabStore } from "../tab-store"; -import dockStoreInjectable from "../../dock/store.injectable"; -import type { DockStore, TabId } from "../../dock/store"; -import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import mockFs from "mock-fs"; -import type { PodLogsTabData } from "../create-pod-logs-tab.injectable"; -import createPodLogsTabInjectable from "../create-pod-logs-tab.injectable"; - -mockWindow(); - -podsStore.items.push(new Pod(dockerPod)); -podsStore.items.push(new Pod(deploymentPod1)); -podsStore.items.push(new Pod(deploymentPod2)); - -describe("log tab store", () => { - let logTabStore: LogTabStore; - let dockStore: DockStore; - let createPodTab: ({ selectedPod, selectedContainer }: PodLogsTabData) => TabId; - - beforeEach(async () => { - const di = getDiForUnitTesting({ doGeneralOverrides: true }); - - mockFs(); - - di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); - - await di.runSetups(); - - dockStore = di.inject(dockStoreInjectable); - logTabStore = di.inject(logTabStoreInjectable); - createPodTab = di.inject(createPodLogsTabInjectable); - - UserStore.createInstance(); - ThemeStore.createInstance(); - }); - - afterEach(() => { - UserStore.resetInstance(); - ThemeStore.resetInstance(); - mockFs.restore(); - }); - - it("creates log tab without sibling pods", () => { - const selectedPod = new Pod(dockerPod); - const selectedContainer = selectedPod.getAllContainers()[0]; - - createPodTab({ - selectedPod, - selectedContainer, - }); - - expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({ - pods: [selectedPod], - selectedPod, - selectedContainer, - showTimestamps: false, - previous: false, - }); - }); - - it("creates log tab with sibling pods", () => { - const selectedPod = new Pod(deploymentPod1); - const siblingPod = new Pod(deploymentPod2); - const selectedContainer = selectedPod.getInitContainers()[0]; - - createPodTab({ - selectedPod, - selectedContainer, - }); - - expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({ - pods: [selectedPod, siblingPod], - selectedPod, - selectedContainer, - showTimestamps: false, - previous: false, - }); - }); - - it("removes item from pods list if pod deleted from store", () => { - const selectedPod = new Pod(deploymentPod1); - const selectedContainer = selectedPod.getInitContainers()[0]; - - createPodTab({ - selectedPod, - selectedContainer, - }); - - podsStore.items.pop(); - - expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({ - pods: [selectedPod], - selectedPod, - selectedContainer, - showTimestamps: false, - previous: false, - }); - }); - - it("adds item into pods list if new sibling pod added to store", () => { - const selectedPod = new Pod(deploymentPod1); - const selectedContainer = selectedPod.getInitContainers()[0]; - - createPodTab({ - selectedPod, - selectedContainer, - }); - - podsStore.items.push(new Pod(deploymentPod3)); - - expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({ - pods: [selectedPod, deploymentPod3], - selectedPod, - selectedContainer, - showTimestamps: false, - previous: false, - }); - }); - - // FIXME: this is failed when it's not .only == depends on something above - it.only("closes tab if no pods left in store", async () => { - const selectedPod = new Pod(deploymentPod1); - const selectedContainer = selectedPod.getInitContainers()[0]; - - const id = createPodTab({ - selectedPod, - selectedContainer, - }); - - podsStore.items.clear(); - - expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined(); - expect(logTabStore.getData(id)).toBeUndefined(); - expect(dockStore.getTabById(id)).toBeUndefined(); - }); -}); diff --git a/src/renderer/components/dock/logs/controls.tsx b/src/renderer/components/dock/logs/controls.tsx index e3348cdb27..59aead384b 100644 --- a/src/renderer/components/dock/logs/controls.tsx +++ b/src/renderer/components/dock/logs/controls.tsx @@ -8,16 +8,22 @@ import "./controls.scss"; import React from "react"; import { observer } from "mobx-react"; -import { cssNames, saveFileDialog } from "../../../utils"; +import { cssNames } from "../../../utils"; import { Checkbox } from "../../checkbox"; import { Icon } from "../../icon"; import type { LogTabViewModel } from "./logs-view-model"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import openSaveFileDialogInjectable from "../../../utils/save-file.injectable"; export interface LogControlsProps { model: LogTabViewModel; } -export const LogControls = observer(({ model }: LogControlsProps) => { +interface Dependencies { + openSaveFileDialog: (filename: string, contents: BlobPart | BlobPart[], type: string) => void; +} + +const NonInjectedLogControls = observer(({ openSaveFileDialog, model }: Dependencies & LogControlsProps) => { const tabData = model.logTabData.get(); if (!tabData) { @@ -43,7 +49,7 @@ export const LogControls = observer(({ model }: LogControlsProps) => { ? model.logs.get() : model.logsWithoutTimestamps.get(); - saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain"); + openSaveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain"); }; return ( @@ -79,3 +85,10 @@ export const LogControls = observer(({ model }: LogControlsProps) => { ); }); + +export const LogControls = withInjectables(NonInjectedLogControls, { + getProps: (di, props) => ({ + openSaveFileDialog: di.inject(openSaveFileDialogInjectable), + ...props, + }), +}); diff --git a/src/renderer/components/dock/logs/list.tsx b/src/renderer/components/dock/logs/list.tsx index 358889f2f3..42e8490a27 100644 --- a/src/renderer/components/dock/logs/list.tsx +++ b/src/renderer/components/dock/logs/list.tsx @@ -212,8 +212,6 @@ export class LogList extends React.Component { }; render() { - const rowHeights = array.filled(this.logs.length, this.lineHeight); - if (!this.logs.length) { return (
@@ -226,7 +224,7 @@ export class LogList extends React.Component {
void; - toPrevOverlay: () => void; - toNextOverlay: () => void; + onSearch?: (query: string) => void; + scrollToOverlay: (lineNumber: number | undefined) => void; model: LogTabViewModel; } - -export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, model }: PodLogSearchProps) => { - const tabData = model.logTabData.get(); +export const LogSearch = observer(({ onSearch, scrollToOverlay, model: { logTabData, searchStore, ...model }}: PodLogSearchProps) => { + const tabData = logTabData.get(); if (!tabData) { return null; @@ -29,27 +27,23 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod const logs = tabData.showTimestamps ? model.logs.get() : model.logsWithoutTimestamps.get(); - const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = model.searchStore; + const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore; const jumpDisabled = !searchQuery || !occurrences.length; - const findCounts = ( -
- {activeFind}/{totalFinds} -
- ); const setSearch = (query: string) => { - model.searchStore.onSearch(logs, query); - onSearch(query); + searchStore.onSearch(logs, query); + onSearch?.(query); + scrollToOverlay(searchStore.activeOverlayLine); }; const onPrevOverlay = () => { setPrevOverlayActive(); - toPrevOverlay(); + scrollToOverlay(searchStore.activeOverlayLine); }; const onNextOverlay = () => { setNextOverlayActive(); - toNextOverlay(); + scrollToOverlay(searchStore.activeOverlayLine); }; const onClear = () => { @@ -64,7 +58,7 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod useEffect(() => { // Refresh search when logs changed - model.searchStore.onSearch(logs); + searchStore.onSearch(logs); }, [logs]); return ( @@ -73,7 +67,11 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod value={searchQuery} onChange={setSearch} showClearIcon={true} - contentRight={totalFinds > 0 && findCounts} + contentRight={totalFinds > 0 && ( +
+ {activeFind}/{totalFinds} +
+ )} onClear={onClear} onKeyDown={onKeyDown} /> diff --git a/src/renderer/components/dock/logs/view.tsx b/src/renderer/components/dock/logs/view.tsx index 083f4feca4..c73933b12f 100644 --- a/src/renderer/components/dock/logs/view.tsx +++ b/src/renderer/components/dock/logs/view.tsx @@ -46,15 +46,13 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore namespaces: data ? [data.namespace] : [], }), [data?.namespace]); - const scrollToOverlay = () => { - const { activeOverlayLine } = model.searchStore; - - if (!logListElement.current || activeOverlayLine === undefined) { + const scrollToOverlay = (overlayLine: number | undefined) => { + if (!logListElement.current || overlayLine === undefined) { return; } // Scroll vertically - logListElement.current.scrollToItem(activeOverlayLine, "center"); + logListElement.current.scrollToItem(overlayLine, "center"); // Scroll horizontally in timeout since virtual list need some time to prepare its contents setTimeout(() => { const overlay = document.querySelector(".PodLogs .list span.active"); @@ -76,10 +74,8 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
)} diff --git a/src/renderer/utils/save-file.injectable.ts b/src/renderer/utils/save-file.injectable.ts new file mode 100644 index 0000000000..593c6955dc --- /dev/null +++ b/src/renderer/utils/save-file.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { saveFileDialog } from "./saveFile"; + +const openSaveFileDialogInjectable = getInjectable({ + instantiate: () => saveFileDialog, + lifecycle: lifecycleEnum.singleton, +}); + +export default openSaveFileDialogInjectable; diff --git a/webpack.extensions.ts b/webpack.extensions.ts index a4b06bf9f8..330684adb4 100644 --- a/webpack.extensions.ts +++ b/webpack.extensions.ts @@ -34,6 +34,10 @@ export default function generateExtensionTypes(): webpack.Configuration { stats: "errors-warnings", module: { rules: [ + { + test: /\.node$/, + loader: "ignore-loader", + }, { test: /\.tsx?$/, loader: "ts-loader", diff --git a/yarn.lock b/yarn.lock index db6e481d1f..77d764e595 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7184,6 +7184,11 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= +ignore-loader@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463" + integrity sha1-2B8kA3bQuk8Nd4lyw60lh0EXpGM= + ignore-walk@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"