1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix build

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-01-26 10:00:22 -05:00
parent 595fb90a2a
commit 993230c60b
12 changed files with 93 additions and 194 deletions

View File

@ -349,6 +349,7 @@
"fork-ts-checker-webpack-plugin": "^5.2.1", "fork-ts-checker-webpack-plugin": "^5.2.1",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"html-webpack-plugin": "^4.5.2", "html-webpack-plugin": "^4.5.2",
"ignore-loader": "^0.1.2",
"include-media": "^1.4.9", "include-media": "^1.4.9",
"jest": "26.6.3", "jest": "26.6.3",
"jest-canvas-mock": "^2.3.1", "jest-canvas-mock": "^2.3.1",

View File

@ -7,12 +7,17 @@ import { getLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extensi
type TentativeTuple<T> = T extends object ? [T] : [undefined?]; type TentativeTuple<T> = T extends object ? [T] : [undefined?];
type MapInjectables<T> = {
[Key in keyof T]: T[Key] extends () => infer Res ? Res : never;
};
export const asLegacyGlobalObjectForExtensionApiWithModifications = < export const asLegacyGlobalObjectForExtensionApiWithModifications = <
TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>, TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>,
TInstantiationParameter, TInstantiationParameter,
OtherFields extends Record<string, () => any>,
>( >(
injectableKey: TInjectable, injectableKey: TInjectable,
otherFields: Record<string | symbol, () => any>, otherFields: OtherFields,
...instantiationParameter: TentativeTuple<TInstantiationParameter> ...instantiationParameter: TentativeTuple<TInstantiationParameter>
) => ) =>
new Proxy( new Proxy(
@ -28,7 +33,7 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = <
...instantiationParameter, ...instantiationParameter,
); );
const propertyValue = instance[propertyName] ?? otherFields[propertyName]?.(); const propertyValue = instance[propertyName] ?? otherFields[propertyName as any];
if (typeof propertyValue === "function") { if (typeof propertyValue === "function") {
return function (...args: any[]) { return function (...args: any[]) {
@ -39,4 +44,4 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = <
return propertyValue; return propertyValue;
}, },
}, },
) as ReturnType<TInjectable["instantiate"]>; ) as ReturnType<TInjectable["instantiate"]> & MapInjectables<OtherFields>;

View File

