Commit d60bd776 by Torkel Ödegaard

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

parent c7691487
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations"
) )
func ValidateOrgAlert(c *middleware.Context) { func ValidateOrgAlert(c *middleware.Context) {
...@@ -231,42 +230,6 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R ...@@ -231,42 +230,6 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R
return ApiSuccess("Test notification sent") 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) { func getAlertIdForRequest(c *middleware.Context) (int64, error) {
alertId := c.QueryInt64("alertId") alertId := c.QueryInt64("alertId")
panelId := c.QueryInt64("panelId") panelId := c.QueryInt64("panelId")
......
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)
}
...@@ -254,8 +254,6 @@ func Register(r *macaron.Macaron) { ...@@ -254,8 +254,6 @@ func Register(r *macaron.Macaron) {
r.Get("/", wrap(GetAlerts)) r.Get("/", wrap(GetAlerts))
}) })
r.Get("/alert-history", wrap(GetAlertHistory))
r.Get("/alert-notifications", wrap(GetAlertNotifications)) r.Get("/alert-notifications", wrap(GetAlertNotifications))
r.Group("/alert-notifications", func() { r.Group("/alert-notifications", func() {
...@@ -266,6 +264,8 @@ func Register(r *macaron.Macaron) { ...@@ -266,6 +264,8 @@ func Register(r *macaron.Macaron) {
r.Delete("/:notificationId", wrap(DeleteAlertNotification)) r.Delete("/:notificationId", wrap(DeleteAlertNotification))
}, reqOrgAdmin) }, reqOrgAdmin)
r.Get("/annotations", wrap(GetAnnotations))
// error test // error test
r.Get("/metrics/error", wrap(GenerateError)) r.Get("/metrics/error", wrap(GenerateError))
......
...@@ -54,17 +54,6 @@ type EvalMatch struct { ...@@ -54,17 +54,6 @@ type EvalMatch struct {
Value float64 `json:"value"` 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 { type NotificationTestCommand struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
......
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"`
}
...@@ -105,6 +105,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro ...@@ -105,6 +105,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
grafanaDatasourceMeta, _ := plugins.DataSources["grafana"] grafanaDatasourceMeta, _ := plugins.DataSources["grafana"]
datasources["-- Grafana --"] = map[string]interface{}{ datasources["-- Grafana --"] = map[string]interface{}{
"type": "grafana", "type": "grafana",
"name": "-- Grafana --",
"meta": grafanaDatasourceMeta, "meta": grafanaDatasourceMeta,
} }
......
...@@ -66,7 +66,6 @@ func (c *EvalContext) GetStateModel() *StateDescription { ...@@ -66,7 +66,6 @@ func (c *EvalContext) GetStateModel() *StateDescription {
default: default:
panic("Unknown rule state " + c.Rule.State) panic("Unknown rule state " + c.Rule.State)
} }
} }
func (a *EvalContext) GetDurationMs() float64 { func (a *EvalContext) GetDurationMs() float64 {
......
...@@ -73,7 +73,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { ...@@ -73,7 +73,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
Text: ctx.GetStateModel().Text, Text: ctx.GetStateModel().Text,
NewState: string(ctx.Rule.State), NewState: string(ctx.Rule.State),
PrevState: string(oldState), PrevState: string(oldState),
Timestamp: time.Now(), Epoch: time.Now().Unix(),
Data: annotationData, Data: annotationData,
} }
......
package annotations package annotations
import ( import "github.com/grafana/grafana/pkg/components/simplejson"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
type Repository interface { type Repository interface {
Save(item *Item) error Save(item *Item) error
...@@ -13,6 +9,8 @@ type Repository interface { ...@@ -13,6 +9,8 @@ type Repository interface {
type ItemQuery struct { type ItemQuery struct {
OrgId int64 `json:"orgId"` OrgId int64 `json:"orgId"`
From int64 `json:"from"`
To int64 `json:"from"`
Type ItemType `json:"type"` Type ItemType `json:"type"`
AlertId int64 `json:"alertId"` AlertId int64 `json:"alertId"`
...@@ -36,17 +34,17 @@ const ( ...@@ -36,17 +34,17 @@ const (
) )
type Item struct { type Item struct {
Id int64 `json:"id"` Id int64 `json:"id"`
OrgId int64 `json:"orgId"` OrgId int64 `json:"orgId"`
Type ItemType `json:"type"` Type ItemType `json:"type"`
Title string `json:"title"` Title string `json:"title"`
Text string `json:"text"` Text string `json:"text"`
Metric string `json:"metric"` Metric string `json:"metric"`
AlertId int64 `json:"alertId"` AlertId int64 `json:"alertId"`
UserId int64 `json:"userId"` UserId int64 `json:"userId"`
PrevState string `json:"prevState"` PrevState string `json:"prevState"`
NewState string `json:"newState"` NewState string `json:"newState"`
Timestamp time.Time `json:"timestamp"` Epoch int64 `json:"epoch"`
Data *simplejson.Json `json:"data"` Data *simplejson.Json `json:"data"`
} }
...@@ -38,6 +38,9 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I ...@@ -38,6 +38,9 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
params = append(params, query.AlertId) params = append(params, query.AlertId)
} }
sql.WriteString(` AND epoch BETWEEN ? AND ?`)
params = append(params, query.From, query.To)
if query.Type != "" { if query.Type != "" {
sql.WriteString(` AND type = ?`) sql.WriteString(` AND type = ?`)
params = append(params, string(query.Type)) params = append(params, string(query.Type))
...@@ -47,7 +50,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I ...@@ -47,7 +50,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
query.Limit = 10 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) items := make([]*annotations.Item, 0)
if err := x.Sql(sql.String(), params...).Find(&items); err != nil { if err := x.Sql(sql.String(), params...).Find(&items); err != nil {
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
) )
func addAnnotationMig(mg *Migrator) { func addAnnotationMig(mg *Migrator) {
table := Table{ table := Table{
Name: "annotation", Name: "annotation",
Columns: []*Column{ Columns: []*Column{
...@@ -19,20 +20,22 @@ func addAnnotationMig(mg *Migrator) { ...@@ -19,20 +20,22 @@ func addAnnotationMig(mg *Migrator) {
{Name: "prev_state", Type: DB_NVarchar, Length: 25, Nullable: false}, {Name: "prev_state", Type: DB_NVarchar, Length: 25, Nullable: false},
{Name: "new_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: "data", Type: DB_Text, Nullable: false},
{Name: "timestamp", Type: DB_DateTime, Nullable: false}, {Name: "epoch", Type: DB_BigInt, Nullable: false},
}, },
Indices: []*Index{ Indices: []*Index{
{Cols: []string{"org_id", "alert_id"}, Type: IndexType}, {Cols: []string{"org_id", "alert_id"}, Type: IndexType},
{Cols: []string{"org_id", "type"}, 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 // 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 org_id & type v2", NewAddIndexMigration(table, table.Indices[1]))
mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2])) mg.AddMigration("add index annotation epoch", NewAddIndexMigration(table, table.Indices[2]))
} }
...@@ -136,12 +136,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ ...@@ -136,12 +136,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
} }
// Annotations // Annotations
case "annotations-query-ctrl": { 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 { return {
baseUrl: scope.currentDatasource.meta.baseUrl, baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
name: 'annotations-query-ctrl-' + scope.currentDatasource.meta.id, name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
bindings: {annotation: "=", datasource: "="}, bindings: {annotation: "=", datasource: "="},
attrs: {"annotation": "currentAnnotation", datasource: "currentDatasource"}, attrs: {"annotation": "ctrl.currentAnnotation", datasource: "ctrl.currentDatasource"},
Component: dsModule.AnnotationsQueryCtrl, Component: dsModule.AnnotationsQueryCtrl,
}; };
}); });
......
...@@ -80,7 +80,7 @@ export class AlertTabCtrl { ...@@ -80,7 +80,7 @@ export class AlertTabCtrl {
} }
getAlertHistory() { 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 => { this.alertHistory = _.map(res, ah => {
ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss'); ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss');
ah.stateModel = alertDef.getStateDisplayModel(ah.newState); ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</a> </a>
</li> </li>
<li ng-class="{active: ctrl.subTabIndex === 2}"> <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>
<li> <li>
<a ng-click="ctrl.delete()">Delete</a> <a ng-click="ctrl.delete()">Delete</a>
...@@ -136,7 +136,7 @@ ...@@ -136,7 +136,7 @@
</div> </div>
<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2"> <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"> <section class="card-section card-list-layout-list">
<ol class="card-list" > <ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory"> <li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">
......
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();
};
});
});
///<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);
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()"> <div ng-controller="AnnotationsEditorCtrl">
<div class="tabbed-view-header"> <div class="tabbed-view-header">
<h2 class="tabbed-view-title"> <h2 class="tabbed-view-title">
Annotations Annotations
...@@ -6,16 +6,16 @@ ...@@ -6,16 +6,16 @@
<ul class="gf-tabs"> <ul class="gf-tabs">
<li class="gf-tabs-item" > <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 List
</a> </a>
</li> </li>
<li class="gf-tabs-item" ng-show="mode === 'edit'"> <li class="gf-tabs-item" ng-show="ctrl.mode === 'edit'">
<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}"> <a class="gf-tabs-link" ng-class="{active: ctrl.mode === 'edit'}">
{{currentAnnotation.name}} {{currentAnnotation.name}}
</a> </a>
</li> </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> <span class="active gf-tabs-link">New</span>
</li> </li>
</ul> </ul>
...@@ -26,18 +26,18 @@ ...@@ -26,18 +26,18 @@
</div> </div>
<div class="tabbed-view-body"> <div class="tabbed-view-body">
<div class="editor-row row" ng-if="mode === 'list'"> <div class="editor-row row" ng-if="ctrl.mode === 'list'">
<div ng-if="annotations.length === 0"> <div ng-if="ctrl.annotations.length === 0">
<em>No annotations defined</em> <em>No annotations defined</em>
</div> </div>
<table class="grafana-options-table"> <table class="grafana-options-table">
<tr ng-repeat="annotation in annotations"> <tr ng-repeat="annotation in ctrl.annotations">
<td style="width:90%"> <td style="width:90%">
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp; <i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp;
{{annotation.name}} {{annotation.name}}
</td> </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(ctrl.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="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%"> <td style="width: 1%">
<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini"> <a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</a> </a>
</td> </td>
<td style="width: 1%"> <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> <i class="fa fa-remove"></i>
</a> </a>
</td> </td>
...@@ -54,43 +54,43 @@ ...@@ -54,43 +54,43 @@
</table> </table>
</div> </div>
<div class="gf-form" ng-show="mode === 'list'"> <div class="gf-form" ng-show="ctrl.mode === 'list'">
<div class="gf-form-button-row"> <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> </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-group">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form gf-size-max-xxl"> <div class="gf-form gf-size-max-xxl">
<span class="gf-form-label">Name</span> <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>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label max-width-10">Datasource</span> <span class="gf-form-label max-width-10">Datasource</span>
<div class="gf-form-select-wrapper"> <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> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label"> <label class="gf-form-label">
<span>Color</span> <span>Color</span>
</label> </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> </div>
</div> </div>
<rebuild-on-change property="currentDatasource"> <rebuild-on-change property="ctrl.currentDatasource">
<plugin-component type="annotations-query-ctrl"> <plugin-component type="annotations-query-ctrl">
</plugin-component> </plugin-component>
</rebuild-on-change> </rebuild-on-change>
<div class="gf-form"> <div class="gf-form">
<div class="gf-form-button-row p-y-0"> <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="ctrl.mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="ctrl.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 === 'edit'" type="button" class="btn btn-success pull-left" ng-click="ctrl.update()">Update</button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -12,6 +12,21 @@ class GrafanaDatasource { ...@@ -12,6 +12,21 @@ class GrafanaDatasource {
maxDataPoints: options.maxDataPoints 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}; export {GrafanaDatasource};
...@@ -8,9 +8,22 @@ class GrafanaQueryCtrl extends QueryCtrl { ...@@ -8,9 +8,22 @@ class GrafanaQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html'; 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 { export {
GrafanaDatasource, GrafanaDatasource,
GrafanaDatasource as Datasource, GrafanaDatasource as Datasource,
GrafanaQueryCtrl as QueryCtrl, GrafanaQueryCtrl as QueryCtrl,
GrafanaAnnotationsQueryCtrl as AnnotationsQueryCtrl,
}; };
<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>
...@@ -4,5 +4,6 @@ ...@@ -4,5 +4,6 @@
"id": "grafana", "id": "grafana",
"builtIn": true, "builtIn": true,
"annotations": true,
"metrics": true "metrics": true
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment