Pārlūkot izejas kodu

Annotations support for ifql datasource

David Kaltschmidt 7 gadi atpakaļ
vecāks
revīzija
cdba2bd184

+ 0 - 1
public/app/plugins/datasource/influxdb-ifql/README.md

@@ -25,6 +25,5 @@ Read more about InfluxDB here:
 
 
 - Syntax highlighting
 - Syntax highlighting
 - Tab completion (functions, values)
 - Tab completion (functions, values)
-- Annotations support
 - Alerting integration
 - Alerting integration
 - Explore UI integration
 - Explore UI integration

+ 22 - 5
public/app/plugins/datasource/influxdb-ifql/datasource.ts

@@ -2,7 +2,13 @@ import _ from 'lodash';
 
 
 import * as dateMath from 'app/core/utils/datemath';
 import * as dateMath from 'app/core/utils/datemath';
 
 
-import { getTableModelFromResult, getTimeSeriesFromResult, getValuesFromResult, parseResults } from './response_parser';
+import {
+  getAnnotationsFromResult,
+  getTableModelFromResult,
+  getTimeSeriesFromResult,
+  getValuesFromResult,
+  parseResults,
+} from './response_parser';
 import expandMacros from './metric_find_query';
 import expandMacros from './metric_find_query';
 
 
 function serializeParams(params) {
 function serializeParams(params) {
@@ -101,11 +107,22 @@ export default class InfluxDatasource {
       });
       });
     }
     }
 
 
