diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 47c77425843..84009e5a9de 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -288,3 +288,19 @@ func canSave(c *models.ReqContext, repo annotations.Repository, annotationID int return nil } + +func GetAnnotationTags(c *models.ReqContext) response.Response { + query := &annotations.TagsQuery{ + OrgID: c.OrgId, + Tag: c.Query("tag"), + Limit: c.QueryInt64("limit"), + } + + repo := annotations.GetRepository() + result, err := repo.FindTags(query) + if err != nil { + return response.Error(500, "Failed to find annotation tags", err) + } + + return response.JSON(200, util.DynMap{"result": result}) +} diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index d901bd5d42c..b5118494bcc 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -259,6 +259,12 @@ func (repo *fakeAnnotationsRepo) Find(query *annotations.ItemQuery) ([]*annotati annotations := []*annotations.ItemDTO{{Id: 1}} return annotations, nil } +func (repo *fakeAnnotationsRepo) FindTags(query *annotations.TagsQuery) (annotations.FindTagsResult, error) { + result := annotations.FindTagsResult{ + Tags: []*annotations.TagsDTO{}, + } + return result, nil +} var fakeAnnoRepo *fakeAnnotationsRepo diff --git a/pkg/api/api.go b/pkg/api/api.go index 949c7f7534e..7e1e7602572 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -410,6 +410,7 @@ func (hs *HTTPServer) registerRoutes() { annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), routing.Wrap(UpdateAnnotation)) annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), routing.Wrap(PatchAnnotation)) annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), routing.Wrap(PostGraphiteAnnotation)) + annotationsRoute.Get("/tags", routing.Wrap(GetAnnotationTags)) }) apiRoute.Post("/frontend-metrics", bind(metrics.PostFrontendMetricsCommand{}), routing.Wrap(hs.PostFrontendMetrics)) diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index 400bbff939b..e008ba2009f 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -17,6 +17,7 @@ type Repository interface { Update(item *Item) error Find(query *ItemQuery) ([]*ItemDTO, error) Delete(params *DeleteParams) error + FindTags(query *TagsQuery) (FindTagsResult, error) } // AnnotationCleaner is responsible for cleaning up old annotations @@ -40,6 +41,28 @@ type ItemQuery struct { Limit int64 `json:"limit"` } +type TagsQuery struct { + OrgID int64 `json:"orgId"` + Tag string `json:"tag"` + + Limit int64 `json:"limit"` +} + +type Tags struct { + Key string + Value string + Count int64 +} + +type TagsDTO struct { + Tag string `json:"tag"` + Count int64 `json:"count"` +} + +type FindTagsResult struct { + Tags []*TagsDTO `json:"tags"` +} + type DeleteParams struct { OrgId int64 Id int64 diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 0a752cd7a20..52eb65468c0 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -268,3 +268,52 @@ func (r *SQLAnnotationRepo) Delete(params *annotations.DeleteParams) error { return nil }) } + +func (r *SQLAnnotationRepo) FindTags(query *annotations.TagsQuery) (annotations.FindTagsResult, error) { + if query.Limit == 0 { + query.Limit = 100 + } + + var sql bytes.Buffer + params := make([]interface{}, 0) + tagKey := `tag.` + dialect.Quote("key") + tagValue := `tag.` + dialect.Quote("value") + + sql.WriteString(` + SELECT + ` + tagKey + `, + ` + tagValue + `, + count(*) as count + FROM tag + INNER JOIN annotation_tag ON tag.id = annotation_tag.tag_id +`) + + sql.WriteString(`WHERE EXISTS(SELECT 1 FROM annotation WHERE annotation.id = annotation_tag.annotation_id AND annotation.org_id = ?)`) + params = append(params, query.OrgID) + + sql.WriteString(` AND (` + tagKey + ` ` + dialect.LikeStr() + ` ? OR ` + tagValue + ` ` + dialect.LikeStr() + ` ?)`) + params = append(params, `%`+query.Tag+`%`, `%`+query.Tag+`%`) + + sql.WriteString(` GROUP BY ` + tagKey + `,` + tagValue) + sql.WriteString(` ORDER BY ` + tagKey + `,` + tagValue) + sql.WriteString(` ` + dialect.Limit(query.Limit)) + + var items []*annotations.Tags + if err := x.SQL(sql.String(), params...).Find(&items); err != nil { + return annotations.FindTagsResult{Tags: []*annotations.TagsDTO{}}, err + } + + tags := make([]*annotations.TagsDTO, 0) + for _, item := range items { + tag := item.Key + if len(item.Value) > 0 { + tag = item.Key + ":" + item.Value + } + tags = append(tags, &annotations.TagsDTO{ + Tag: tag, + Count: item.Count, + }) + } + + return annotations.FindTagsResult{Tags: tags}, nil +} diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index 05b902134b0..d753f037fa2 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -251,5 +251,47 @@ func TestAnnotations(t *testing.T) { require.NoError(t, err) assert.Empty(t, items) }) + + t.Run("Should find tags by key", func(t *testing.T) { + result, err := repo.FindTags(&annotations.TagsQuery{ + OrgID: 1, + Tag: "server", + }) + require.NoError(t, err) + require.Len(t, result.Tags, 1) + require.Equal(t, "server:server-1", result.Tags[0].Tag) + require.Equal(t, int64(1), result.Tags[0].Count) + }) + + t.Run("Should find tags by value", func(t *testing.T) { + result, err := repo.FindTags(&annotations.TagsQuery{ + OrgID: 1, + Tag: "outage", + }) + require.NoError(t, err) + require.Len(t, result.Tags, 2) + require.Equal(t, "outage", result.Tags[0].Tag) + require.Equal(t, "type:outage", result.Tags[1].Tag) + require.Equal(t, int64(1), result.Tags[0].Count) + require.Equal(t, int64(1), result.Tags[1].Count) + }) + + t.Run("Should not find tags in other org", func(t *testing.T) { + result, err := repo.FindTags(&annotations.TagsQuery{ + OrgID: 0, + Tag: "server-1", + }) + require.NoError(t, err) + require.Len(t, result.Tags, 0) + }) + + t.Run("Should not find tags that do not exist", func(t *testing.T) { + result, err := repo.FindTags(&annotations.TagsQuery{ + OrgID: 0, + Tag: "unknown:tag", + }) + require.NoError(t, err) + require.Len(t, result.Tags, 0) + }) }) }