Browse Source

feat(annotations): added support to show grafana stored annotations in graphs, #5982

Torkel Ödegaard 9 years ago
parent
commit
d60bd77658

+ 0 - 37
pkg/api/alerting.go

@@ -8,7 +8,6 @@ import (
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/alerting"
-	"github.com/grafana/grafana/pkg/services/annotations"
 )
 
 func ValidateOrgAlert(c *middleware.Context) {
@@ -231,42 +230,6 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R
 	return ApiSuccess("Test notification sent")
 }
 
-func GetAlertHistory(c *middleware.Context) Response {
-	alertId, err := getAlertIdForRequest(c)
-	if err != nil {
-		return ApiError(400, "Invalid request", err)
-	}
-
-	query := &annotations.ItemQuery{
-		AlertId: alertId,
-		Type:    annotations.AlertType,
-		OrgId:   c.OrgId,
-		Limit:   c.QueryInt64("limit"),
-	}
-
-	repo := annotations.GetRepository()
-
-	items, err := repo.Find(query)
-	if err != nil {
-		return ApiError(500, "Failed to get history for alert", err)
-	}
-
-	var result []dtos.AlertHistory
-	for _, item := range items {
-		result = append(result, dtos.AlertHistory{
-			AlertId:   item.AlertId,
-			Timestamp: item.Timestamp,
-			Data:      item.Data,
-			NewState:  item.NewState,
-			Text:      item.Text,
-			Metric:    item.Metric,
-			Title:     item.Title,
-		})
-	}
-
-	return Json(200, result)
-}
-
 func getAlertIdForRequest(c *middleware.Context) (int64, error) {
 	alertId := c.QueryInt64("alertId")
 	panelId := c.QueryInt64("panelId")

+ 42 - 0
pkg/api/annotations.go

@@ -0,0 +1,42 @@
+package api
+
+import (
+	"github.com/grafana/grafana/pkg/api/dtos"
+	"github.com/grafana/grafana/pkg/middleware"
+	"github.com/grafana/grafana/pkg/services/annotations"
+)
+
+func GetAnnotations(c *middleware.Context) Response {
+
+	query := &annotations.ItemQuery{
+		From:  c.QueryInt64("from") / 1000,
+		To:    c.QueryInt64("to") / 1000,
+		Type:  annotations.ItemType(c.Query("type")),
+		OrgId: c.OrgId,
+		Limit: c.QueryInt64("limit"),
+	}
+
+	repo := annotations.GetRepository()
+
+	items, err := repo.Find(query)
+	if err != nil {
+		return ApiError(500, "Failed to get annotations", err)
+	}
+
+	result := make([]dtos.Annotation, 0)
+
+	for _, item := range items {
+		result = append(result, dtos.Annotation{
+			AlertId:   item.AlertId,
+			Time:      item.Epoch * 1000,
+			Data:      item.Data,
+			NewState:  item.NewState,
+			PrevState: item.PrevState,
+			Text:      item.Text,
+			Metric:    item.Metric,
+			Title:     item.Title,
+		})
+	}
+
+	return Json(200, result)
+}

+ 2 - 2
pkg/api/api.go

@@ -254,8 +254,6 @@ func Register(r *macaron.Macaron) {
 			r.Get("/", wrap(GetAlerts))
 		})
 
