Ver Fonte

Template variable support for ifql datasource

* Implements findMetricQuery()
* Macros for template queries: measurements(), tags(), tag_values(),
 field_keys()
* Tests for macro expansion
David Kaltschmidt há 7 anos atrás
pai
commit
2c86484e54

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

@@ -14,9 +14,15 @@ Read more about InfluxDB here:
 
 [http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
 
+## Supported Template Variable Macros:
+
+* List all measurements for a given database: `measurements(database)`
+* List all tags for a given database and measurement: `tags(database, measurement)`
+* List all tag values for a given database, measurement, and tag: `tag_valuess(database, measurement, tag)`
+* List all field keys for a given database and measurement: `field_keys(database, measurement)`
+
 ## Roadmap
 
-- metricFindQuery
 - Syntax highlighting
 - Tab completion (functions, values)
 - Annotations support

+ 29 - 19
public/app/plugins/datasource/influxdb-ifql/datasource.ts

@@ -2,7 +2,8 @@ import _ from 'lodash';
 
 import * as dateMath from 'app/core/utils/datemath';
 
-import { getTableModelFromResult, getTimeSeriesFromResult, parseResults } from './response_parser';
+import { getTableModelFromResult, getTimeSeriesFromResult, getValuesFromResult, parseResults } from './response_parser';
+import expandMacros from './metric_find_query';
 
 function serializeParams(params) {
   if (!params) {
@@ -54,25 +55,21 @@ export default class InfluxDatasource {
     this.supportMetrics = true;
   }
 
-  prepareQueries(options) {
-    const targets = _.cloneDeep(options.targets);
+  prepareQueryTarget(target, options) {
+    // Replace grafana variables
     const timeFilter = this.getTimeFilter(options);
     options.scopedVars.range = { value: timeFilter };
-
-    // Filter empty queries and replace grafana variables
-    const queryTargets = targets.filter(t => t.query).map(t => {
-      const interpolated = this.templateSrv.replace(t.query, options.scopedVars);
-      return {
-        ...t,
-        query: interpolated,
-      };
-    });
-
-    return queryTargets;
+    const interpolated = this.templateSrv.replace(target.query, options.scopedVars);
+    return {
+      ...target,
+      query: interpolated,
+    };
   }
 
   query(options) {
-    const queryTargets = this.prepareQueries(options);
+    const queryTargets = options.targets
+      .filter(target => target.query)
+      .map(target => this.prepareQueryTarget(target, options));
     if (queryTargets.length === 0) {
       return Promise.resolve({ data: [] });
     }
@@ -112,10 +109,23 @@ export default class InfluxDatasource {
   }
 
   metricFindQuery(query: string, options?: any) {
-    // TODO not implemented
-    var interpolated = this.templateSrv.replace(query, null, 'regex');
-
-    return this._seriesQuery(interpolated, options).then(_.curry(parseResults)(query));
+    const interpreted = expandMacros(query);
+
+    // Use normal querier in silent mode
+    const queryOptions = {
+      rangeRaw: { to: 'now', from: 'now - 1h' },
+      scopedVars: {},
+      ...options,
+      silent: true,
+    };
+    const target = this.prepareQueryTarget({ query: interpreted }, queryOptions);
+    return this._seriesQuery(target.query, queryOptions).then(response => {
+      const results = parseResults(response.data);
+      const values = _.uniq(_.flatten(results.map(getValuesFromResult)));
+      return values
+        .filter(value => value && value[0] !== '_') // Ignore internal fields
+        .map(value => ({ text: value }));
+    });
   }
 
   _seriesQuery(query: string, options?: any) {

+ 63 - 0
public/app/plugins/datasource/influxdb-ifql/metric_find_query.ts

@@ -0,0 +1,63 @@
+// MACROS
+
+// List all measurements for a given database: `measurements(database)`
+const MEASUREMENTS_REGEXP = /^\s*measurements\((.+)\)\s*$/;
+
+// List all tags for a given database and measurement: `tags(database, measurement)`
+const TAGS_REGEXP = /^\s*tags\((.+)\s*,\s*(.+)\)\s*$/;
+
+// List all tag values for a given database, measurement, and tag: `tag_valuess(database, measurement, tag)`
+const TAG_VALUES_REGEXP = /^\s*tag_values\((.+)\s*,\s*(.+)\s*,\s*(.+)\)\s*$/;
+
+// List all field keys for a given database and measurement: `field_keys(database, measurement)`
+const FIELD_KEYS_REGEXP = /^\s*field_keys\((.+)\s*,\s*(.+)\)\s*$/;
+
+export default function expandMacros(query) {
+  const measurementsQuery = query.match(MEASUREMENTS_REGEXP);
+  if (measurementsQuery) {
+    const database = measurementsQuery[1];
+    return `from(db:"${database}")
+    |> range($range)
+    |> group(by:["_measurement"])
+    |> distinct(column:"_measurement")
+    |> group(none:true)`;
+  }
+
+  const tagsQuery = query.match(TAGS_REGEXP);
+  if (tagsQuery) {
+    const database = tagsQuery[1];
+    const measurement = tagsQuery[2];
+    return `from(db:"${database}")
+    |> range($range)
+    |> filter(fn:(r) => r._measurement == "${measurement}")
+    |> keys()`;
+  }
+
+  const tagValuesQuery = query.match(TAG_VALUES_REGEXP);
+  if (tagValuesQuery) {
+    const database = tagValuesQuery[1];
+    const measurement = tagValuesQuery[2];
+    const tag = tagValuesQuery[3];
+    return `from(db:"${database}")
+    |> range($range)
+    |> filter(fn:(r) => r._measurement == "${measurement}")
+    |> group(by:["${tag}"])
+    |> distinct(column:"${tag}")
+    |> group(none:true)`;
+  }
+
+  const fieldKeysQuery = query.match(FIELD_KEYS_REGEXP);
+  if (fieldKeysQuery) {
+    const database = fieldKeysQuery[1];
+    const measurement = fieldKeysQuery[2];
+    return `from(db:"${database}")
+    |> range($range)
+    |> filter(fn:(r) => r._measurement == "${measurement}")
+    |> group(by:["_field"])
+    |> distinct(column:"_field")
+    |> group(none:true)`;
+  }
+
+  // By default return pure query
+  return query;
+}

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

@@ -86,3 +86,8 @@ export function getTimeSeriesFromResult(result: string) {
 
   return seriesList;
 }
+
+export function getValuesFromResult(result: string) {
+  const data = parseCSV(result);
+  return data.map(record => record['_value']);
+}

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

@@ -13,41 +13,27 @@ describe('InfluxDB (IFQL)', () => {
     targets: [],
   };
 
-  let queries: any[];
-
-  describe('prepareQueries()', () => {
-    it('filters empty queries', () => {
-      queries = ds.prepareQueries(DEFAULT_OPTIONS);
-      expect(queries.length).toBe(0);
-
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        targets: [{ query: '' }],
-      });
-      expect(queries.length).toBe(0);
-    });
+  describe('prepareQueryTarget()', () => {
+    let target: any;
 
     it('replaces $range variable', () => {
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        targets: [{ query: 'from(db: "test") |> range($range)' }],
-      });
-      expect(queries.length).toBe(1);
-      expect(queries[0].query).toBe('from(db: "test") |> range(start: -3h)');
+      target = ds.prepareQueryTarget({ query: 'from(db: "test") |> range($range)' }, DEFAULT_OPTIONS);
+      expect(target.query).toBe('from(db: "test") |> range(start: -3h)');
     });
 
     it('replaces $range variable with custom dates', () => {
       const to = moment();
       const from = moment().subtract(1, 'hours');
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        rangeRaw: { to, from },
-        targets: [{ query: 'from(db: "test") |> range($range)' }],
-      });
-      expect(queries.length).toBe(1);
+      target = ds.prepareQueryTarget(
+        { query: 'from(db: "test") |> range($range)' },
+        {
+          ...DEFAULT_OPTIONS,
+          rangeRaw: { to, from },
+        }
+      );
       const start = from.toISOString();
       const stop = to.toISOString();
-      expect(queries[0].query).toBe(`from(db: "test") |> range(start: ${start}, stop: ${stop})`);
+      expect(target.query).toBe(`from(db: "test") |> range(start: ${start}, stop: ${stop})`);
     });
   });
 });

+ 43 - 0
public/app/plugins/datasource/influxdb-ifql/specs/metric_find_query.jest.ts

@@ -0,0 +1,43 @@
+import expandMacros from '../metric_find_query';
+
+describe('metric find query', () => {
+  describe('expandMacros()', () => {
+    it('returns a non-macro query unadulterated', () => {
+      const query = 'from(db:"telegraf") |> last()';
+      const result = expandMacros(query);
+      expect(result).toBe(query);
+    });
+
+    it('returns a measurement query for measurements()', () => {
+      const query = ' measurements(mydb) ';
+      const result = expandMacros(query).replace(/\s/g, '');
+      expect(result).toBe(
+        'from(db:"mydb")|>range($range)|>group(by:["_measurement"])|>distinct(column:"_measurement")|>group(none:true)'
+      );
+    });
+
+    it('returns a tags query for tags()', () => {
+      const query = ' tags(mydb , mymetric) ';
+      const result = expandMacros(query).replace(/\s/g, '');
+      expect(result).toBe('from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")|>keys()');
+    });
+
+    it('returns a tag values query for tag_values()', () => {
+      const query = ' tag_values(mydb , mymetric, mytag) ';
+      const result = expandMacros(query).replace(/\s/g, '');
+      expect(result).toBe(
+        'from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' +
+          '|>group(by:["mytag"])|>distinct(column:"mytag")|>group(none:true)'
+      );
+    });
+
+    it('returns a field keys query for field_keys()', () => {
+      const query = ' field_keys(mydb , mymetric) ';
+      const result = expandMacros(query).replace(/\s/g, '');
+      expect(result).toBe(
+        'from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' +
+          '|>group(by:["_field"])|>distinct(column:"_field")|>group(none:true)'
+      );
+    });
+  });
+});

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

@@ -2,6 +2,7 @@ import {
   getNameFromRecord,
   getTableModelFromResult,
   getTimeSeriesFromResult,
+  getValuesFromResult,
   parseResults,
   parseValue,
 } from '../response_parser';
@@ -33,6 +34,14 @@ describe('influxdb ifql response parser', () => {
     });
   });
 
+  describe('getValuesFromResult()', () => {
+    it('returns all values from the _value field in the response', () => {
+      const results = parseResults(response);
+      const values = getValuesFromResult(results[0]);
+      expect(values.length).toBe(300);
+    });
+  });
+
   describe('getNameFromRecord()', () => {
     it('expects name based on measurements and tags', () => {
       const record = {