Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log search #1114

Merged
merged 46 commits into from Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
edf59c9
Moving logs to virtual list
aleksfront Oct 20, 2020
7234d2e
Introducing log search
aleksfront Oct 20, 2020
118ed20
Setting ref for VirtualList to access its methods
aleksfront Oct 21, 2020
5c084b8
Introducing search store
aleksfront Oct 21, 2020
f8548d6
Centering overlay when scroll to it
aleksfront Oct 21, 2020
93ae4b6
Using SearchInput in PodLogSearch
aleksfront Oct 21, 2020
7ac13f5
Using Prev/Next icons for search
aleksfront Oct 21, 2020
14e88a5
No trigger logs load when scrolled by method
aleksfront Oct 22, 2020
de33f52
SearchInput refactoring
aleksfront Oct 22, 2020
cfb6c3c
Adding find counters
aleksfront Oct 22, 2020
45b6faf
Clean search query on dock tab change
aleksfront Oct 22, 2020
a6eb283
Refresh search when logs get changed
aleksfront Oct 22, 2020
e3a3fb4
Case-insensitive search
aleksfront Oct 22, 2020
01e485d
Improve logs scrolling experience
aleksfront Oct 22, 2020
6bb874d
Catching empty logs in various places
aleksfront Oct 22, 2020
4b0b635
Fixing downloading logs
aleksfront Oct 22, 2020
d5ed79d
Clean up some duplicated styles
aleksfront Oct 22, 2020
c099711
Removing jump-to-bottom animation
aleksfront Oct 22, 2020
c962ea6
Fixing since label
aleksfront Oct 22, 2020
99b6bab
Reducing container selector size
aleksfront Oct 22, 2020
fcf9422
Scroll down to bottom after each reload
aleksfront Oct 23, 2020
de536b8
Fix search within timestamps if they not provided
aleksfront Oct 23, 2020
d736f24
Use log row hover color from theme
aleksfront Oct 23, 2020
2e2eec7
Add search bindings for 'Esc' & 'Enter' hits
aleksfront Oct 26, 2020
b0c0794
Merge branch 'master' into logs-search
aleksfront Oct 30, 2020
61a2936
Merge branch 'master' into logs-search
aleksfront Oct 30, 2020
bc8a688
Focus input fields on CmdOrCtrl+F
aleksfront Oct 30, 2020
16cde00
Move search.store.ts in to /common folder
aleksfront Nov 1, 2020
d262c76
search.store.ts -> search-store.ts
aleksfront Nov 1, 2020
4ec29ce
Adding test for search store
aleksfront Nov 1, 2020
a054ae2
Adding integration tests for logs
aleksfront Nov 2, 2020
139c114
Fixing scroll jumping bug
aleksfront Nov 2, 2020
83c760d
Removing download icon check for testing purpose
aleksfront Nov 2, 2020
ea96a7f
Merge branch 'master' into logs-search
aleksfront Nov 2, 2020
858acc1
Removing clicking on nginx-create-pod-test
aleksfront Nov 3, 2020
94ed372
Moving log tests before cluster operations
aleksfront Nov 3, 2020
da12885
Build extensions before integration tests
nevalla Nov 3, 2020
cc3af77
Build also npm before integration tests
nevalla Nov 3, 2020
6283daa
Move npm build and extension build into own build step
nevalla Nov 3, 2020
4e1b901
Merge branch 'master' into logs-search
aleksfront Nov 4, 2020
cc8902c
Removing separator sketches
aleksfront Nov 4, 2020
c3c2e52
Horizontal scrolling to founded keyword
aleksfront Nov 5, 2020
4caddf5
Merge branch 'master' into logs-search
aleksfront Nov 5, 2020
871173e
Delaying horizontal scrolling
aleksfront Nov 5, 2020
b67d367
Merge branch 'master' into logs-search
aleksfront Nov 9, 2020
eb4435e
Merge branch 'master' into logs-search
aleksfront Nov 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions .azure-pipelines.yml
Expand Up @@ -37,6 +37,10 @@ jobs:
displayName: Cache Yarn packages
- script: make install-deps
displayName: Install dependencies
- script: make build-npm
displayName: Generate npm package
- script: make build-extensions
displayName: Build bundled extensions
- script: make integration-win
displayName: Run integration tests
- script: make build
Expand Down Expand Up @@ -74,6 +78,10 @@ jobs:
condition: eq(variables.CACHE_RESTORED, 'true')
- script: make install-deps
displayName: Install dependencies
- script: make build-npm
displayName: Generate npm package
- script: make build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
- script: make integration-mac
Expand Down Expand Up @@ -121,6 +129,10 @@ jobs:
displayName: Install dependencies
- script: make lint
displayName: Lint
- script: make build-npm
displayName: Generate npm package
- script: make build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
- bash: |
Expand Down
34 changes: 34 additions & 0 deletions integration/__tests__/app.tests.ts
Expand Up @@ -410,6 +410,40 @@ describe("Lens integration tests", () => {
})
})

