Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Add search bar and filters for logcat output #147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ fun generateLogcatHtml(logcatOutput: File): String = when (logcatOutput.exists()
true -> logcatOutput
.readLines()
.map { line -> """<div class="log__${cssClassForLogcatLine(line)}">${StringEscapeUtils.escapeXml11(line)}</div>""" }
.fold(StringBuilder("""<div class="content"><div class="card log">""")) { stringBuilder, line ->
.fold(StringBuilder("""<div id="static_logs" class="content"><div class="card log">""")) { stringBuilder, line ->
stringBuilder.appendln(line)
}
.appendln("""</div></div>""")
Expand Down
2 changes: 1 addition & 1 deletion html-report/layout/log-container.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="content">
<div id="static_logs" class="content">
<div class="card log">
${log_entries}
</div>
Expand Down
61 changes: 61 additions & 0 deletions html-report/src/components/LogContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { Component } from 'react';
import cx from 'classnames';
import LogFilter from './LogFilter'

export default class LogContainer extends Component {

state = {
logs: [],
results: [],
loading: true,
hide: false
}

componentDidMount() {
document.addEventListener('DOMContentLoaded', (event) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

It has no sense adding listener DOMContentLoaded as the method componentDidMount itself says that the DOM is ready. Please read the React documentation to understand React way: https://reactjs.org/docs/state-and-lifecycle.html

this.loadStaticLogs()
})
}

loadStaticLogs() {
let logDivs = document.querySelectorAll('#static_logs > div > div')
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bad practice to reach elements directly from DOM in react, because it is against the whole concept of virtual DOM, except for rare cases (like reaching out <head> tag and some other corner cases).
Also, just fyi, using operator > is a bad practice, and sticking to simple tag div (without id or data-attribute) is also a bad practise — if HTML changes, the code is broken.

if (!logDivs.length) {
this.setState({hide: true, loading: false})
return
}
let logs = []
for (var div of logDivs) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't use var for defining variables, as we use es-2015 syntax. Please check it out here: https://old.babeljs.io/learn-es2015/#let-const

Also, you can use method map for array iterating. Please check it out here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

logs.push(Object.assign({level: div.className, line: div.innerText, key: logs.length}))
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no sense in using Object.assign here — what you actually want is just to add a new object to the array. Object.assign is a method used to perform more complicated actions with the objects. Please see the documentation here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Also, if you want to add a unique key, here comes the map method — it has a unique index for each item in the array.

}
this.setState({logs: logs, results: logs, loading: false})
Copy link
Contributor

Choose a reason for hiding this comment

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

Fyi, in es6, if a key is the same as a value, you can write it like this: {logs, results: logs, loading: false}, please check it out here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer

window.document.getElementById("static_logs").remove()
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing DOM directly with JS in React is a forbidden action and it is against basic React rules. Please read the React manual for more information.

}

getSearchResults(results) {
this.setState({ results: results });
}

render() {
return this.state.hide
? (<div></div>)
Copy link
Contributor

Choose a reason for hiding this comment

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

In React, if we don't need a component to render, we return null

: (
<div className="margin-top-20">
<LogFilter
setSearchResults={ (results) => this.getSearchResults(results) }
data={this.state.logs}
/>
{ !this.state.loading &&
<div className="card log">
{ this.state.results.map((entry, i) => {
return (
<div key={ entry.key } className={ entry.level }>
{ entry.line }
</div>
)
})
}
</div>}
</div>
);
}
}
37 changes: 37 additions & 0 deletions html-report/src/components/LogFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import SearchBar from './SearchBar';
import LogFilterButton from './LogFilterButton';

