mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	feat(annotations): added support to show grafana stored annotations in graphs, #5982
This commit is contained in:
		@@ -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
									
								
								pkg/api/annotations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								pkg/api/annotations.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
}
 | 
			
		||||
@@ -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))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
								pkg/api/dtos/annotations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								pkg/api/dtos/annotations.go
									
									
									
									
									
										Normal file
									
								
							@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
@@ -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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,6 @@ func (c *EvalContext) GetStateModel() *StateDescription {
 | 
			
		||||
	default:
 | 
			
		||||
		panic("Unknown rule state " + c.Rule.State)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *EvalContext) GetDurationMs() float64 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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]))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user