Browse Source

feat(annotations): working on alert annotations, #5694

Torkel Ödegaard 9 years ago
parent
commit
357358898d

+ 0 - 3
pkg/components/imguploader/imguploader.go

@@ -4,7 +4,6 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 
 
-	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/kr/s3/s3util"
 	"github.com/kr/s3/s3util"
 )
 )
@@ -31,8 +30,6 @@ func (u *S3Uploader) Upload(path string) (string, error) {
 
 
 	s3util.DefaultConfig.AccessKey = u.accessKey
 	s3util.DefaultConfig.AccessKey = u.accessKey
 	s3util.DefaultConfig.SecretKey = u.secretKey
 	s3util.DefaultConfig.SecretKey = u.secretKey
-	log.Info("AccessKey: %s", u.accessKey)
-	log.Info("SecretKey: %s", u.secretKey)
 
 
 	header := make(http.Header)
 	header := make(http.Header)
 	header.Add("x-amz-acl", "public-read")
 	header.Add("x-amz-acl", "public-read")

+ 1 - 0
pkg/models/alert.go

@@ -23,6 +23,7 @@ const (
 	AlertSeverityCritical AlertSeverityType = "critical"
 	AlertSeverityCritical AlertSeverityType = "critical"
 	AlertSeverityWarning  AlertSeverityType = "warning"
 	AlertSeverityWarning  AlertSeverityType = "warning"
 	AlertSeverityInfo     AlertSeverityType = "info"
 	AlertSeverityInfo     AlertSeverityType = "info"
+	AlertSeverityOK       AlertSeverityType = "ok"
 )
 )
 
 
 func (s AlertSeverityType) IsValid() bool {
 func (s AlertSeverityType) IsValid() bool {

+ 0 - 24
pkg/models/annotation.go

@@ -1,24 +0,0 @@
-package models
-
-import (
-	"time"
-
-	"github.com/grafana/grafana/pkg/components/simplejson"
-)
-
-type AnnotationType string
-
-type Annotation struct {
-	Id            int64
-	OrgId         int64
-	Type          AnnotationType
-	Title         string
-	Text          string
-	AlertId       int64
-	UserId        int64
-	PreviousState string
-	NewState      string
-	Timestamp     time.Time
-
-	Data *simplejson.Json
-}

+ 5 - 5
pkg/services/alerting/engine.go

@@ -38,8 +38,8 @@ func (e *Engine) Start() {
 	e.log.Info("Starting Alerting Engine")
 	e.log.Info("Starting Alerting Engine")
 
 
 	go e.alertingTicker()
 	go e.alertingTicker()
-	go e.execDispatch()
-	go e.resultDispatch()
+	go e.execDispatcher()
+	go e.resultDispatcher()
 }
 }
 
 
 func (e *Engine) Stop() {
 func (e *Engine) Stop() {
@@ -70,7 +70,7 @@ func (e *Engine) alertingTicker() {
 	}
 	}
 }
 }
 
 