const SEARCH_FIELDS = ['level', 'line'];
const SEARCH_REF = 'line';
export default class LogFilter extends Component {
static propTypes = {
setSearchResults: PropTypes.func,
data: PropTypes.array
};

render() {
return (
<div>
<div className="card">
<div className="title-common">LOGS</div>
<LogFilterButton onClick={ () => this.performFilterSearch("level:verbose") } text="Verbose" disabled={ !!!this.props.data.length } />
Copy link
Contributor

Choose a reason for hiding this comment

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

Using !!! is not a good practice — what you basically do is converting a number to a boolean with the !! and checking the opposite value. In JS you can use just number to check for true or false. If the number is 0 — it's a false value, if > 0 — it's a true value.

<LogFilterButton onClick={ () => this.performFilterSearch("level:debug") } text="Debug" disabled={ !!!this.props.data.length } />
Copy link
Contributor

Choose a reason for hiding this comment

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

If you use smth more than 1 time — put it into a variable.

<LogFilterButton onClick={ () => this.performFilterSearch("level:info") } text="Info" disabled={ !!!this.props.data.length } />
<LogFilterButton onClick={ () => this.performFilterSearch("level:warning") } text="Warning" disabled={ !!!this.props.data.length } />
<LogFilterButton onClick={ () => this.performFilterSearch("level:error") } text="Error" disabled={ !!!this.props.data.length } />
<LogFilterButton onClick={ () => this.performFilterSearch("level:assert") } text="Assert" disabled={ !!!this.props.data.length } />
</div>
<SearchBar
setSearchResults={this.props.setSearchResults}
searchFields={SEARCH_FIELDS}
searchRef={SEARCH_REF}
data={this.props.data}
setPerformFilterSearchCallback={ callback => (this.performFilterSearch = callback) }
/>
</div>
);
}
}
13 changes: 13 additions & 0 deletions html-report/src/components/LogFilterButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

const LogFilterButton = ({text, disabled, onClick}) => (
<button
className="button secondary margin-right-10"
onClick={ onClick }
disabled={ disabled }
>
{text}
</button>
);

export default LogFilterButton;
95 changes: 49 additions & 46 deletions html-report/src/components/SearchBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,47 @@ import cx from 'classnames';
import elasticlunr from 'elasticlunr';
import convertTime from './../utils/convertTime'

