The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/api/annotations.go

285 lines
6.8 KiB

package api
import (
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util"
)
func GetAnnotations(c *models.ReqContext) Response {
query := &annotations.ItemQuery{
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
OrgId: c.OrgId,
UserId: c.QueryInt64("userId"),
AlertId: c.QueryInt64("alertId"),
DashboardId: c.QueryInt64("dashboardId"),
PanelId: c.QueryInt64("panelId"),
Limit: c.QueryInt64("limit"),
Tags: c.QueryStrings("tags"),
Type: c.Query("type"),
MatchAny: c.QueryBool("matchAny"),
}
repo := annotations.GetRepository()
items, err := repo.Find(query)
if err != nil {
return Error(500, "Failed to get annotations", err)
}
for _, item := range items {
if item.Email != "" {
item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
}
}
return JSON(200, items)
}
type CreateAnnotationError struct {
message string
}
func (e *CreateAnnotationError) Error() string {
return e.message
}
func PostAnnotation(c *models.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
repo := annotations.GetRepository()
if cmd.Text == "" {
err := &CreateAnnotationError{"text field should not be empty"}
return Error(500, "Failed to save annotation", err)
}
item := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
DashboardId: cmd.DashboardId,
PanelId: cmd.PanelId,
Epoch: cmd.Time,
EpochEnd: cmd.TimeEnd,
Text: cmd.Text,
Data: cmd.Data,
Tags: cmd.Tags,
}
if err := repo.Save(&item); err != nil {
return Error(500, "Failed to save annotation", err)
}
startID := item.Id
return JSON(200, util.DynMap{
"message": "Annotation added",
"id": startID,
})
}
func formatGraphiteAnnotation(what string, data string) string {
text := what
if data != "" {
text = text + "\n" + data
}
return text
}
func PostGraphiteAnnotation(c *models.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd) Response {
repo := annotations.GetRepository()
if cmd.What == "" {
err := &CreateAnnotationError{"what field should not be empty"}
return Error(500, "Failed to save Graphite annotation", err)
}
text := formatGraphiteAnnotation(cmd.What, cmd.Data)
// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
var tagsArray []string
switch tags := cmd.Tags.(type) {
case string:
if tags != "" {
tagsArray = strings.Split(tags, " ")
} else {
tagsArray = []string{}
}
case []interface{}:
for _, t := range tags {
if tagStr, ok := t.(string); ok {
tagsArray = append(tagsArray, tagStr)
} else {
err := &CreateAnnotationError{"tag should be a string"}
return Error(500, "Failed to save Graphite annotation", err)
}
}
default:
err := &CreateAnnotationError{"unsupported tags format"}
return Error(500, "Failed to save Graphite annotation", err)
}
item := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
Epoch: cmd.When * 1000,
Text: text,
Tags: tagsArray,
}
if err := repo.Save(&item); err != nil {
return Error(500, "Failed to save Graphite annotation", err)
}
return JSON(200, util.DynMap{
"message": "Graphite annotation added",
"id": item.Id,
})
}
func UpdateAnnotation(c *models.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository()
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
item := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
Id: annotationID,
Epoch: cmd.Time,
EpochEnd: cmd.TimeEnd,
Text: cmd.Text,
Tags: cmd.Tags,
}
if err := repo.Update(&item); err != nil {
return Error(500, "Failed to update annotation", err)
}
return Success("Annotation updated")
}
func PatchAnnotation(c *models.ReqContext, cmd dtos.PatchAnnotationsCmd) Response {
annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository()
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return Error(404, "Could not find annotation to update", err)
}
existing := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
Id: annotationID,
Epoch: items[0].Time,
EpochEnd: items[0].TimeEnd,
Text: items[0].Text,
Tags: items[0].Tags,
}
if cmd.Tags != nil {
existing.Tags = cmd.Tags
}
if cmd.Text != "" && cmd.Text != existing.Text {
existing.Text = cmd.Text
}
if cmd.Time > 0 && cmd.Time != existing.Epoch {
existing.Epoch = cmd.Time
}
if cmd.TimeEnd > 0 && cmd.TimeEnd != existing.EpochEnd {
existing.EpochEnd = cmd.TimeEnd
}
if err := repo.Update(&existing); err != nil {
return Error(500, "Failed to update annotation", err)
}
return Success("Annotation patched")
}
func DeleteAnnotations(c *models.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
repo := annotations.GetRepository()
err := repo.Delete(&annotations.DeleteParams{
OrgId: c.OrgId,
Id: cmd.AnnotationId,
DashboardId: cmd.DashboardId,
PanelId: cmd.PanelId,
})
if err != nil {
return Error(500, "Failed to delete annotations", err)
}
return Success("Annotations deleted")
}
func DeleteAnnotationByID(c *models.ReqContext) Response {
repo := annotations.GetRepository()
annotationID := c.ParamsInt64(":annotationId")
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
err := repo.Delete(&annotations.DeleteParams{
OrgId: c.OrgId,
Id: annotationID,
})
if err != nil {
return Error(500, "Failed to delete annotation", err)
}
return Success("Annotation deleted")
}
func canSaveByDashboardID(c *models.ReqContext, dashboardID int64) (bool, error) {
if dashboardID == 0 && !c.SignedInUser.HasRole(models.ROLE_EDITOR) {
return false, nil
}
if dashboardID != 0 {
guard := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
return false, err
}
}
return true, nil
}
func canSave(c *models.ReqContext, repo annotations.Repository, annotationID int64) Response {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return Error(500, "Could not find annotation to update", err)
}
dashboardID := items[0].DashboardId
if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
return nil
}