Переглянути джерело

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
David Kaltschmidt 7 роки тому
батько
коміт
4803b8f3c0

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

@@ -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>
                     )}

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

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

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

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

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

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