Browse Source

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

Explore: Scan for older logs
David 7 years ago
parent
commit
32511533c3

+ 51 - 4
public/app/features/explore/Explore.tsx

@@ -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> {
         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> {
       };
       };
     }
     }
     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> {
     }
     }
   }
   }
 
 
+  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> {
     }
     }
   };
   };
 
 
-  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> {
     );
     );
   };
   };
 
 
+  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> {
       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> {
     }
     }
 
 
     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> {
 
 
       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> {
       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> {
               </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> {
                           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>
                     )}
                     )}

+ 36 - 2
public/app/features/explore/Logs.tsx

@@ -1,6 +1,7 @@
 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 {
   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> {
     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> {
     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> {
               </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>
     );
     );
   }
   }

+ 7 - 4
public/app/features/explore/TimePicker.tsx

@@ -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
     };
     };
   }
   }
 
 
-  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
         toRaw: nextRange.to.format(DATE_FORMAT),
         toRaw: nextRange.to.format(DATE_FORMAT),
       },
       },
       () => {
       () => {
-        onChangeTime(nextRange);
+        onChangeTime(nextRange, scanning);
       }
       }
     );
     );
+
+    return nextRange;
   }
   }
 
 
   handleChangeFrom = e => {
   handleChangeFrom = e => {

+ 3 - 0
public/app/types/explore.ts

@@ -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 {
   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;

+ 6 - 0
public/sass/pages/_explore.scss

@@ -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;