-func (e *Engine) execDispatch() {
+func (e *Engine) execDispatcher() {
 	for job := range e.execQueue {
 	for job := range e.execQueue {
 		e.log.Debug("Starting executing alert rule %s", job.Rule.Name)
 		e.log.Debug("Starting executing alert rule %s", job.Rule.Name)
 		go e.executeJob(job)
 		go e.executeJob(job)
@@ -92,10 +92,10 @@ func (e *Engine) executeJob(job *Job) {
 	e.resultQueue <- context
 	e.resultQueue <- context
 }
 }
 
 
-func (e *Engine) resultDispatch() {
+func (e *Engine) resultDispatcher() {
 	defer func() {
 	defer func() {
 		if err := recover(); err != nil {
 		if err := recover(); err != nil {
-			e.log.Error("Engine Panic, stopping resultHandler", "error", err, "stack", log.Stack(1))
+			e.log.Error("Panic in resultDispatcher", "error", err, "stack", log.Stack(1))
 		}
 		}
 	}()
 	}()
 
 

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

@@ -56,6 +56,10 @@ func (c *EvalContext) GetStateText() string {
 	}
 	}
 }
 }
 
 
+func (c *EvalContext) GetNotificationTitle() string {
+	return "[" + c.GetStateText() + "] " + c.Rule.Name
+}
+
 func (c *EvalContext) getDashboardSlug() (string, error) {
 func (c *EvalContext) getDashboardSlug() (string, error) {
 	if c.dashboardSlug != "" {
 	if c.dashboardSlug != "" {
 		return c.dashboardSlug, nil
 		return c.dashboardSlug, nil

+ 1 - 0
pkg/services/alerting/notifiers/email.go

@@ -47,6 +47,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
 
 
 	cmd := &m.SendEmailCommand{
 	cmd := &m.SendEmailCommand{
 		Data: map[string]interface{}{
 		Data: map[string]interface{}{
+			"Title":     context.GetNotificationTitle(),
 			"RuleState": context.Rule.State,
 			"RuleState": context.Rule.State,
 			"RuleName":  context.Rule.Name,
 			"RuleName":  context.Rule.Name,
 			"Severity":  context.Rule.Severity,
 			"Severity":  context.Rule.Severity,

+ 1 - 3
pkg/services/alerting/notifiers/slack.go

@@ -39,8 +39,6 @@ type SlackNotifier struct {
 func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
 func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
 	this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name)
 	this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name)
 
 
-	rule := context.Rule
-
 	ruleUrl, err := context.GetRuleUrl()
 	ruleUrl, err := context.GetRuleUrl()
 	if err != nil {
 	if err != nil {
 		this.log.Error("Failed get rule link", "error", err)
 		this.log.Error("Failed get rule link", "error", err)
@@ -68,7 +66,7 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
 				// "author_name": "Bobby Tables",
 				// "author_name": "Bobby Tables",
 				// "author_link": "http://flickr.com/bobby/",
 				// "author_link": "http://flickr.com/bobby/",
 				// "author_icon": "http://flickr.com/icons/bobby.jpg",
 				// "author_icon": "http://flickr.com/icons/bobby.jpg",
-				"title":      "[" + context.GetStateText() + "] " + rule.Name,
+				"title":      context.GetNotificationTitle(),
 				"title_link": ruleUrl,
 				"title_link": ruleUrl,
 				// "text":       "Optional text that appears within the attachment",
 				// "text":       "Optional text that appears within the attachment",
 				"fields":    fields,
 				"fields":    fields,

+ 3 - 1
pkg/services/alerting/notifiers/webhook.go

@@ -42,7 +42,9 @@ func (this *WebhookNotifier) Notify(context *alerting.EvalContext) {
 	this.log.Info("Sending webhook")
 	this.log.Info("Sending webhook")
 
 
 	bodyJSON := simplejson.New()
 	bodyJSON := simplejson.New()
-	bodyJSON.Set("name", context.Rule.Name)
+	bodyJSON.Set("title", context.GetNotificationTitle())
+	bodyJSON.Set("ruleId", context.Rule.Id)
+	bodyJSON.Set("ruleName", context.Rule.Name)
 	bodyJSON.Set("firing", context.Firing)
 	bodyJSON.Set("firing", context.Firing)
 	bodyJSON.Set("severity", context.Rule.Severity)
 	bodyJSON.Set("severity", context.Rule.Severity)
 
 

+ 34 - 16
pkg/services/alerting/result_handler.go

@@ -1,13 +1,16 @@
 package alerting
 package alerting
 
 
 import (
 import (
+	"time"
+
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/annotations"
 )
 )
 
 
 type ResultHandler interface {
 type ResultHandler interface {
-	Handle(result *EvalContext)
+	Handle(ctx *EvalContext)
 }
 }
 
 
 type DefaultResultHandler struct {
 type DefaultResultHandler struct {
@@ -22,32 +25,47 @@ func NewResultHandler() *DefaultResultHandler {
 	}
 	}
 }
 }
 
 
-func (handler *DefaultResultHandler) Handle(result *EvalContext) {
-	var newState m.AlertStateType
+func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
+	oldState := ctx.Rule.State
 
 
-	if result.Error != nil {
-		handler.log.Error("Alert Rule Result Error", "ruleId", result.Rule.Id, "error", result.Error)
-		newState = m.AlertStatePending
-	} else if result.Firing {
-		newState = m.AlertStateFiring
+	if ctx.Error != nil {
+		handler.log.Error("Alert Rule Result Error", "ruleId", ctx.Rule.Id, "error", ctx.Error)
+		ctx.Rule.State = m.AlertStatePending
+	} else if ctx.Firing {
+		ctx.Rule.State = m.AlertStateFiring
 	} else {
 	} else {
-		newState = m.AlertStateOK
+		ctx.Rule.State = m.AlertStateOK
 	}
 	}
 
 
-	if result.Rule.State != newState {
-		handler.log.Info("New state change", "alertId", result.Rule.Id, "newState", newState, "oldState", result.Rule.State)
+	if ctx.Rule.State != oldState {
+		handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
 
 
 		cmd := &m.SetAlertStateCommand{
 		cmd := &m.SetAlertStateCommand{
-			AlertId: result.Rule.Id,
-			OrgId:   result.Rule.OrgId,
-			State:   newState,
+			AlertId: ctx.Rule.Id,
+			OrgId:   ctx.Rule.OrgId,
+			State:   ctx.Rule.State,
 		}
 		}
 
 
 		if err := bus.Dispatch(cmd); err != nil {
 		if err := bus.Dispatch(cmd); err != nil {
 			handler.log.Error("Failed to save state", "error", err)
 			handler.log.Error("Failed to save state", "error", err)
 		}
 		}
 
 
-		result.Rule.State = newState
-		handler.notifier.Notify(result)
+		// save annotation
+		item := annotations.Item{
+			OrgId:     ctx.Rule.OrgId,
+			Type:      annotations.AlertType,
+			AlertId:   ctx.Rule.Id,
+			Title:     ctx.Rule.Name,
+			Text:      ctx.GetStateText(),
+			NewState:  string(ctx.Rule.State),
+			PrevState: string(oldState),
+			Timestamp: time.Now(),
+		}
+		annotationRepo := annotations.GetRepository()
+		if err := annotationRepo.Save(&item); err != nil {
+			handler.log.Error("Failed to save annotation for new alert state", "error", err)
+		}
+
+		handler.notifier.Notify(ctx)
 	}
 	}
 }
 }

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

@@ -19,7 +19,7 @@ func NewScheduler() Scheduler {
 }
 }
 
 
 func (s *SchedulerImpl) Update(rules []*Rule) {
 func (s *SchedulerImpl) Update(rules []*Rule) {
-	s.log.Debug("Scheduling update", "rules.count", len(rules))
+	s.log.Debug("Scheduling update", "ruleCount", len(rules))
 
 
 	jobs := make(map[int64]*Job, 0)
 	jobs := make(map[int64]*Job, 0)
 
 

+ 44 - 0
pkg/services/annotations/annotations.go

@@ -0,0 +1,44 @@
+package annotations
+
+import (
+	"time"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+)
+
+type Repository interface {
+	Save(item *Item) error
+}
+
+var repositoryInstance Repository
+
+func GetRepository() Repository {
+	return repositoryInstance
+}
+
+func SetRepository(rep Repository) {
+	repositoryInstance = rep
+}
+
+type ItemType string
+
+const (
+	AlertType ItemType = "alert"
+)
+
+type Item struct {
+	Id          int64     `json:"id"`
+	OrgId       int64     `json:"orgId"`
+	PanelLinkId string    `json:"panelLinkId"`
+	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"`
+
+	Data *simplejson.Json `json:"data"`
+}

+ 21 - 0
pkg/services/sqlstore/annotation.go

@@ -0,0 +1,21 @@
+package sqlstore
+
+import (
+	"github.com/go-xorm/xorm"
+	"github.com/grafana/grafana/pkg/services/annotations"
+)
+
+type SqlAnnotationRepo struct {
+}
+
+func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
+	return inTransaction(func(sess *xorm.Session) error {
+
+		if _, err := sess.Table("annotation").Insert(item); err != nil {
+			return err
+		}
+
+		return nil
+	})
+
+}

+ 0 - 15
pkg/services/sqlstore/migrations/alert_mig.go

@@ -31,21 +31,6 @@ func addAlertMigrations(mg *Migrator) {
 	// create table
 	// create table
 	mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1))
 	mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1))
 
 
-	alert_state_log := Table{
-		Name: "alert_state",
-		Columns: []*Column{
-			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-			{Name: "alert_id", Type: DB_BigInt, Nullable: false},
-			{Name: "org_id", Type: DB_BigInt, Nullable: false},
-			{Name: "state", Type: DB_NVarchar, Length: 50, Nullable: false},
-			{Name: "info", Type: DB_Text, Nullable: true},
-			{Name: "triggered_alerts", Type: DB_Text, Nullable: true},
-			{Name: "created", Type: DB_DateTime, Nullable: false},
-		},
-	}
-
-	mg.AddMigration("create alert_state_log table v1", NewAddTableMigration(alert_state_log))
-
 	alert_heartbeat := Table{
 	alert_heartbeat := Table{
 		Name: "alert_heartbeat",
 		Name: "alert_heartbeat",
 		Columns: []*Column{
 		Columns: []*Column{

+ 40 - 0
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -0,0 +1,40 @@
+package migrations
+
+import (
+	. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+)
+
+func addAnnotationMig(mg *Migrator) {
+	table := Table{
+		Name: "annotation",
+		Columns: []*Column{
+			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			{Name: "alert_id", Type: DB_BigInt, Nullable: true},
+			{Name: "user_id", Type: DB_BigInt, Nullable: true},
+			{Name: "panel_link_id", Type: DB_NVarchar, Length: 32, Nullable: false},
+			{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
+			{Name: "title", Type: DB_Text, Nullable: false},
+			{Name: "text", Type: DB_Text, Nullable: false},
+			{Name: "metric", Type: DB_NVarchar, Length: 255, Nullable: true},
+			{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},
+		},
+		Indices: []*Index{
+			{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
+			{Cols: []string{"org_id", "type"}, Type: IndexType},
+			{Cols: []string{"org_id", "panel_link_id"}, Type: IndexType},
+			{Cols: []string{"timestamp"}, Type: IndexType},
+		},
+	}
+
+	mg.AddMigration("create annotation table v1", 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 & type", NewAddIndexMigration(table, table.Indices[1]))
+	mg.AddMigration("add index annotation org_id & panel_link_id ", NewAddIndexMigration(table, table.Indices[2]))
+	mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[3]))
+}

+ 1 - 0
pkg/services/sqlstore/migrations/migrations.go

@@ -23,6 +23,7 @@ func AddMigrations(mg *Migrator) {
 	addPlaylistMigrations(mg)
 	addPlaylistMigrations(mg)
 	addPreferencesMigrations(mg)
 	addPreferencesMigrations(mg)
 	addAlertMigrations(mg)
 	addAlertMigrations(mg)
+	addAnnotationMig(mg)
 }
 }
 
 
 func addMigrationLogMigrations(mg *Migrator) {
 func addMigrationLogMigrations(mg *Migrator) {

+ 3 - 0
pkg/services/sqlstore/sqlstore.go

@@ -10,6 +10,7 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/annotations"
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
@@ -97,6 +98,8 @@ func SetEngine(engine *xorm.Engine) (err error) {
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
 	}
 	}
 
 
+	annotations.SetRepository(&SqlAnnotationRepo{})
+
 	return nil
 	return nil
 }
 }
 
 

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

@@ -10,7 +10,7 @@ import {
 var alertQueryDef = new QueryPartDef({
 var alertQueryDef = new QueryPartDef({
   type: 'query',
   type: 'query',
   params: [
   params: [
-    {name: "queryRefId", type: 'string', options: ['#A', '#B', '#C', '#D']},
+    {name: "queryRefId", type: 'string', options: ['A', 'B', 'C', 'D', 'E', 'F']},
     {name: "from", type: "string", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']},
     {name: "from", type: "string", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']},
     {name: "to", type: "string", options: ['now']},
     {name: "to", type: "string", options: ['now']},
   ],
   ],
@@ -142,7 +142,7 @@ export class AlertTabCtrl {
       return memo;
       return memo;
     }, []);
     }, []);
 
 
-    ///this.panelCtrl.editingAlert = true;
+    this.panelCtrl.editingAlert = true;
     this.syncThresholds();
     this.syncThresholds();
     this.panelCtrl.render();
     this.panelCtrl.render();
   }
   }