-		r.Get("/alert-history", wrap(GetAlertHistory))
-
 		r.Get("/alert-notifications", wrap(GetAlertNotifications))
 
 		r.Group("/alert-notifications", func() {
@@ -266,6 +264,8 @@ func Register(r *macaron.Macaron) {
 			r.Delete("/:notificationId", wrap(DeleteAlertNotification))
 		}, reqOrgAdmin)
 
+		r.Get("/annotations", wrap(GetAnnotations))
+
 		// error test
 		r.Get("/metrics/error", wrap(GenerateError))
 

+ 0 - 11
pkg/api/dtos/alerting.go

@@ -54,17 +54,6 @@ type EvalMatch struct {
 	Value  float64           `json:"value"`
 }
 
-type AlertHistory struct {
-	AlertId   int64     `json:"alertId"`
-	NewState  string    `json:"newState"`
-	Timestamp time.Time `json:"timestamp"`
-	Title     string    `json:"title"`
-	Text      string    `json:"text"`
-	Metric    string    `json:"metric"`
-
-	Data *simplejson.Json `json:"data"`
-}
-
 type NotificationTestCommand struct {
 	Name     string           `json:"name"`
 	Type     string           `json:"type"`

+ 15 - 0
pkg/api/dtos/annotations.go

@@ -0,0 +1,15 @@
+package dtos
+
+import "github.com/grafana/grafana/pkg/components/simplejson"
+
+type Annotation struct {
+	AlertId   int64  `json:"alertId"`
+	NewState  string `json:"newState"`
+	PrevState string `json:"prevState"`
+	Time      int64  `json:"time"`
+	Title     string `json:"title"`
+	Text      string `json:"text"`
+	Metric    string `json:"metric"`
+
+	Data *simplejson.Json `json:"data"`
+}

+ 1 - 0
pkg/api/frontendsettings.go

@@ -105,6 +105,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 	grafanaDatasourceMeta, _ := plugins.DataSources["grafana"]
 	datasources["-- Grafana --"] = map[string]interface{}{
 		"type": "grafana",
+		"name": "-- Grafana --",
 		"meta": grafanaDatasourceMeta,
 	}
 

+ 0 - 1
pkg/services/alerting/eval_context.go

@@ -66,7 +66,6 @@ func (c *EvalContext) GetStateModel() *StateDescription {
 	default:
 		panic("Unknown rule state " + c.Rule.State)
 	}
-
 }
 
 func (a *EvalContext) GetDurationMs() float64 {

+ 1 - 1
pkg/services/alerting/result_handler.go

@@ -73,7 +73,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
 			Text:      ctx.GetStateModel().Text,
 			NewState:  string(ctx.Rule.State),
 			PrevState: string(oldState),
-			Timestamp: time.Now(),
+			Epoch:     time.Now().Unix(),
 			Data:      annotationData,
 		}
 

+ 14 - 16
pkg/services/annotations/annotations.go

@@ -1,10 +1,6 @@
 package annotations
 
-import (
-	"time"
-
-	"github.com/grafana/grafana/pkg/components/simplejson"
-)
+import "github.com/grafana/grafana/pkg/components/simplejson"
 
 type Repository interface {
 	Save(item *Item) error
@@ -13,6 +9,8 @@ type Repository interface {
 
 type ItemQuery struct {
 	OrgId   int64    `json:"orgId"`
+	From    int64    `json:"from"`
+	To      int64    `json:"from"`
 	Type    ItemType `json:"type"`
 	AlertId int64    `json:"alertId"`
 
@@ -36,17 +34,17 @@ const (
 )
 
 type Item struct {
-	Id        int64     `json:"id"`
-	OrgId     int64     `json:"orgId"`
-	Type      ItemType  `json:"type"`
-	Title     string    `json:"title"`
-	Text      string    `json:"text"`
-	Metric    string    `json:"metric"`
-	AlertId   int64     `json:"alertId"`
-	UserId    int64     `json:"userId"`
-	PrevState string    `json:"prevState"`
-	NewState  string    `json:"newState"`
-	Timestamp time.Time `json:"timestamp"`
+	Id        int64    `json:"id"`
+	OrgId     int64    `json:"orgId"`
+	Type      ItemType `json:"type"`
+	Title     string   `json:"title"`
+	Text      string   `json:"text"`
+	Metric    string   `json:"metric"`
+	AlertId   int64    `json:"alertId"`
+	UserId    int64    `json:"userId"`
+	PrevState string   `json:"prevState"`
+	NewState  string   `json:"newState"`
+	Epoch     int64    `json:"epoch"`
 
 	Data *simplejson.Json `json:"data"`
 }

+ 4 - 1
pkg/services/sqlstore/annotation.go

@@ -38,6 +38,9 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
 		params = append(params, query.AlertId)
 	}
 
+	sql.WriteString(` AND epoch BETWEEN ? AND ?`)
+	params = append(params, query.From, query.To)
+
 	if query.Type != "" {
 		sql.WriteString(` AND type = ?`)
 		params = append(params, string(query.Type))
@@ -47,7 +50,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
 		query.Limit = 10
 	}
 
-	sql.WriteString(fmt.Sprintf("ORDER BY timestamp DESC LIMIT %v", query.Limit))
+	sql.WriteString(fmt.Sprintf("ORDER BY epoch DESC LIMIT %v", query.Limit))
 
 	items := make([]*annotations.Item, 0)
 	if err := x.Sql(sql.String(), params...).Find(&items); err != nil {

+ 9 - 6
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -5,6 +5,7 @@ import (
 )
 
 func addAnnotationMig(mg *Migrator) {
+
 	table := Table{
 		Name: "annotation",
 		Columns: []*Column{
@@ -19,20 +20,22 @@ func addAnnotationMig(mg *Migrator) {
 			{Name: "prev_state", Type: DB_NVarchar, Length: 25, Nullable: false},
 			{Name: "new_state", Type: DB_NVarchar, Length: 25, Nullable: false},
 			{Name: "data", Type: DB_Text, Nullable: false},
-			{Name: "timestamp", Type: DB_DateTime, Nullable: false},
+			{Name: "epoch", Type: DB_BigInt, Nullable: false},
 		},
 		Indices: []*Index{
 			{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
 			{Cols: []string{"org_id", "type"}, Type: IndexType},
-			{Cols: []string{"timestamp"}, Type: IndexType},
+			{Cols: []string{"epoch"}, Type: IndexType},
 		},
 	}
 
-	mg.AddMigration("create annotation table v1", NewAddTableMigration(table))
+	mg.AddMigration("Drop old annotation table v2", NewDropTableMigration("annotation"))
+
+	mg.AddMigration("create annotation table v3", NewAddTableMigration(table))
 
 	// create indices
-	mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0]))
+	mg.AddMigration("add index annotation org_id & alert_id v2", NewAddIndexMigration(table, table.Indices[0]))
 
-	mg.AddMigration("add index annotation org_id & type", NewAddIndexMigration(table, table.Indices[1]))
-	mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2]))
+	mg.AddMigration("add index annotation org_id & type v2", NewAddIndexMigration(table, table.Indices[1]))
+	mg.AddMigration("add index annotation epoch", NewAddIndexMigration(table, table.Indices[2]))
 }

+ 4 - 4
public/app/core/directives/plugin_component.ts

@@ -136,12 +136,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
       }
       // Annotations
       case "annotations-query-ctrl": {
-        return System.import(scope.currentDatasource.meta.module).then(function(dsModule) {
+        return System.import(scope.ctrl.currentDatasource.meta.module).then(function(dsModule) {
           return {
-            baseUrl: scope.currentDatasource.meta.baseUrl,
-            name: 'annotations-query-ctrl-' + scope.currentDatasource.meta.id,
+            baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
+            name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
             bindings: {annotation: "=", datasource: "="},
-            attrs: {"annotation": "currentAnnotation", datasource: "currentDatasource"},
+            attrs: {"annotation": "ctrl.currentAnnotation", datasource: "ctrl.currentDatasource"},
             Component: dsModule.AnnotationsQueryCtrl,
           };
         });

+ 1 - 1
public/app/features/alerting/alert_tab_ctrl.ts

@@ -80,7 +80,7 @@ export class AlertTabCtrl {
   }
 
   getAlertHistory() {
-    this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}`).then(res => {
+    this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50`).then(res => {
       this.alertHistory = _.map(res, ah => {
         ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss');
         ah.stateModel = alertDef.getStateDisplayModel(ah.newState);

+ 2 - 2
public/app/features/alerting/partials/alert_tab.html

@@ -10,7 +10,7 @@
 				</a>
 			</li>
 			<li ng-class="{active: ctrl.subTabIndex === 2}">
-				<a ng-click="ctrl.changeTabIndex(2)">Alert History</a>
+				<a ng-click="ctrl.changeTabIndex(2)">State history</a>
 			</li>
       <li>
 				<a ng-click="ctrl.delete()">Delete</a>
@@ -136,7 +136,7 @@
 		</div>
 
 		<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2">
-			<h5 class="section-heading">Alert history</h5>
+      <h5 class="section-heading">State history <span class="muted small">(last 50 state changes)</span></h5>
 			<section class="card-section card-list-layout-list">
 				<ol class="card-list" >
 					<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">

+ 0 - 76
public/app/features/annotations/editor_ctrl.js

@@ -1,76 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery'
-],
-function (angular, _, $) {
-  'use strict';
-
-  var module = angular.module('grafana.controllers');
-
-  module.controller('AnnotationsEditorCtrl', function($scope, datasourceSrv) {
-    var annotationDefaults = {
-      name: '',
-      datasource: null,
-      iconColor: 'rgba(255, 96, 96, 1)',
-      enable: true
-    };
-
-    $scope.init = function() {
-      $scope.mode = 'list';
-      $scope.datasources = datasourceSrv.getAnnotationSources();
-      $scope.annotations = $scope.dashboard.annotations.list;
-      $scope.reset();
-
-      $scope.$watch('mode', function(newVal) {
-        if (newVal === 'new') { $scope.reset(); }
-      });
-    };
-
-    $scope.datasourceChanged = function() {
-      return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
-        $scope.currentDatasource = ds;
-        $scope.currentAnnotation.datasource = $scope.currentAnnotation.datasource;
-      });
-    };
-
-    $scope.edit = function(annotation) {
-      $scope.currentAnnotation = annotation;
-      $scope.currentIsNew = false;
-      $scope.datasourceChanged();
-      $scope.mode = 'edit';
-
-      $(".tooltip.in").remove();
-    };
-
-    $scope.reset = function() {
-      $scope.currentAnnotation = angular.copy(annotationDefaults);
-      $scope.currentAnnotation.datasource = $scope.datasources[0].name;
-      $scope.currentIsNew = true;
-      $scope.datasourceChanged();
-    };
-
-    $scope.update = function() {
-      $scope.reset();
-      $scope.mode = 'list';
-      $scope.broadcastRefresh();
-    };
-
-    $scope.add = function() {
-      $scope.annotations.push($scope.currentAnnotation);
-      $scope.reset();
-      $scope.mode = 'list';
-      $scope.updateSubmenuVisibility();
-      $scope.broadcastRefresh();
-    };
-
-    $scope.removeAnnotation = function(annotation) {
-      var index = _.indexOf($scope.annotations, annotation);
-      $scope.annotations.splice(index, 1);
-      $scope.updateSubmenuVisibility();
-      $scope.broadcastRefresh();
-    };
-
-  });
-
-});

+ 82 - 0
public/app/features/annotations/editor_ctrl.ts

@@ -0,0 +1,82 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+import config from 'app/core/config';
+import $ from 'jquery';
+import coreModule from 'app/core/core_module';
+
+export class AnnotationsEditorCtrl {
+  mode: any;
+  datasources: any;
+  annotations: any;
+  currentAnnotation: any;
+  currentDatasource: any;
+  currentIsNew: any;
+
+  annotationDefaults: any = {
+    name: '',
+    datasource: null,
+    iconColor: 'rgba(255, 96, 96, 1)',
+    enable: true
+  };
+
+  constructor(private $scope, private datasourceSrv) {
+    $scope.ctrl = this;
+
+    this.mode = 'list';
+    this.datasources = datasourceSrv.getAnnotationSources();
+    this.annotations = $scope.dashboard.annotations.list;
+    this.reset();
+
+    $scope.$watch('mode', newVal => {
+      if (newVal === 'new') {
+        this.reset();
+      }
+    });
+  }
+
+  datasourceChanged() {
+    return this.datasourceSrv.get(this.currentAnnotation.datasource).then(ds => {
+      this.currentDatasource = ds;
+    });
+  }
+
+  edit(annotation) {
+    this.currentAnnotation = annotation;
+    this.currentIsNew = false;
+    this.datasourceChanged();
+    this.mode = 'edit';
+    $(".tooltip.in").remove();
+  }
+
+  reset() {
+    this.currentAnnotation = angular.copy(this.annotationDefaults);
+    this.currentAnnotation.datasource = this.datasources[0].name;
+    this.currentIsNew = true;
+    this.datasourceChanged();
+  }
+
+  update() {
+    this.reset();
+    this.mode = 'list';
+    this.$scope.broadcastRefresh();
+  };
+
+  add() {
+    this.annotations.push(this.currentAnnotation);
+    this.reset();
+    this.mode = 'list';
+    this.$scope.updateSubmenuVisibility();
+    this.$scope.broadcastRefresh();
+  };
+
+  removeAnnotation(annotation) {
+    var index = _.indexOf(this.annotations, annotation);
+    this.annotations.splice(index, 1);
+    this.$scope.updateSubmenuVisibility();
+    this.$scope.broadcastRefresh();
+  }
+}
+
+coreModule.controller('AnnotationsEditorCtrl', AnnotationsEditorCtrl);

+ 20 - 20
public/app/features/annotations/partials/editor.html

@@ -1,4 +1,4 @@
-<div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
+<div ng-controller="AnnotationsEditorCtrl">
 	<div class="tabbed-view-header">
 		<h2 class="tabbed-view-title">
 			Annotations
@@ -6,16 +6,16 @@
 
 		<ul class="gf-tabs">
 			<li class="gf-tabs-item" >
-				<a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
+				<a class="gf-tabs-link" ng-click="ctrl.mode = 'list';" ng-class="{active: ctrl.mode === 'list'}">
 					List
 				</a>
 			</li>
-			<li class="gf-tabs-item" ng-show="mode === 'edit'">
-				<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
+			<li class="gf-tabs-item" ng-show="ctrl.mode === 'edit'">
+				<a class="gf-tabs-link" ng-class="{active: ctrl.mode === 'edit'}">
 					{{currentAnnotation.name}}
 				</a>
 			</li>
-			<li class="gf-tabs-item" ng-show="mode === 'new'">
+			<li class="gf-tabs-item" ng-show="ctrl.mode === 'new'">
 				<span class="active gf-tabs-link">New</span>
 			</li>
 		</ul>
@@ -26,18 +26,18 @@
 	</div>
 
 	<div class="tabbed-view-body">
-		<div class="editor-row row" ng-if="mode === 'list'">
-			<div ng-if="annotations.length === 0">
+		<div class="editor-row row" ng-if="ctrl.mode === 'list'">
+			<div ng-if="ctrl.annotations.length === 0">
 				<em>No annotations defined</em>
 			</div>
 			<table class="grafana-options-table">
-				<tr ng-repeat="annotation in annotations">
+				<tr ng-repeat="annotation in ctrl.annotations">
 					<td style="width:90%">
 						<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp;
 						{{annotation.name}}
 					</td>
-					<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
-					<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
+					<td style="width: 1%"><i ng-click="_.move(ctrl.annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
+					<td style="width: 1%"><i ng-click="_.move(ctrl.annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
 
 					<td style="width: 1%">
 						<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
@@ -46,7 +46,7 @@
 						</a>
 					</td>
 					<td style="width: 1%">
-						<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
+						<a ng-click="ctrl.removeAnnotation(annotation)" class="btn btn-danger btn-mini">
 							<i class="fa fa-remove"></i>
 						</a>
 					</td>
@@ -54,43 +54,43 @@
 			</table>
 		</div>
 
-		<div class="gf-form" ng-show="mode === 'list'">
+		<div class="gf-form" ng-show="ctrl.mode === 'list'">
 			<div class="gf-form-button-row">
-				<a type="button" class="btn gf-form-button btn-success" ng-click="mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
+				<a type="button" class="btn gf-form-button btn-success" ng-click="ctrl.mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
 			</div>
 		</div>
 
-		<div class="annotations-basic-settings" ng-if="mode === 'edit' || mode === 'new'">
+		<div class="annotations-basic-settings" ng-if="ctrl.mode === 'edit' || ctrl.mode === 'new'">
 			<div class="gf-form-group">
 				<div class="gf-form-inline">
 					<div class="gf-form gf-size-max-xxl">
 						<span class="gf-form-label">Name</span>
-						<input type="text" class="gf-form-input" ng-model='currentAnnotation.name' placeholder="name"></input>
+						<input type="text" class="gf-form-input" ng-model='ctrl.currentAnnotation.name' placeholder="name"></input>
 					</div>
 					<div class="gf-form">
 						<span class="gf-form-label max-width-10">Datasource</span>
 						<div class="gf-form-select-wrapper">
-							<select class="gf-form-input gf-size-auto" ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
+							<select class="gf-form-input gf-size-auto" ng-model="ctrl.currentAnnotation.datasource" ng-options="f.name as f.name for f in ctrl.datasources" ng-change="ctrl.datasourceChanged()"></select>
 						</div>
 					</div>
 					<div class="gf-form">
 						<label class="gf-form-label">
 							<span>Color</span>
 						</label>
-						<spectrum-picker class="gf-form-input" ng-model="currentAnnotation.iconColor"></spectrum-picker>
+						<spectrum-picker class="gf-form-input" ng-model="ctrl.currentAnnotation.iconColor"></spectrum-picker>
 					</div>
 				</div>
 			</div>
 
-			<rebuild-on-change property="currentDatasource">
+			<rebuild-on-change property="ctrl.currentDatasource">
 				<plugin-component type="annotations-query-ctrl">
 				</plugin-component>
 			</rebuild-on-change>
 
 			<div class="gf-form">
 				<div class="gf-form-button-row p-y-0">
-					<button ng-show="mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="add()">Add</button>
-					<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update()">Update</button>
+					<button ng-show="ctrl.mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="ctrl.add()">Add</button>
+					<button ng-show="ctrl.mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="ctrl.update()">Update</button>
 				</div>
 			</div>
 		</div>

+ 15 - 0
public/app/plugins/datasource/grafana/datasource.ts

@@ -12,6 +12,21 @@ class GrafanaDatasource {
       maxDataPoints: options.maxDataPoints
     });
   }