-    var timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw });
-    var query = options.annotation.query.replace('$timeFilter', timeFilter);
-    query = this.templateSrv.replace(query, null, 'regex');
+    const { query } = options.annotation;
+    const queryOptions = {
+      scopedVars: {},
+      ...options,
+      silent: true,
+    };
+    const target = this.prepareQueryTarget({ query }, queryOptions);
 
 
-    return {};
+    return this._seriesQuery(target.query, queryOptions).then(response => {
+      const results = parseResults(response.data);
+      if (results.length === 0) {
+        throw { message: 'No results in response from InfluxDB' };
+      }
+      const annotations = _.flatten(results.map(result => getAnnotationsFromResult(result, options.annotation)));
+      return annotations;
+    });
   }
   }
 
 
   metricFindQuery(query: string, options?: any) {
   metricFindQuery(query: string, options?: any) {

+ 6 - 8
public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html

@@ -1,11 +1,13 @@
-
 <div class="gf-form-group">
 <div class="gf-form-group">
 	<div class="gf-form">
 	<div class="gf-form">
-		<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="select text from events where $timeFilter limit 1000"></input>
+		<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder='from(db:"telegraf") |> range($range)'></input>
 	</div>
 	</div>
 </div>
 </div>
 
 
-<h5 class="section-heading">Field mappings <tip>If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
+<h5 class="section-heading">Field mappings
+	<tip>If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed
+		of a title, tags, and an additional text field.</tip>
+</h5>
 <div class="gf-form-group">
 <div class="gf-form-group">
 	<div class="gf-form-inline">
 	<div class="gf-form-inline">
 		<div class="gf-form">
 		<div class="gf-form">
@@ -16,9 +18,5 @@
 			<span class="gf-form-label width-4">Tags</span>
 			<span class="gf-form-label width-4">Tags</span>
 			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.tagsColumn' placeholder=""></input>
 			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.tagsColumn' placeholder=""></input>
 		</div>
 		</div>
-		<div class="gf-form" ng-show="ctrl.annotation.titleColumn">
-			<span class="gf-form-label width-4">Title <em class="muted">(deprecated)</em></span>
-			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.titleColumn' placeholder=""></input>
-		</div>
 	</div>
 	</div>
-</div>
+</div>

+ 1 - 1
public/app/plugins/datasource/influxdb-ifql/plugin.json

@@ -4,7 +4,7 @@
   "id": "influxdb-ifql",
   "id": "influxdb-ifql",
   "defaultMatchFormat": "regex values",
   "defaultMatchFormat": "regex values",
   "metrics": true,
   "metrics": true,
-  "annotations": false,
+  "annotations": true,
   "alerting": false,
   "alerting": false,
   "queryOptions": {
   "queryOptions": {
     "minInterval": true
     "minInterval": true

+ 41 - 5
public/app/plugins/datasource/influxdb-ifql/response_parser.ts

@@ -1,4 +1,5 @@
 import Papa from 'papaparse';
 import Papa from 'papaparse';
+import flatten from 'lodash/flatten';
 import groupBy from 'lodash/groupBy';
 import groupBy from 'lodash/groupBy';
 
 
 import TableModel from 'app/core/table_model';
 import TableModel from 'app/core/table_model';
@@ -6,17 +7,25 @@ import TableModel from 'app/core/table_model';
 const filterColumnKeys = key => key && key[0] !== '_' && key !== 'result' && key !== 'table';
 const filterColumnKeys = key => key && key[0] !== '_' && key !== 'result' && key !== 'table';
 
 
 const IGNORE_FIELDS_FOR_NAME = ['result', '', 'table'];
 const IGNORE_FIELDS_FOR_NAME = ['result', '', 'table'];
+
+export const getTagsFromRecord = record =>
+  Object.keys(record)
+    .filter(key => key[0] !== '_')
+    .filter(key => IGNORE_FIELDS_FOR_NAME.indexOf(key) === -1)
+    .reduce((tags, key) => {
+      tags[key] = record[key];
+      return tags;
+    }, {});
+
 export const getNameFromRecord = record => {
 export const getNameFromRecord = record => {
   // Measurement and field
   // Measurement and field
   const metric = [record._measurement, record._field];
   const metric = [record._measurement, record._field];
 
 
   // Add tags
   // Add tags
-  const tags = Object.keys(record)
-    .filter(key => key[0] !== '_')
-    .filter(key => IGNORE_FIELDS_FOR_NAME.indexOf(key) === -1)
-    .map(key => `${key}=${record[key]}`);
+  const tags = getTagsFromRecord(record);
+  const tagValues = Object.keys(tags).map(key => `${key}=${tags[key]}`);
 
 
-  return [...metric, ...tags].join(' ');
+  return [...metric, ...tagValues].join(' ');
 };
 };
 
 
 const parseCSV = (input: string) =>
 const parseCSV = (input: string) =>
@@ -36,6 +45,33 @@ export function parseResults(response: string): any[] {
   return response.trim().split(/\n\s*\s/);
   return response.trim().split(/\n\s*\s/);
 }
 }
 
 
+export function getAnnotationsFromResult(result: string, options: any) {
+  const data = parseCSV(result);
+  if (data.length === 0) {
+    return [];
+  }
+
+  const annotations = [];
+  const textSelector = options.textCol || '_value';
+  const tagsSelector = options.tagsCol || '';
+  const tagSelection = tagsSelector.split(',').map(t => t.trim());
+
+  data.forEach(record => {
+    // Remove empty values, then split in different tags for comma separated values
+    const tags = getTagsFromRecord(record);
+    const tagValues = flatten(tagSelection.filter(tag => tags[tag]).map(tag => tags[tag].split(',')));
+
+    annotations.push({
+      annotation: options,
+      time: parseTime(record._time),
+      tags: tagValues,
+      text: record[textSelector],
+    });
+  });
+
+  return annotations;
+}
+
 export function getTableModelFromResult(result: string) {
 export function getTableModelFromResult(result: string) {
   const data = parseCSV(result);
   const data = parseCSV(result);
 
 

+ 12 - 0
public/app/plugins/datasource/influxdb-ifql/specs/response_parser.jest.ts

@@ -1,4 +1,5 @@
 import {
 import {
+  getAnnotationsFromResult,
   getNameFromRecord,
   getNameFromRecord,
   getTableModelFromResult,
   getTableModelFromResult,
   getTimeSeriesFromResult,
   getTimeSeriesFromResult,
@@ -16,6 +17,17 @@ describe('influxdb ifql response parser', () => {
     });
     });
   });
   });
 
 
+  describe('getAnnotationsFromResult()', () => {
+    it('expects a list of annotations', () => {
+      const results = parseResults(response);
+      const annotations = getAnnotationsFromResult(results[0], { tagsCol: 'cpu' });
+      expect(annotations.length).toBe(300);
+      expect(annotations[0].tags.length).toBe(1);
+      expect(annotations[0].tags[0]).toBe('cpu-total');
+      expect(annotations[0].text).toBe('0');
+    });
+  });
+
   describe('getTableModelFromResult()', () => {
   describe('getTableModelFromResult()', () => {
     it('expects a table model', () => {
     it('expects a table model', () => {
       const results = parseResults(response);
       const results = parseResults(response);