diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 0a81e04831b..4cfd174978c 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -188,6 +188,57 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS } }; + this.annotationQuery = function(options) { + var annotation = options.annotation; + var expr = annotation.expr || ''; + var tagKeys = annotation.tagKeys || ''; + var titleFormat = annotation.titleFormat || ''; + var textFormat = annotation.textFormat || ''; + + if (!expr) { return $q.when([]); } + + var interpolated; + try { + interpolated = templateSrv.replace(expr); + } catch (err) { + return $q.reject(err); + } + + var query = { + expr: interpolated, + step: '60s' + }; + var start = getPrometheusTime(options.range.from, false); + var end = getPrometheusTime(options.range.to, true); + return this.performTimeSeriesQuery(query, start, end).then(function(results) { + var eventList = []; + tagKeys = tagKeys.split(','); + + _.each(results.data.data.result, function(series) { + var tags = _.chain(series.metric) + .filter(function(v, k) { + return _.contains(tagKeys, k); + }).value(); + + _.each(series.values, function(value) { + if (value[1] === '1') { + var event = { + annotation: annotation, + time: Math.floor(value[0]) * 1000, + title: renderTemplate(titleFormat, series.metric), + tags: tags, + text: renderTemplate(textFormat, series.metric) + }; + + eventList.push(event); + } + }); + }); + + return eventList; + }); + }; + this.testDatasource = function() { return this.metricFindQuery('metrics(.*)').then(function() { return { status: 'success', message: 'Data source is working', title: 'Success' }; @@ -240,22 +291,26 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return getOriginalMetricName(labelData); } + return renderTemplate(options.legendFormat, labelData) || '{}'; + } + + function renderTemplate(format, data) { var originalSettings = _.templateSettings; _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g }; - var template = _.template(templateSrv.replace(options.legendFormat)); - var metricName; + var template = _.template(templateSrv.replace(format)); + var result; try { - metricName = template(labelData); + result = template(data); } catch (e) { - metricName = '{}'; + result = null; } _.templateSettings = originalSettings; - return metricName; + return result; } function getOriginalMetricName(labelData) { diff --git a/public/app/plugins/datasource/prometheus/module.ts b/public/app/plugins/datasource/prometheus/module.ts index 9af37384b4f..0ddba422bcc 100644 --- a/public/app/plugins/datasource/prometheus/module.ts +++ b/public/app/plugins/datasource/prometheus/module.ts @@ -5,8 +5,13 @@ class PrometheusConfigCtrl { static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html'; } +class PrometheusAnnotationsQueryCtrl { + static templateUrl = 'public/app/plugins/datasource/prometheus/partials/annotations.editor.html'; +} + export { PrometheusDatasource as Datasource, PrometheusQueryCtrl as QueryCtrl, - PrometheusConfigCtrl as ConfigCtrl + PrometheusConfigCtrl as ConfigCtrl, + PrometheusAnnotationsQueryCtrl as AnnotationsQueryCtrl, }; diff --git a/public/app/plugins/datasource/prometheus/partials/annotations.editor.html b/public/app/plugins/datasource/prometheus/partials/annotations.editor.html new file mode 100644 index 00000000000..ffeb7d6deea --- /dev/null +++ b/public/app/plugins/datasource/prometheus/partials/annotations.editor.html @@ -0,0 +1,28 @@ +
+
+
Search expression
+
+ +
+
+
+ +
+
+
Field formats
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
diff --git a/public/app/plugins/datasource/prometheus/plugin.json b/public/app/plugins/datasource/prometheus/plugin.json index 4cd55605816..66dd9145e6a 100644 --- a/public/app/plugins/datasource/prometheus/plugin.json +++ b/public/app/plugins/datasource/prometheus/plugin.json @@ -3,5 +3,6 @@ "name": "Prometheus", "id": "prometheus", - "metrics": true + "metrics": true, + "annotations": true } diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index fc3d2f489de..4a1b4121dba 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -157,4 +157,45 @@ describe('PrometheusDatasource', function() { expect(results.length).to.be(3); }); }); + describe('When performing annotationQuery', function() { + var results; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('ALERTS{alertstate="firing"}') + + '&start=1443438675&end=1443460275&step=60s'; + var options = { + annotation: { + expr: 'ALERTS{alertstate="firing"}', + tagKeys: 'job', + titleFormat: '{{alertname}}', + textFormat: '{{instance}}' + }, + range: { + from: moment(1443438674760), + to: moment(1443460274760) + } + }; + var response = { + status: "success", + data: { + resultType: "matrix", + result: [{ + metric: {"__name__": "ALERTS", alertname: "InstanceDown", alertstate: "firing", instance: "testinstance", job: "testjob"}, + values: [[1443454528, "1"]] + }] + } + }; + beforeEach(function() { + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.annotationQuery(options).then(function(data) { results = data; }); + ctx.$httpBackend.flush(); + }); + it('should return annotation list', function() { + ctx.$rootScope.$apply(); + expect(results.length).to.be(1); + expect(results[0].tags).to.contain('testjob'); + expect(results[0].title).to.be('InstanceDown'); + expect(results[0].text).to.be('testinstance'); + expect(results[0].time).to.be(1443454528 * 1000); + }); + }); });