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:
parent
595fb90a2a
commit
993230c60b
@ -349,6 +349,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",
|
||||
|
||||
@ -7,12 +7,17 @@ import { getLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extensi
|
||||
|
||||
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 = <
|
||||
TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>,
|
||||
TInstantiationParameter,
|
||||
OtherFields extends Record<string, () => any>,
|
||||
>(
|
||||
injectableKey: TInjectable,
|
||||
otherFields: Record<string | symbol, () => any>,
|
||||
otherFields: OtherFields,
|
||||
...instantiationParameter: TentativeTuple<TInstantiationParameter>
|
||||
) =>
|
||||
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<TInjectable["instantiate"]>;
|
||||
) as ReturnType<TInjectable["instantiate"]> & MapInjectables<OtherFields>;
|
||||
|
||||
@ -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<LogTabViewModelDependen
|
||||
});
|
||||
}
|
||||
|
||||
const getOnePodViewModel = (tabId: TabId): LogTabViewModel => {
|
||||
const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): 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<LogTabViewModelDependencies> = {}): 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("<LogResourceSelector />", () => {
|
||||
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("<LogResourceSelector />", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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) => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const LogControls = withInjectables<Dependencies, LogControlsProps>(NonInjectedLogControls, {
|
||||
getProps: (di, props) => ({
|
||||
openSaveFileDialog: di.inject(openSaveFileDialogInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -210,8 +210,6 @@ export class LogList extends React.Component<LogListProps> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const rowHeights = array.filled(this.logs.length, this.lineHeight);
|
||||
|
||||
if (!this.logs.length) {
|
||||
return (
|
||||
<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" )}>
|
||||
<VirtualList
|
||||
items={this.logs}
|
||||
rowHeights={rowHeights}
|
||||
rowHeights={array.filled(this.logs.length, this.lineHeight)}
|
||||
getRow={this.getLogRow}
|
||||
onScroll={this.onScroll}
|
||||
outerRef={this.virtualListDiv}
|
||||
|
||||
@ -12,15 +12,13 @@ import { Icon } from "../../icon";
|
||||
import type { LogTabViewModel } from "./logs-view-model";
|
||||
|
||||
export interface PodLogSearchProps {
|
||||
onSearch: (query: string) => 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 = (
|
||||
<div className="find-count">
|
||||
{activeFind}/{totalFinds}
|
||||
</div>
|
||||
);
|
||||
|
||||
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 && (
|
||||
<div className="find-count">
|
||||
{activeFind}/{totalFinds}
|
||||
</div>
|
||||
)}
|
||||
onClear={onClear}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
|
||||
@ -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
|
||||
<div className="flex gaps">
|
||||
<LogResourceSelector model={model} />
|
||||
<LogSearch
|
||||
onSearch={scrollToOverlay}
|
||||
model={model}
|
||||
toPrevOverlay={scrollToOverlay}
|
||||
toNextOverlay={scrollToOverlay}
|
||||
scrollToOverlay={scrollToOverlay}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
13
src/renderer/utils/save-file.injectable.ts
Normal file
13
src/renderer/utils/save-file.injectable.ts
Normal 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;
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user