diff --git a/public/app/plugins/datasource/influxdb-ifql/README.md b/public/app/plugins/datasource/influxdb-ifql/README.md index 91f82b2a89d..5acb563b453 100644 --- a/public/app/plugins/datasource/influxdb-ifql/README.md +++ b/public/app/plugins/datasource/influxdb-ifql/README.md @@ -14,13 +14,16 @@ 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 -- Sync Grafana time ranges with `range()` -- Template variable expansion - Syntax highlighting - Tab completion (functions, values) -- Result helpers (result counts, table previews) -- Annotations support - Alerting integration - Explore UI integration diff --git a/public/app/plugins/datasource/influxdb-ifql/datasource.ts b/public/app/plugins/datasource/influxdb-ifql/datasource.ts index 41bdbe81c43..c71b48ad96b 100644 --- a/public/app/plugins/datasource/influxdb-ifql/datasource.ts +++ b/public/app/plugins/datasource/influxdb-ifql/datasource.ts @@ -2,7 +2,14 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; -import { getTableModelFromResult, getTimeSeriesFromResult, parseResults } from './response_parser'; +import { + getAnnotationsFromResult, + getTableModelFromResult, + getTimeSeriesFromResult, + getValuesFromResult, + parseResults, +} from './response_parser'; +import expandMacros from './metric_find_query'; function serializeParams(params) { if (!params) { @@ -54,25 +61,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: [] }); } @@ -81,13 +84,9 @@ export default class InfluxDatasource { const { query, resultFormat } = target; if (resultFormat === 'table') { - return ( - this._seriesQuery(query, options) - .then(response => parseResults(response.data)) - // Keep only first result from each request - .then(results => results[0]) - .then(getTableModelFromResult) - ); + return this._seriesQuery(query, options) + .then(response => parseResults(response.data)) + .then(results => results.map(getTableModelFromResult)); } else { return this._seriesQuery(query, options) .then(response => parseResults(response.data)) @@ -108,18 +107,42 @@ 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) { - // TODO not implemented - var interpolated = this.templateSrv.replace(query, null, 'regex'); + const interpreted = expandMacros(query); - return this._seriesQuery(interpolated, options).then(_.curry(parseResults)(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) { diff --git a/public/app/plugins/datasource/influxdb-ifql/metric_find_query.ts b/public/app/plugins/datasource/influxdb-ifql/metric_find_query.ts new file mode 100644 index 00000000000..4356fbdd48e --- /dev/null +++ b/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; +} diff --git a/public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html index 48991426c1e..53407f2a3f2 100644 --- a/public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html +++ b/public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html @@ -1,11 +1,13 @@ -