diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 37d247a7c8f..bd40d8d63e9 100644 --- a/pkg/api/alerting.go +++ b/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") diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go new file mode 100644 index 00000000000..4e43a7ea21e --- /dev/null +++ b/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) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index de3a1de8a65..71331acda9f 100644 --- a/pkg/api/api.go +++ b/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)) diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 83b21757d32..2b91cf0cb5b 100644 --- a/pkg/api/dtos/alerting.go +++ b/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"` diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go new file mode 100644 index 00000000000..2068b6186d2 --- /dev/null +++ b/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"` +} diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 5a84409d2c1..3a019e80c49 100644 --- a/pkg/api/frontendsettings.go +++ b/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, } diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index d1ac851daf1..f54a4e212be 100644 --- a/pkg/services/alerting/eval_context.go +++ b/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 { diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 7932aead4aa..6239a1e58d7 100644 --- a/pkg/services/alerting/result_handler.go +++ b/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, } diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index 06be152ec31..e9ef899eca9 100644 --- a/pkg/services/annotations/annotations.go +++ b/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"` } diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 5cf4461b370..e5a5443c3bf 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/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 { diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 11b4eeed629..ddb622894ac 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/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])) } diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index dbe9932d574..60685fae74e 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/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, }; }); diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index f3cc998deaa..d19904eea0d 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/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); diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 770e3a2cb05..c5517990934 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -10,7 +10,7 @@
  • - Alert History + State history
  • Delete @@ -136,7 +136,7 @@
    -
    Alert history
    +
    State history (last 50 state changes)
    1. diff --git a/public/app/features/annotations/editor_ctrl.js b/public/app/features/annotations/editor_ctrl.js deleted file mode 100644 index c37592cc905..00000000000 --- a/public/app/features/annotations/editor_ctrl.js +++ /dev/null @@ -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(); - }; - - }); - -}); diff --git a/public/app/features/annotations/editor_ctrl.ts b/public/app/features/annotations/editor_ctrl.ts new file mode 100644 index 00000000000..6d4837def48 --- /dev/null +++ b/public/app/features/annotations/editor_ctrl.ts @@ -0,0 +1,82 @@ +/// + +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); diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 702d8921bef..f42e5a3a7ae 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -1,4 +1,4 @@ -
      +

      Annotations @@ -6,16 +6,16 @@ @@ -26,18 +26,18 @@

      -
      -
      +
      +
      No annotations defined
      - + - - + + @@ -54,43 +54,43 @@
        {{annotation.name}} @@ -46,7 +46,7 @@ - +
      -
      + -
      +
      Name - +
      Datasource
      - +
      - +
      - +
      - - + +
      diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index b2914155107..bdd6ba014a2 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/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}; diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index be560940032..615a0b2d175 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/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, }; diff --git a/public/app/plugins/datasource/grafana/partials/annotations.editor.html b/public/app/plugins/datasource/grafana/partials/annotations.editor.html new file mode 100644 index 00000000000..54e21ac902e --- /dev/null +++ b/public/app/plugins/datasource/grafana/partials/annotations.editor.html @@ -0,0 +1,20 @@ + +
      +
      Filters
      +
      +
      + Type +
      + +
      +
      +
      + Max limit +
      + +
      +
      +
      +
      diff --git a/public/app/plugins/datasource/grafana/plugin.json b/public/app/plugins/datasource/grafana/plugin.json index fdccb24b59d..906ef0e9bf1 100644 --- a/public/app/plugins/datasource/grafana/plugin.json +++ b/public/app/plugins/datasource/grafana/plugin.json @@ -4,5 +4,6 @@ "id": "grafana", "builtIn": true, + "annotations": true, "metrics": true }