Log search (#1114)

* Moving logs to virtual list

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Introducing log search

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Setting ref for VirtualList to access its methods

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Introducing search store

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Centering overlay when scroll to it

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Using SearchInput in PodLogSearch

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Using Prev/Next icons for search

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* No trigger logs load when scrolled by method

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* SearchInput refactoring

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding find counters

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Clean search query on dock tab change

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Refresh search when logs get changed

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Case-insensitive search

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Improve logs scrolling experience

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Catching empty logs in various places

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fixing downloading logs

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Clean up some duplicated styles

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Removing jump-to-bottom animation

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fixing since label

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Reducing container selector size

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Scroll down to bottom after each reload

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix search within timestamps if they not provided

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Use log row hover color from theme

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add search bindings for 'Esc' & 'Enter' hits

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Focus input fields on CmdOrCtrl+F

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Move search.store.ts in to /common folder

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* search.store.ts -> search-store.ts

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding test for search store

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding integration tests for logs

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fixing scroll jumping bug

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Removing download icon check for testing purpose

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Removing clicking on nginx-create-pod-test

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Moving log tests before cluster operations

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Build extensions before integration tests

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Build also npm before integration tests

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Move npm build and extension build into own build step

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Removing separator sketches

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Horizontal scrolling to founded keyword

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Delaying horizontal scrolling

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Alex AndreevandLauri Nevala authored and GitHub committed 2020-11-09 17:46:14 +03:00
1 parent f69f8c793f
commit 16fb35e3f9
25 files changed
+697 -144

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
export * from './input'
export * from './search-input'
export * from './search-input-url'
export * from './file-input'
+8 -2
View File
@@ -27,6 +27,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
showValidationLine?: boolean; // show animated validation line for async validators
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
iconRight?: string | React.ReactNode;
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
validators?: InputValidator | InputValidator[];
onChange?(value: T, evt: React.ChangeEvent<InputElement>): void;
onSubmit?(value: T): void;
@@ -216,6 +217,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) {
@@ -261,7 +266,7 @@ export class Input extends React.Component<InputProps, State> {
render() {
const {
multiLine, showValidationLine, validators, theme, maxRows, children,
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight,
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight, contentRight,
...inputProps
} = this.props;
const { focused, dirty, valid, validating, errors } = this.state;
@@ -293,7 +298,8 @@ export class Input extends React.Component<InputProps, State> {
<label className="input-area flex gaps align-center">
{isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft}
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
{isString(iconRight) ? <Icon material={iconRight}/> : iconRight}
{isString(iconRight) ? <Icon material={iconRight} /> : iconRight}
{contentRight}
</label>
<div className="input-info flex gaps">
{!valid && dirty && (
@@ -0,0 +1,49 @@
import React from "react";
import debounce from "lodash/debounce";
import { autorun, observable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { getSearch, setSearch } from "../../navigation";
import { InputProps } from "./input";
import { SearchInput } from "./search-input";
interface Props extends InputProps {
compact?: boolean; // show only search-icon when not focused
}
@observer
export class SearchInputUrl extends React.Component<Props> {
@observable inputVal = ""; // fix: use empty string to avoid react warnings
@disposeOnUnmount
updateInput = autorun(() => this.inputVal = getSearch())
updateUrl = debounce((val: string) => setSearch(val), 250)
setValue = (value: string) => {
this.inputVal = value;
this.updateUrl(value);
}
clear = () => {
this.setValue("");
this.updateUrl.flush();
}
onChange = (val: string, evt: React.ChangeEvent<any>) => {
this.setValue(val);
if (this.props.onChange) {
this.props.onChange(val, evt);
}
}
render() {
const { inputVal } = this;
return (
<SearchInput
value={inputVal}
onChange={this.onChange}
onClear={this.clear}
{...this.props}
/>
)
}
}
@@ -6,7 +6,10 @@
> label {
color: inherit;
background: none;
border: none;
border-radius: $radius;
box-shadow: 0 0 0 1px $halfGray;
padding: 6px 6px 6px 10px;
.Icon {
+29 -25
View File
@@ -1,22 +1,22 @@
import "./search-input.scss";
import React from "react";
import debounce from "lodash/debounce"
import { autorun, observable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import React, { createRef } from "react";
import { t } from "@lingui/macro";
import { observer } from "mobx-react";
import { _i18n } from "../../i18n";
import { autobind, cssNames } from "../../utils";
import { Icon } from "../icon";
import { cssNames } from "../../utils";
import { Input, InputProps } from "./input";
import { getSearch, setSearch } from "../../navigation";
import { _i18n } from '../../i18n';
interface Props extends InputProps {
compact?: boolean; // show only search-icon when not focused
closeIcon?: boolean;
onClear?: () => void;
}
const defaultProps: Partial<Props> = {
autoFocus: true,
closeIcon: true,
get placeholder() {
return _i18n._(t`Search...`)
},
@@ -26,27 +26,24 @@ const defaultProps: Partial<Props> = {
export class SearchInput extends React.Component<Props> {
static defaultProps = defaultProps as object;
@observable inputVal = ""; // fix: use empty string to avoid react warnings
private input = createRef<Input>();
@disposeOnUnmount
updateInput = autorun(() => this.inputVal = getSearch())
updateUrl = debounce((val: string) => setSearch(val), 250)
componentDidMount() {
addEventListener("keydown", this.focus);
}
setValue = (value: string) => {
this.inputVal = value;
this.updateUrl(value);
componentWillUnmount() {
removeEventListener("keydown", this.focus);
}
clear = () => {
this.setValue("");
this.updateUrl.flush();
if (this.props.onClear) {
this.props.onClear();
}
}
onChange = (val: string, evt: React.ChangeEvent<any>) => {
this.setValue(val);
if (this.props.onChange) {
this.props.onChange(val, evt);
}
this.props.onChange(val, evt);
}
onKeyDown = (evt: React.KeyboardEvent<any>) => {
@@ -61,20 +58,27 @@ 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 { inputVal } = this;
const { className, compact, ...inputProps } = this.props;
const icon = inputVal
? <Icon small material="close" onClick={this.clear}/>
const { className, compact, closeIcon, onClear, ...inputProps } = this.props;
const icon = this.props.value
? closeIcon ? <Icon small material="close" onClick={this.clear}/> : null
: <Icon small material="search"/>
return (
<Input
{...inputProps}
className={cssNames("SearchInput", className, { compact })}
value={inputVal}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
iconRight={icon}
ref={this.input}
/>
)
}