+
+  annotationQuery(options) {
+    return this.backendSrv.get('/api/annotations', {
+      from: options.range.from.valueOf(),
+      to: options.range.to.valueOf(),
+      limit: options.limit,
+      type: options.type,
+    }).then(data => {
+      return data.map(item => {
+        item.annotation = options.annotation;
+        return item;
+      });
+    });
+  }
+
 }
 
 export {GrafanaDatasource};

+ 13 - 0
public/app/plugins/datasource/grafana/module.ts

@@ -8,9 +8,22 @@ class GrafanaQueryCtrl extends QueryCtrl {
   static templateUrl = 'partials/query.editor.html';
 }
 
+class GrafanaAnnotationsQueryCtrl {
+  annotation: any;
+
+  constructor() {
+    this.annotation.type = this.annotation.type || 'alert';
+    this.annotation.limit = this.annotation.limit || 100;
+  }
+
+  static templateUrl = 'partials/annotations.editor.html';
+}
+
+
 export {
   GrafanaDatasource,
   GrafanaDatasource as Datasource,
   GrafanaQueryCtrl as QueryCtrl,
+  GrafanaAnnotationsQueryCtrl as AnnotationsQueryCtrl,
 };
 

+ 20 - 0
public/app/plugins/datasource/grafana/partials/annotations.editor.html

@@ -0,0 +1,20 @@
+
+<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>
+		<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>
+</div>

+ 1 - 0
public/app/plugins/datasource/grafana/plugin.json

@@ -4,5 +4,6 @@
   "id": "grafana",
 
   "builtIn": true,
+  "annotations": true,
   "metrics": true
 }