mirror of https://github.com/grafana/grafana
mysql: annotation support. Fixes #8382
Simple query editor - a text area with a Show Help section. Validation for empty query and if the time_sec column is missing.pull/7727/merge
parent
d47078e27f
commit
73cb035231
@ -1,20 +1,34 @@ |
||||
|
||||
<div class="gf-form-group"> |
||||
<h6>Filters</h6> |
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-7">Type</span> |
||||
<div class="gf-form-select-wrapper"> |
||||
<select class="gf-form-input" ng-model="ctrl.annotation.type" ng-options="f.value as f.text for f in [{text: 'Alert', value: 'alert'}]"> |
||||
</select> |
||||
</div> |
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form gf-form--grow"> |
||||
<textarea rows="10" class="gf-form-input" ng-model="ctrl.annotation.rawQuery" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-7">Max limit</span> |
||||
<div class="gf-form-select-wrapper"> |
||||
<select class="gf-form-input" ng-model="ctrl.annotation.limit" ng-options="f for f in [10,50,100,200,300,500,1000,2000]"> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp"> |
||||
Show Help |
||||
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i> |
||||
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form" ng-show="ctrl.showHelp"> |
||||
<pre class="gf-form-pre alert alert-info"><h6>Annotation Query Format</h6> |
||||
An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. |
||||
|
||||
- column with alias: <b>time_sec</b> for the annotation event. Format is UTC in seconds, use UNIX_TIMESTAMP(column) |
||||
- column with alias <b>title</b> for the annotation title |
||||
- column with alias: <b>text</b> for the annotation text |
||||
- column with alias: <b>tags</b> for annotation tags. This is a comma separated string of tags e.g. 'tag1,tag2' |
||||
|
||||
|
||||
Macros: |
||||
- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec |
||||
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) > from AND UNIX_TIMESTAMP(time_date_time) < 1492750877 |
||||
</pre> |
||||
</div> |
||||
</div> |
||||
|
||||
@ -0,0 +1,79 @@ |
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; |
||||
import moment from 'moment'; |
||||
import helpers from 'test/specs/helpers'; |
||||
import {MysqlDatasource} from '../datasource'; |
||||
|
||||
describe('MySQLDatasource', function() { |
||||
var ctx = new helpers.ServiceTestContext(); |
||||
var instanceSettings = {name: 'mysql'}; |
||||
|
||||
beforeEach(angularMocks.module('grafana.core')); |
||||
beforeEach(angularMocks.module('grafana.services')); |
||||
beforeEach(ctx.providePhase(['backendSrv'])); |
||||
|
||||
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { |
||||
ctx.$q = $q; |
||||
ctx.$httpBackend = $httpBackend; |
||||
ctx.$rootScope = $rootScope; |
||||
ctx.ds = $injector.instantiate(MysqlDatasource, {instanceSettings: instanceSettings}); |
||||
$httpBackend.when('GET', /\.html$/).respond(''); |
||||
})); |
||||
|
||||
describe('When performing annotationQuery', function() { |
||||
let results; |
||||
|
||||
const annotationName = 'MyAnno'; |
||||
|
||||
const options = { |
||||
annotation: { |
||||
name: annotationName, |
||||
rawQuery: 'select time_sec, title, text, tags from table;' |
||||
}, |
||||
range: { |
||||
from: moment(1432288354), |
||||
to: moment(1432288401) |
||||
} |
||||
}; |
||||
|
||||
const response = { |
||||
results: { |
||||
MyAnno: { |
||||
refId: annotationName, |
||||
tables: [ |
||||
{ |
||||
columns: [{text: 'time_sec'}, {text: 'title'}, {text: 'text'}, {text: 'tags'}], |
||||
rows: [ |
||||
[1432288355, 'aTitle', 'some text', 'TagA,TagB'], |
||||
[1432288390, 'aTitle2', 'some text2', ' TagB , TagC'], |
||||
[1432288400, 'aTitle3', 'some text3'] |
||||
] |
||||
} |
||||
] |
||||
} |
||||
} |
||||
}; |
||||
|
||||
beforeEach(function() { |
||||
ctx.backendSrv.datasourceRequest = function(options) { |
||||
return ctx.$q.when({data: response, status: 200}); |
||||
}; |
||||
ctx.ds.annotationQuery(options).then(function(data) { results = data; }); |
||||
ctx.$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should return annotation list', function() { |
||||
expect(results.length).to.be(3); |
||||
|
||||
expect(results[0].title).to.be('aTitle'); |
||||
expect(results[0].text).to.be('some text'); |
||||
expect(results[0].tags[0]).to.be('TagA'); |
||||
expect(results[0].tags[1]).to.be('TagB'); |
||||
|
||||
expect(results[1].tags[0]).to.be('TagB'); |
||||
expect(results[1].tags[1]).to.be('TagC'); |
||||
|
||||
expect(results[2].tags.length).to.be(0); |
||||
}); |
||||
}); |
||||
|
||||
}); |
||||
Loading…
Reference in new issue