Commit 32511533 by David Committed by GitHub

Merge pull request #14178 from grafana/davkal/explore-scan-log-range

Explore: Scan for older logs
parents ce9e1a8f 593cc38c
...@@ -97,6 +97,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -97,6 +97,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
* Local ID cache to compare requested vs selected datasource * Local ID cache to compare requested vs selected datasource
*/ */
requestedDatasourceId: string; requestedDatasourceId: string;
scanTimer: NodeJS.Timer;
/**
* Timepicker to control scanning
*/
timepickerRef: React.RefObject<TimePicker>;
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -122,6 +127,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -122,6 +127,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
history: [], history: [],
queryTransactions: [], queryTransactions: [],
range: initialRange, range: initialRange,
scanning: false,
showingGraph: true, showingGraph: true,
showingLogs: true, showingLogs: true,
showingStartPage: false, showingStartPage: false,
...@@ -132,6 +138,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -132,6 +138,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
}; };
} }
this.modifiedQueries = initialQueries.slice(); this.modifiedQueries = initialQueries.slice();
this.timepickerRef = React.createRef();
} }
async componentDidMount() { async componentDidMount() {
...@@ -164,6 +171,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -164,6 +171,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
} }
} }
componentWillUnmount() {
clearTimeout(this.scanTimer);
}
async setDatasource(datasource: any, origin?: DataSource) { async setDatasource(datasource: any, origin?: DataSource) {
const supportsGraph = datasource.meta.metrics; const supportsGraph = datasource.meta.metrics;
const supportsLogs = datasource.meta.logs; const supportsLogs = datasource.meta.logs;
...@@ -317,11 +328,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -317,11 +328,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
} }
}; };
onChangeTime = (nextRange: RawTimeRange) => { onChangeTime = (nextRange: RawTimeRange, scanning?: boolean) => {
const range: RawTimeRange = { const range: RawTimeRange = {
...nextRange, ...nextRange,
}; };
this.setState({ range }, () => this.onSubmit()); if (this.state.scanning && !scanning) {
this.onStopScanning();
}
this.setState({ range, scanning }, () => this.onSubmit());
}; };
onClickClear = () => { onClickClear = () => {
...@@ -496,6 +510,24 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -496,6 +510,24 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
); );
}; };
onStartScanning = () => {
this.setState({ scanning: true }, this.scanPreviousRange);
};
scanPreviousRange = () => {
const scanRange = this.timepickerRef.current.move(-1, true);
this.setState({ scanRange });
};
onStopScanning = () => {
clearTimeout(this.scanTimer);
this.setState(state => {
const { queryTransactions } = state;
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
return { queryTransactions: nextQueryTransactions, scanning: false, scanRange: undefined };
});
};
onSubmit = () => { onSubmit = () => {
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state; const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
// Keep table queries first since they need to return quickly // Keep table queries first since they need to return quickly
...@@ -563,6 +595,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -563,6 +595,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
done: false, done: false,
latency: 0, latency: 0,
options: queryOptions, options: queryOptions,
scanning: this.state.scanning,
}; };
// Using updater style because we might be modifying queryTransactions in quick succession // Using updater style because we might be modifying queryTransactions in quick succession
...@@ -599,7 +632,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -599,7 +632,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
} }
this.setState(state => { this.setState(state => {
const { history, queryTransactions } = state; const { history, queryTransactions, scanning } = state;
// Transaction might have been discarded // Transaction might have been discarded
const transaction = queryTransactions.find(qt => qt.id === transactionId); const transaction = queryTransactions.find(qt => qt.id === transactionId);
...@@ -629,6 +662,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -629,6 +662,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const nextHistory = updateHistory(history, datasourceId, queries); const nextHistory = updateHistory(history, datasourceId, queries);
// Keep scanning for results if this was the last scanning transaction
if (_.size(result) === 0 && scanning) {
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
if (!other) {
this.scanTimer = setTimeout(this.scanPreviousRange, 1000);
}
}
return { return {
history: nextHistory, history: nextHistory,
queryTransactions: nextQueryTransactions, queryTransactions: nextQueryTransactions,
...@@ -740,6 +781,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -740,6 +781,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
initialQueries, initialQueries,
queryTransactions, queryTransactions,
range, range,
scanning,
scanRange,
showingGraph, showingGraph,
showingLogs, showingLogs,
showingStartPage, showingStartPage,
...@@ -822,7 +865,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -822,7 +865,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
</button> </button>
</div> </div>
) : null} ) : null}
<TimePicker range={range} onChangeTime={this.onChangeTime} /> <TimePicker ref={this.timepickerRef} range={range} onChangeTime={this.onChangeTime} />
<div className="navbar-buttons"> <div className="navbar-buttons">
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}> <button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}>
Clear All Clear All
...@@ -898,7 +941,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -898,7 +941,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
loading={logsLoading} loading={logsLoading}
position={position} position={position}
onChangeTime={this.onChangeTime} onChangeTime={this.onChangeTime}
onStartScanning={this.onStartScanning}
onStopScanning={this.onStopScanning}
range={range} range={range}
scanning={scanning}
scanRange={scanRange}
/> />
</Panel> </Panel>
)} )}
......
import React, { Fragment, PureComponent } from 'react'; import React, { Fragment, PureComponent } from 'react';
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import * as rangeUtil from 'app/core/utils/rangeutil';
import { RawTimeRange } from 'app/types/series'; import { RawTimeRange } from 'app/types/series';
import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } from 'app/core/logs_model'; import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } from 'app/core/logs_model';
import { findHighlightChunksInText } from 'app/core/utils/text'; import { findHighlightChunksInText } from 'app/core/utils/text';
...@@ -28,7 +29,11 @@ interface LogsProps { ...@@ -28,7 +29,11 @@ interface LogsProps {
loading: boolean; loading: boolean;
position: string; position: string;
range?: RawTimeRange; range?: RawTimeRange;
scanning?: boolean;
scanRange?: RawTimeRange;
onChangeTime?: (range: RawTimeRange) => void; onChangeTime?: (range: RawTimeRange) => void;
onStartScanning?: () => void;
onStopScanning?: () => void;
} }
interface LogsState { interface LogsState {
...@@ -83,8 +88,18 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -83,8 +88,18 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
this.setState({ hiddenLogLevels }); this.setState({ hiddenLogLevels });
}; };
onClickScan = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStartScanning();
};
onClickStopScan = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStopScanning();
};
render() { render() {
const { className = '', data, loading = false, position, range } = this.props; const { className = '', data, loading = false, position, range, scanning, scanRange } = this.props;
const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state; const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state;
const hasData = data && data.rows && data.rows.length > 0; const hasData = data && data.rows && data.rows.length > 0;
const filteredData = filterLogLevels(data, hiddenLogLevels); const filteredData = filterLogLevels(data, hiddenLogLevels);
...@@ -111,6 +126,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -111,6 +126,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
const logEntriesStyle = { const logEntriesStyle = {
gridTemplateColumns: cssColumnSizes.join(' '), gridTemplateColumns: cssColumnSizes.join(' '),
}; };
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
return ( return (
<div className={`${className} logs`}> <div className={`${className} logs`}>
...@@ -200,7 +216,25 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -200,7 +216,25 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
</Fragment> </Fragment>
))} ))}
</div> </div>
{!loading && !hasData && 'No data was returned.'} {!loading &&
!hasData &&
!scanning && (
<div className="logs-nodata">
No logs found.
<a className="link" onClick={this.onClickScan}>
Scan for older logs
</a>
</div>
)}
{scanning && (
<div className="logs-nodata">
<span>{scanText}</span>
<a className="link" onClick={this.onClickStopScan}>
Stop scan
</a>
</div>
)}
</div> </div>
); );
} }
......
...@@ -35,7 +35,7 @@ interface TimePickerProps { ...@@ -35,7 +35,7 @@ interface TimePickerProps {
isOpen?: boolean; isOpen?: boolean;
isUtc?: boolean; isUtc?: boolean;
range?: RawTimeRange; range?: RawTimeRange;
onChangeTime?: (Range) => void; onChangeTime?: (range: RawTimeRange, scanning?: boolean) => void;
} }
interface TimePickerState { interface TimePickerState {
...@@ -92,12 +92,13 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke ...@@ -92,12 +92,13 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
}; };
} }
move(direction: number) { move(direction: number, scanning?: boolean): RawTimeRange {
const { onChangeTime } = this.props; const { onChangeTime } = this.props;
const { fromRaw, toRaw } = this.state; const { fromRaw, toRaw } = this.state;
const from = dateMath.parse(fromRaw, false); const from = dateMath.parse(fromRaw, false);
const to = dateMath.parse(toRaw, true); const to = dateMath.parse(toRaw, true);
const timespan = (to.valueOf() - from.valueOf()) / 2; const step = scanning ? 1 : 2;
const timespan = (to.valueOf() - from.valueOf()) / step;
let nextTo, nextFrom; let nextTo, nextFrom;
if (direction === -1) { if (direction === -1) {
...@@ -127,9 +128,11 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke ...@@ -127,9 +128,11 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
toRaw: nextRange.to.format(DATE_FORMAT), toRaw: nextRange.to.format(DATE_FORMAT),
}, },
() => { () => {
onChangeTime(nextRange); onChangeTime(nextRange, scanning);
} }
); );
return nextRange;
} }
handleChangeFrom = e => { handleChangeFrom = e => {
......
...@@ -140,6 +140,7 @@ export interface QueryTransaction { ...@@ -140,6 +140,7 @@ export interface QueryTransaction {
result?: any; // Table model / Timeseries[] / Logs result?: any; // Table model / Timeseries[] / Logs
resultType: ResultType; resultType: ResultType;
rowIndex: number; rowIndex: number;
scanning?: boolean;
} }
export interface TextMatch { export interface TextMatch {
...@@ -162,6 +163,8 @@ export interface ExploreState { ...@@ -162,6 +163,8 @@ export interface ExploreState {
initialQueries: DataQuery[]; initialQueries: DataQuery[];
queryTransactions: QueryTransaction[]; queryTransactions: QueryTransaction[];
range: RawTimeRange; range: RawTimeRange;
scanning?: boolean;
scanRange?: RawTimeRange;
showingGraph: boolean; showingGraph: boolean;
showingLogs: boolean; showingLogs: boolean;
showingStartPage?: boolean; showingStartPage?: boolean;
......
...@@ -267,6 +267,12 @@ ...@@ -267,6 +267,12 @@
} }
} }
.logs-nodata {
> * {
margin-left: 0.5em;
}
}
.logs-meta { .logs-meta {
flex: 1; flex: 1;
color: $text-color-weak; color: $text-color-weak;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment