mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Annotations support for ifql datasource
This commit is contained in:
parent
2c86484e54
commit
cdba2bd184
@ -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
|
||||||
|
@ -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 });
|
const { query } = options.annotation;
|
||||||
var query = options.annotation.query.replace('$timeFilter', timeFilter);
|
const queryOptions = {
|
||||||
query = this.templateSrv.replace(query, null, 'regex');
|
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) {
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
@ -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)
|
const tags = getTagsFromRecord(record);
|
||||||
.filter(key => key[0] !== '_')
|
const tagValues = Object.keys(tags).map(key => `${key}=${tags[key]}`);
|
||||||
.filter(key => IGNORE_FIELDS_FOR_NAME.indexOf(key) === -1)
|
|
||||||
.map(key => `${key}=${record[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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user