Commit 4803b8f3 by David Kaltschmidt

Explore: Scan for older logs

Sometimes log streams dont return any lines for the given range. Would be great to automate the search until some logs are found.

- Allow Explore to drive TimePicker via ref
- Show `Scan` link in Logs when there is no data
- Click on `Scan` sets Explore into scanning state
- While scanning, tell Timepicker to shift left
- TimePicker change triggers new queries with shifted time range
- Remember if query transaction was started via scan
- keep scanning until something was found
- Manual use of timepicker cancels scanning
parent 9afb8b64
......@@ -97,6 +97,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
* Local ID cache to compare requested vs selected datasource
*/
requestedDatasourceId: string;
/**
* Timepicker to control scanning
*/
timepickerRef: React.RefObject<TimePicker>;
constructor(props) {
super(props);
......@@ -122,6 +126,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
history: [],
queryTransactions: [],
range: initialRange,
scanning: false,
showingGraph: true,
showingLogs: true,
showingStartPage: false,
......@@ -132,6 +137,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
};
}
this.modifiedQueries = initialQueries.slice();
this.timepickerRef = React.createRef();
}
async componentDidMount() {
......@@ -317,11 +323,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
}
};
onChangeTime = (nextRange: RawTimeRange) => {
onChangeTime = (nextRange: RawTimeRange, scanning?: boolean) => {
const range: RawTimeRange = {
...nextRange,
};
this.setState({ range }, () => this.onSubmit());
if (this.state.scanning && !scanning) {
this.stopScanOlder();
}
this.setState({ range, scanning }, () => this.onSubmit());
};
onClickClear = () => {
......@@ -496,6 +505,18 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
);
};
onStartScanOlder = () => {
this.setState({ scanning: true }, this.scanOlder);
};
scanOlder = () => {
this.timepickerRef.current.move(-1, true);
};
stopScanOlder = () => {
// Stop ongoing scan transactions
};
onSubmit = () => {
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
// Keep table queries first since they need to return quickly
......@@ -563,6 +584,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
done: false,
latency: 0,
options: queryOptions,
scanning: this.state.scanning,
};
// Using updater style because we might be modifying queryTransactions in quick succession
......@@ -599,7 +621,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
}
this.setState(state => {
const { history, queryTransactions } = state;
const { history, queryTransactions, scanning } = state;
// Transaction might have been discarded
const transaction = queryTransactions.find(qt => qt.id === transactionId);
......@@ -629,6 +651,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const nextHistory = updateHistory(history, datasourceId, queries);
if (_.size(result) === 0 && scanning) {
// Keep scanning if this was the last scanning transaction
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
if (!other) {
setTimeout(this.scanOlder, 1000);
}
}
return {
history: nextHistory,
queryTransactions: nextQueryTransactions,
......@@ -740,6 +770,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
initialQueries,
queryTransactions,
range,
scanning,
showingGraph,
showingLogs,
showingStartPage,
......@@ -822,7 +853,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
</button>
</div>
) : null}
<TimePicker range={range} onChangeTime={this.onChangeTime} />
<TimePicker ref={this.timepickerRef} range={range} onChangeTime={this.onChangeTime} />
<div className="navbar-buttons">
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}>
Clear All
......@@ -898,7 +929,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
loading={logsLoading}
position={position}
onChangeTime={this.onChangeTime}
onStartScanOlder={this.onStartScanOlder}
range={range}
scanning={scanning}
/>
</Panel>
)}
......
......@@ -28,7 +28,9 @@ interface LogsProps {
loading: boolean;
position: string;
range?: RawTimeRange;
scanning?: boolean;
onChangeTime?: (range: RawTimeRange) => void;
onStartScanOlder?: () => void;
}
interface LogsState {
......@@ -83,8 +85,13 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
this.setState({ hiddenLogLevels });
};
onClickScanOlder = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStartScanOlder();
};
render() {
const { className = '', data, loading = false, position, range } = this.props;
const { className = '', data, loading = false, position, range, scanning } = this.props;
const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state;
const hasData = data && data.rows && data.rows.length > 0;
const filteredData = filterLogLevels(data, hiddenLogLevels);
......@@ -200,7 +207,19 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
</Fragment>
))}
</div>
{!loading && !hasData && 'No data was returned.'}
{!loading &&
!hasData && (
<div>
No logs found.
{scanning ? (
'Scanning...'
) : (
<a className="link" onClick={this.onClickScanOlder}>
Scan for older logs
</a>
)}
</div>
)}
</div>
);
}
......
......@@ -35,7 +35,7 @@ interface TimePickerProps {
isOpen?: boolean;
isUtc?: boolean;
range?: RawTimeRange;
onChangeTime?: (Range) => void;
onChangeTime?: (range: RawTimeRange, scanning?: boolean) => void;
}
interface TimePickerState {
......@@ -92,12 +92,13 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
};
}
move(direction: number) {
move(direction: number, scanning?: boolean) {
const { onChangeTime } = this.props;
const { fromRaw, toRaw } = this.state;
const from = dateMath.parse(fromRaw, false);
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;
if (direction === -1) {
......@@ -127,7 +128,7 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
toRaw: nextRange.to.format(DATE_FORMAT),
},
() => {
onChangeTime(nextRange);
onChangeTime(nextRange, scanning);
}
);
}
......
......@@ -140,6 +140,7 @@ export interface QueryTransaction {
result?: any; // Table model / Timeseries[] / Logs
resultType: ResultType;
rowIndex: number;
scanning?: boolean;
}
export interface TextMatch {
......@@ -162,6 +163,7 @@ export interface ExploreState {
initialQueries: DataQuery[];
queryTransactions: QueryTransaction[];
range: RawTimeRange;
scanning?: boolean;
showingGraph: boolean;
showingLogs: boolean;
showingStartPage?: boolean;
......
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