describe("viewing pod logs", () => {
beforeEach(appStartAddCluster, 40000)

afterEach(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
}
})

it(`shows a logs for a pod`, async () => {
expect(clusterAdded).toBe(true)
// Go to Pods page
await app.client.click(".sidebar-nav #workloads span.link-text")
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
// Open logs tab in dock
await app.client.click(".list .TableRow:first-child")
await app.client.waitForVisible(".Drawer")
await app.client.click(".drawer-title .Menu li:nth-child(2)")
// Check if controls are available
await app.client.waitForVisible(".PodLogs .VirtualList")
await app.client.waitForVisible(".PodLogControls")
await app.client.waitForVisible(".PodLogControls .SearchInput")
await app.client.waitForVisible(".PodLogControls .SearchInput input")
// Search for semicolon
await app.client.keys(":")
await app.client.waitForVisible(".PodLogs .list span.active")
// Click through controls
await app.client.click(".PodLogControls .timestamps-icon")
await app.client.click(".PodLogControls .undo-icon")
})
})

describe("cluster operations", () => {
beforeEach(appStartAddCluster, 40000)

Expand Down
80 changes: 80 additions & 0 deletions src/common/__tests__/search-store.test.ts
@@ -0,0 +1,80 @@
/**
* @jest-environment jsdom
*/

import { SearchStore } from "../search-store"

let searchStore: SearchStore = null;

const logs = [
"1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost",
"1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization",
"1:M 30 Oct 2020 16:17:41.623 * Starting Partial resynchronization request from 172.17.0.12:6379 accepted. Sending 0 bytes of backlog starting from offset 14407."
]

describe("search store tests", () => {
beforeEach(async () => {
searchStore = new SearchStore();
})

it("does nothing with empty search query", () => {
searchStore.onSearch([], "");
expect(searchStore.occurrences).toEqual([]);
})

it("doesn't brake if no text provided", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

break?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course.

searchStore.onSearch(null, "replica");
expect(searchStore.occurrences).toEqual([]);

searchStore.onSearch([], "replica");
expect(searchStore.occurrences).toEqual([]);
})

it("find 3 occurences across 3 lines", () => {
searchStore.onSearch(logs, "172");
expect(searchStore.occurrences).toEqual([0, 1, 2]);
})

it("find occurences within 1 line (case-insensitive)", () => {
searchStore.onSearch(logs, "Starting");
expect(searchStore.occurrences).toEqual([2, 2]);
})

it("sets overlay index equal to first occurence", () => {
searchStore.onSearch(logs, "Replica");
expect(searchStore.activeOverlayIndex).toBe(0);
})

it("set overlay index to next occurence", () => {
searchStore.onSearch(logs, "172");
searchStore.setNextOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(1);
})

it("sets overlay to last occurence", () => {
searchStore.onSearch(logs, "172");
searchStore.setPrevOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(2);
})

it("gets line index where overlay is located", () => {
searchStore.onSearch(logs, "synchronization");
expect(searchStore.activeOverlayLine).toBe(1);
})

it("escapes string for using in regex", () => {
const regex = searchStore.escapeRegex("some.interesting-query\\#?()[]");
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
})

it("gets active find number", () => {
searchStore.onSearch(logs, "172");
searchStore.setNextOverlayActive();
expect(searchStore.activeFind).toBe(2);
})

it("gets total finds number", () => {
searchStore.onSearch(logs, "Starting");
expect(searchStore.totalFinds).toBe(2);
})
})
@@ -1,5 +1,5 @@
import { action, computed, observable } from "mobx";
import { autobind } from "../../utils";
import { autobind } from "../renderer/utils";

export class SearchStore {
@observable searchQuery = ""; // Text in the search input
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/dock/pod-log-controls.tsx
Expand Up @@ -112,6 +112,7 @@ export const PodLogControls = observer((props: Props) => {
material="get_app"
onClick={downloadLogs}
tooltip={_i18n._(t`Save`)}
className="download-icon"
/>
<PodLogSearch {...props} />
</div>
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/components/dock/pod-log-search.tsx
Expand Up @@ -3,7 +3,7 @@ import "./pod-log-search.scss";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { SearchInput } from "../input";
import { searchStore } from "./search.store";
import { searchStore } from "../../../common/search-store";
import { Icon } from "../icon";
import { _i18n } from "../../i18n";
import { t } from "@lingui/macro";
Expand Down Expand Up @@ -40,6 +40,16 @@ export const PodLogSearch = observer((props: PodLogSearchProps) => {
toNextOverlay();
}

const onClear = () => {
setSearch("");
}

const onKeyDown = (evt: React.KeyboardEvent<any>) => {
if (evt.key === "Enter") {
onNextOverlay();
}
}

useEffect(() => {
// Refresh search when logs changed
searchStore.onSearch(logs);
Expand All @@ -52,6 +62,8 @@ export const PodLogSearch = observer((props: PodLogSearchProps) => {
onChange={setSearch}
closeIcon={false}
contentRight={totalFinds > 0 && findCounts}
onClear={onClear}
onKeyDown={onKeyDown}
/>
<Icon
material="keyboard_arrow_up"
Expand All @@ -68,7 +80,7 @@ export const PodLogSearch = observer((props: PodLogSearchProps) => {
<Icon
material="close"
tooltip={_i18n._(t`Clear`)}
onClick={() => setSearch("")}
onClick={onClear}
/>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/components/dock/pod-logs.scss
Expand Up @@ -92,6 +92,10 @@
}
}

.logs .VirtualList .list {
overflow-x: scroll!important;
}

&.noscroll {
.logs .VirtualList .list {
overflow-x: hidden!important; // fixing scroll to bottom issues in PodLogs
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/dock/pod-logs.store.ts
Expand Up @@ -5,7 +5,7 @@ import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { t } from "@lingui/macro";
import { _i18n } from "../../i18n";
import { searchStore } from "./search.store";
import { searchStore } from "../../../common/search-store";

export interface IPodLogsData {
pod: Pod;
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/dock/pod-logs.tsx
Expand Up @@ -13,7 +13,7 @@ import { IPodLogsData, logRange, podLogsStore } from "./pod-logs.store";
import { Button } from "../button";
import { PodLogControls } from "./pod-log-controls";
import { VirtualList } from "../virtual-list";
import { searchStore } from "./search.store";
import { searchStore } from "../../../common/search-store";
import { ListOnScrollProps } from "react-window";

interface Props {
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/components/input/input.tsx
Expand Up @@ -214,6 +214,10 @@ export class Input extends React.Component<InputProps, State> {
onKeyDown(evt: React.KeyboardEvent<any>) {
const modified = evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey;

if (this.props.onKeyDown) {
this.props.onKeyDown(evt);
}

switch (evt.key) {
case "Enter":
if (this.props.onSubmit && !modified && !evt.repeat) {
Expand Down
23 changes: 21 additions & 2 deletions src/renderer/components/input/search-input.tsx
@@ -1,10 +1,10 @@
import "./search-input.scss";

import React from "react";
import React, { createRef } from "react";
import { t } from "@lingui/macro";
import { observer } from "mobx-react";
import { _i18n } from "../../i18n";
import { cssNames } from "../../utils";
import { autobind, cssNames } from "../../utils";
import { Icon } from "../icon";
import { Input, InputProps } from "./input";

Expand All @@ -26,6 +26,16 @@ const defaultProps: Partial<Props> = {
export class SearchInput extends React.Component<Props> {
static defaultProps = defaultProps as object;

private input = createRef<Input>();

componentDidMount() {
addEventListener("keydown", this.focus);
}

componentWillUnmount() {
removeEventListener("keydown", this.focus);
}

clear = () => {
if (this.props.onClear) {
this.props.onClear();
Expand All @@ -48,6 +58,14 @@ export class SearchInput extends React.Component<Props> {
}
}

@autobind()
focus(evt: KeyboardEvent) {
const meta = evt.metaKey || evt.ctrlKey;
if (meta && evt.key == "f") {
this.input.current.focus();
}
}

render() {
const { className, compact, closeIcon, onClear, ...inputProps } = this.props;
const icon = this.props.value
Expand All @@ -60,6 +78,7 @@ export class SearchInput extends React.Component<Props> {
onChange={this.onChange}
onKeyDown={this.onKeyDown}
iconRight={icon}
ref={this.input}
/>
)
}
Expand Down