@ -19,6 +19,7 @@ import directoryForUserDataInjectable from "../../../../../common/app-paths/dire
import callForLogsInjectable from "../call-for-logs.injectable"; import callForLogsInjectable from "../call-for-logs.injectable";
import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model"; import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model";
import type { TabId } from "../../dock/store"; import type { TabId } from "../../dock/store";
import userEvent from "@testing-library/user-event";
jest.mock("electron", () => ({ jest.mock("electron", () => ({
app: { app: {
@ -57,7 +58,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependen
}); });
} }
const getOnePodViewModel = (tabId: TabId): LogTabViewModel => { const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): LogTabViewModel => {
const selectedPod = new Pod(dockerPod); const selectedPod = new Pod(dockerPod);
return mockLogTabViewModel(tabId, { return mockLogTabViewModel(tabId, {
@ -75,10 +76,11 @@ const getOnePodViewModel = (tabId: TabId): LogTabViewModel => {
return undefined; return undefined;
}, },
...deps,
}); });
}; };
const getFewPodsTabData = (tabId: TabId): LogTabViewModel => { const getFewPodsTabData = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): LogTabViewModel => {
const selectedPod = new Pod(deploymentPod1); const selectedPod = new Pod(deploymentPod1);
const anotherPod = new Pod(deploymentPod2); const anotherPod = new Pod(deploymentPod2);
@ -109,6 +111,7 @@ const getFewPodsTabData = (tabId: TabId): LogTabViewModel => {
return []; return [];
}, },
...deps,
}); });
}; };
@ -167,18 +170,15 @@ describe("<LogResourceSelector />", () => {
const { container, findByText } = render(getComponent(model)); const { container, findByText } = render(getComponent(model));
selectEvent.openMenu(container.querySelector(".pod-selector")); selectEvent.openMenu(container.querySelector(".pod-selector"));
expect(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
expect(await findByText("dockerExporter", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
expect(await findByText("deploymentPod1", { 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 () => { it("renders sibling containers in dropdown", async () => {
const model = getFewPodsTabData("foobar"); const model = getFewPodsTabData("foobar");
const { findByText, container } = render(getComponent(model)); 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("node-exporter-1")).toBeInTheDocument();
expect(await findByText("init-node-exporter")).toBeInTheDocument(); expect(await findByText("init-node-exporter")).toBeInTheDocument();
expect(await findByText("init-node-exporter-1")).toBeInTheDocument(); expect(await findByText("init-node-exporter-1")).toBeInTheDocument();
@ -187,10 +187,19 @@ describe("<LogResourceSelector />", () => {
it("renders pod owner as dropdown title", async () => { it("renders pod owner as dropdown title", async () => {
const model = getFewPodsTabData("foobar"); const model = getFewPodsTabData("foobar");
const { findByText, container } = render(getComponent(model)); 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(); 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();
});
}); });

View File

@ -0,0 +1,6 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export {};

View File

@ -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();
});
});

View File

@ -8,16 +8,22 @@ import "./controls.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cssNames, saveFileDialog } from "../../../utils"; import { cssNames } from "../../../utils";
import { Checkbox } from "../../checkbox"; import { Checkbox } from "../../checkbox";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
import type { LogTabViewModel } from "./logs-view-model"; import type { LogTabViewModel } from "./logs-view-model";
import { withInjectables } from "@ogre-tools/injectable-react";
import openSaveFileDialogInjectable from "../../../utils/save-file.injectable";
export interface LogControlsProps { export interface LogControlsProps {
model: LogTabViewModel; 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(); const tabData = model.logTabData.get();
if (!tabData) { if (!tabData) {
@ -43,7 +49,7 @@ export const LogControls = observer(({ model }: LogControlsProps) => {
? model.logs.get() ? model.logs.get()
: model.logsWithoutTimestamps.get(); : model.logsWithoutTimestamps.get();
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain"); openSaveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
}; };
return ( return (
@ -79,3 +85,10 @@ export const LogControls = observer(({ model }: LogControlsProps) => {
</div> </div>
); );
}); });
export const LogControls = withInjectables<Dependencies, LogControlsProps>(NonInjectedLogControls, {
getProps: (di, props) => ({
openSaveFileDialog: di.inject(openSaveFileDialogInjectable),
...props,
}),
});

View File

@ -210,8 +210,6 @@ export class LogList extends React.Component<LogListProps> {
}; };
render() { render() {
const rowHeights = array.filled(this.logs.length, this.lineHeight);
if (!this.logs.length) { if (!this.logs.length) {
return ( return (
<div className="LogList flex box grow align-center justify-center"> <div className="LogList flex box grow align-center justify-center">
@ -224,7 +222,7 @@ export class LogList extends React.Component<LogListProps> {
<div className={cssNames("LogList flex" )}> <div className={cssNames("LogList flex" )}>
<VirtualList <VirtualList
items={this.logs} items={this.logs}
rowHeights={rowHeights} rowHeights={array.filled(this.logs.length, this.lineHeight)}
getRow={this.getLogRow} getRow={this.getLogRow}
onScroll={this.onScroll} onScroll={this.onScroll}
outerRef={this.virtualListDiv} outerRef={this.virtualListDiv}

View File

@ -12,15 +12,13 @@ import { Icon } from "../../icon";
import type { LogTabViewModel } from "./logs-view-model"; import type { LogTabViewModel } from "./logs-view-model";
export interface PodLogSearchProps { export interface PodLogSearchProps {
onSearch: (query: string) => void; onSearch?: (query: string) => void;
toPrevOverlay: () => void; scrollToOverlay: (lineNumber: number | undefined) => void;
toNextOverlay: () => void;
model: LogTabViewModel; model: LogTabViewModel;
} }
export const LogSearch = observer(({ onSearch, scrollToOverlay, model: { logTabData, searchStore, ...model }}: PodLogSearchProps) => {
export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, model }: PodLogSearchProps) => { const tabData = logTabData.get();
const tabData = model.logTabData.get();
if (!tabData) { if (!tabData) {
return null; return null;
@ -29,27 +27,23 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod
const logs = tabData.showTimestamps const logs = tabData.showTimestamps
? model.logs.get() ? model.logs.get()
: model.logsWithoutTimestamps.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 jumpDisabled = !searchQuery || !occurrences.length;
const findCounts = (
<div className="find-count">
{activeFind}/{totalFinds}
</div>
);
const setSearch = (query: string) => { const setSearch = (query: string) => {
model.searchStore.onSearch(logs, query); searchStore.onSearch(logs, query);
onSearch(query); onSearch?.(query);
scrollToOverlay(searchStore.activeOverlayLine);
}; };
const onPrevOverlay = () => { const onPrevOverlay = () => {
setPrevOverlayActive(); setPrevOverlayActive();
toPrevOverlay(); scrollToOverlay(searchStore.activeOverlayLine);
}; };
const onNextOverlay = () => { const onNextOverlay = () => {
setNextOverlayActive(); setNextOverlayActive();
toNextOverlay(); scrollToOverlay(searchStore.activeOverlayLine);
}; };
const onClear = () => { const onClear = () => {
@ -64,7 +58,7 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod
useEffect(() => { useEffect(() => {
// Refresh search when logs changed // Refresh search when logs changed
model.searchStore.onSearch(logs); searchStore.onSearch(logs);
}, [logs]); }, [logs]);
return ( return (
@ -73,7 +67,11 @@ export const LogSearch = observer(({ onSearch, toPrevOverlay, toNextOverlay, mod
value={searchQuery} value={searchQuery}
onChange={setSearch} onChange={setSearch}
showClearIcon={true} showClearIcon={true}
contentRight={totalFinds > 0 && findCounts} contentRight={totalFinds > 0 && (
<div className="find-count">
{activeFind}/{totalFinds}
</div>
)}
onClear={onClear} onClear={onClear}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />

View File

@ -46,15 +46,13 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
namespaces: data ? [data.namespace] : [], namespaces: data ? [data.namespace] : [],
}), [data?.namespace]); }), [data?.namespace]);
const scrollToOverlay = () => { const scrollToOverlay = (overlayLine: number | undefined) => {
const { activeOverlayLine } = model.searchStore; if (!logListElement.current || overlayLine === undefined) {
if (!logListElement.current || activeOverlayLine === undefined) {
return; return;
} }
// Scroll vertically // 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 // Scroll horizontally in timeout since virtual list need some time to prepare its contents
setTimeout(() => { setTimeout(() => {
const overlay = document.querySelector(".PodLogs .list span.active"); const overlay = document.querySelector(".PodLogs .list span.active");
@ -76,10 +74,8 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
<div className="flex gaps"> <div className="flex gaps">
<LogResourceSelector model={model} /> <LogResourceSelector model={model} />
<LogSearch <LogSearch
onSearch={scrollToOverlay}
model={model} model={model}
toPrevOverlay={scrollToOverlay} scrollToOverlay={scrollToOverlay}
toNextOverlay={scrollToOverlay}
/> />
</div> </div>
)} )}

View File

@ -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;

View File

@ -34,6 +34,10 @@ export default function generateExtensionTypes(): webpack.Configuration {
stats: "errors-warnings", stats: "errors-warnings",
module: { module: {
rules: [ rules: [
{
test: /\.node$/,
loader: "ignore-loader",
},
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
loader: "ts-loader", loader: "ts-loader",

View File

@ -7135,6 +7135,11 @@ ignore-by-default@^1.0.1:
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= 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: ignore-walk@^3.0.1:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"