Commit 35735889 by Torkel Ödegaard

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

parent c5e90b18
......@@ -4,7 +4,6 @@ import (
"io/ioutil"
"net/http"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/util"
"github.com/kr/s3/s3util"
)
......@@ -31,8 +30,6 @@ func (u *S3Uploader) Upload(path string) (string, error) {
s3util.DefaultConfig.AccessKey = u.accessKey
s3util.DefaultConfig.SecretKey = u.secretKey
log.Info("AccessKey: %s", u.accessKey)
log.Info("SecretKey: %s", u.secretKey)
header := make(http.Header)
header.Add("x-amz-acl", "public-read")
......
......@@ -23,6 +23,7 @@ const (
AlertSeverityCritical AlertSeverityType = "critical"
AlertSeverityWarning AlertSeverityType = "warning"
AlertSeverityInfo AlertSeverityType = "info"
AlertSeverityOK AlertSeverityType = "ok"
)
func (s AlertSeverityType) IsValid() bool {
......
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
}
......@@ -38,8 +38,8 @@ func (e *Engine) Start() {
e.log.Info("Starting Alerting Engine")
go e.alertingTicker()
go e.execDispatch()
go e.resultDispatch()
go e.execDispatcher()
go e.resultDispatcher()
}
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 {
e.log.Debug("Starting executing alert rule %s", job.Rule.Name)
go e.executeJob(job)
......@@ -92,10 +92,10 @@ func (e *Engine) executeJob(job *Job) {
e.resultQueue <- context
}
func (e *Engine) resultDispatch() {
func (e *Engine) resultDispatcher() {
defer func() {
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))
}
}()
......
......@@ -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) {
if c.dashboardSlug != "" {
return c.dashboardSlug, nil
......
......@@ -47,6 +47,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
cmd := &m.SendEmailCommand{
Data: map[string]interface{}{
"Title": context.GetNotificationTitle(),
"RuleState": context.Rule.State,
"RuleName": context.Rule.Name,
"Severity": context.Rule.Severity,
......
......@@ -39,8 +39,6 @@ type SlackNotifier struct {
func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name)
rule := context.Rule
ruleUrl, err := context.GetRuleUrl()
if err != nil {
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_link": "http://flickr.com/bobby/",
// "author_icon": "http://flickr.com/icons/bobby.jpg",
"title": "[" + context.GetStateText() + "] " + rule.Name,
"title": context.GetNotificationTitle(),
"title_link": ruleUrl,
// "text": "Optional text that appears within the attachment",
"fields": fields,
......
......@@ -42,7 +42,9 @@ func (this *WebhookNotifier) Notify(context *alerting.EvalContext) {
this.log.Info("Sending webhook")
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("severity", context.Rule.Severity)
......
package alerting
import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
)
type ResultHandler interface {
Handle(result *EvalContext)
Handle(ctx *EvalContext)
}
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 {
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{
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 {
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)
}
}
......@@ -19,7 +19,7 @@ func NewScheduler() Scheduler {
}
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)
......
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"`
}
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
})
}
......@@ -31,21 +31,6 @@ func addAlertMigrations(mg *Migrator) {
// create table
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{
Name: "alert_heartbeat",
Columns: []*Column{
......
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]))
}
......@@ -23,6 +23,7 @@ func AddMigrations(mg *Migrator) {
addPlaylistMigrations(mg)
addPreferencesMigrations(mg)
addAlertMigrations(mg)
addAnnotationMig(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
......
......@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
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/migrator"
"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)
}
annotations.SetRepository(&SqlAnnotationRepo{})
return nil
}
......
......@@ -10,7 +10,7 @@ import {
var alertQueryDef = new QueryPartDef({
type: 'query',
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: "to", type: "string", options: ['now']},
],
......@@ -142,7 +142,7 @@ export class AlertTabCtrl {
return memo;
}, []);
///this.panelCtrl.editingAlert = true;
this.panelCtrl.editingAlert = true;
this.syncThresholds();
this.panelCtrl.render();
}
......
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