const SEARCH_FIELDS = ['package_name', 'class_name', 'name', 'id', 'status'];
const SEARCH_REF = 'id';
const EL_SEARCH = elasticlunr();
export default class SearchBar extends Component {
static propTypes = {
setSearchResults: PropTypes.func
setSearchResults: PropTypes.func,
setPerformFilterSearchCallback: PropTypes.func,
searchFields: PropTypes.array,
searchRef: PropTypes.string,
data: PropTypes.array,
};

state = {
data: window.suite.tests,
data: this.props.data,
error: false,
searchLabel: null,
searchParams: null,
query: ''
};

componentWillMount() {
let { data } = this.state;
elasticlunr.clearStopWords();
SEARCH_FIELDS.forEach(f => EL_SEARCH.addField(f))
EL_SEARCH.setRef(SEARCH_REF);
if (data.length) {
data.forEach(item => EL_SEARCH.addDoc(item))
}
this.props.searchFields.forEach(f => EL_SEARCH.addField(f))
EL_SEARCH.setRef(this.props.searchRef);
}

componentDidMount() {
this.props.setPerformFilterSearchCallback(this.performFilterSearch)
}

componentWillReceiveProps(props) {
let data = props.data;
if (data === this.state.data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This code will always result in false, please read the JS documentation on how arrays works.

return;
}
if (data.length) {
data.forEach(item => EL_SEARCH.addDoc(item))
}
this.setState({data: props.data});
}

componentWillUnmount() {
this.props.setPerformFilterSearchCallback(undefined)
}

mapResults(results) {
Expand All @@ -42,14 +59,14 @@ export default class SearchBar extends Component {
};

setTagSearch = (field, callback) => {
if (SEARCH_FIELDS.indexOf(field) < 0) {
if (this.props.searchFields.indexOf(field) < 0) {
this.setState({ error: true });
return;
}

let params = {};
params.fields = {};
SEARCH_FIELDS.forEach((f) => {
this.props.searchFields.forEach((f) => {
if (f === field) {
params.fields[f] = { boost: 1 }
} else {
Expand Down Expand Up @@ -97,43 +114,29 @@ export default class SearchBar extends Component {
render() {
let errorTextClasses = cx('form-item__error-text col-100', { visible: this.state.error });
let errorInputClasses = cx({ 'is-invalid-input': this.state.error });
const data = window.suite;

return (
<div>
<div className="row justify-between">
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:passed') }>
<div className="text-sub-title-light">Passed</div>
<div className="card-info__content status-passed">{ data.passed_count }</div>
</div>
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:failed') }>
<div className="text-sub-title-light">Failed</div>
<div className="card-info__content status-failed">{ data.failed_count }</div>
</div>
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:ignored') }>
<div className="text-sub-title-light">Ignored</div>
<div className="card-info__content status-ignored">{ data.ignored_count }</div>
</div>
<div className="card card-info">
<div className="text-sub-title-light">Duration</div>
<div className="card-info__content">{ convertTime(data.duration_millis) }</div>
</div>
</div>
<div className="card">
<div className="form-container">
<div className="row search-params full">
<div className="row full-width-content input-group full">
<div className="form-item">
<div className="vertical-aligned-content">
{ this.state.searchLabel && <div className="label margin-right-20">{ this.state.searchLabel }:</div> }
<input type="text" className={ errorInputClasses } placeholder="Search" value={ this.state.query }
onChange={ this.setSearchQuery } />
<button type="reset" className="button secondary margin-left-20" onClick={ this.clearResults }>
Reset
</button>
</div>
<div className={ errorTextClasses }>No such key exists!</div>
<div className="card">
<div className="form-container">
<div className="row search-params full">
<div className="row full-width-content input-group full">
<div className="form-item">
<div className="vertical-aligned-content">
{ this.state.searchLabel && <div className="label margin-right-20">{ this.state.searchLabel }:</div> }
<input
type="text" className={ errorInputClasses } placeholder="Search" value={ this.state.query }
onChange={ this.setSearchQuery }
disabled={ !!!this.state.data.length ? "disabled" : "" }
/>
<button
type="reset" className="button secondary margin-left-20"
onClick={ this.clearResults }
disabled={ !!!this.state.data.length ? "disabled" : "" }
>
Reset
</button>
</div>
<div className={ errorTextClasses }>No such key exists!</div>
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions html-report/src/components/Suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cx from 'classnames';
import randomColor from 'randomcolor';
import convertTime from './../utils/convertTime';
import paths from './../utils/paths';
import SearchBar from './SearchBar';
import SuiteFilter from './SuiteFilter';

export default class Suite extends Component {
state = {
Expand Down Expand Up @@ -38,7 +38,7 @@ export default class Suite extends Component {
<div className="content margin-top-20">
<div className="title-common"><a href={ paths.fromSuiteToIndex }>Suites list</a>/ Suite {data.id}</div>

<SearchBar setSearchResults={ (results) => this.getSearchResults(results) } />
<SuiteFilter setSearchResults={ (results) => this.getSearchResults(results) } />

<div className="card">
<div className="vertical-aligned-content title-common">
Expand Down
51 changes: 51 additions & 0 deletions html-report/src/components/SuiteFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import convertTime from './../utils/convertTime'
import SearchBar from './SearchBar';

const SEARCH_FIELDS = ['package_name', 'class_name', 'name', 'id', 'status'];
const SEARCH_REF = 'id';
export default class SuiteFilter extends Component {
static propTypes = {
setSearchResults: PropTypes.func
};

performFilterSearch = (query) => {
this.searchBar.performFilterSearch(query)
};

render() {
const data = window.suite;

return (
<div>
<div className="row justify-between">
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:passed') }>
<div className="text-sub-title-light">Passed</div>
<div className="card-info__content status-passed">{ data.passed_count }</div>
</div>
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:failed') }>
<div className="text-sub-title-light">Failed</div>
<div className="card-info__content status-failed">{ data.failed_count }</div>
</div>
<div className="card card-info filter-card" onClick={ () => this.performFilterSearch('status:ignored') }>
<div className="text-sub-title-light">Ignored</div>
<div className="card-info__content status-ignored">{ data.ignored_count }</div>
</div>
<div className="card card-info">
<div className="text-sub-title-light">Duration</div>
<div className="card-info__content">{ convertTime(data.duration_millis) }</div>
</div>
</div>
<SearchBar
setSearchResults={this.props.setSearchResults}
searchFields={SEARCH_FIELDS}
searchRef={SEARCH_REF}
data={data.tests}
setPerformFilterSearchCallback={ callback => (this.performFilterSearch = callback) }
/>
</div>
)
}
}
11 changes: 10 additions & 1 deletion html-report/src/components/TestItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import React, { Component } from 'react';
import cx from 'classnames';
import convertTime from './../utils/convertTime'
import paths from './../utils/paths'
import LogContainer from './LogContainer'

export default class TestItem extends Component {

state = {
data: window.test,
};

componentWillMount() {
document.title = `Test ${window.test.name}`;
}

render() {
const data = window.test;
const data = this.state.data

let statusLabelClass = cx('label', 'margin-right-10', {
alert: data.status === 'failed',
success: data.status === 'passed'
Expand Down Expand Up @@ -65,6 +72,8 @@ export default class TestItem extends Component {
}) }
</ul>
</div>}

<LogContainer />
</div>
</div>
);
Expand Down