Chore: SQL store split for annotations (#55089)

* Chore: SQL store split for annotations

* Apply suggestion from code review
This commit is contained in:
Sofia Papagiannaki 2022-09-19 10:54:37 +03:00 committed by GitHub
parent 469f915b8c
commit 754eea20b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 499 additions and 467 deletions

View File

@ -63,9 +63,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response {
} }
} }
repo := annotations.GetRepository() items, err := hs.annotationsRepo.Find(c.Req.Context(), query)
items, err := repo.Find(c.Req.Context(), query)
if err != nil { if err != nil {
return response.Error(500, "Failed to get annotations", err) return response.Error(500, "Failed to get annotations", err)
} }
@ -135,8 +133,6 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
repo := annotations.GetRepository()
if cmd.Text == "" { if cmd.Text == "" {
err := &AnnotationError{"text field should not be empty"} err := &AnnotationError{"text field should not be empty"}
return response.Error(400, "Failed to save annotation", err) return response.Error(400, "Failed to save annotation", err)
@ -154,7 +150,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response {
Tags: cmd.Tags, Tags: cmd.Tags,
} }
if err := repo.Save(&item); err != nil { if err := hs.annotationsRepo.Save(c.Req.Context(), &item); err != nil {
if errors.Is(err, annotations.ErrTimerangeMissing) { if errors.Is(err, annotations.ErrTimerangeMissing) {
return response.Error(400, "Failed to save annotation", err) return response.Error(400, "Failed to save annotation", err)
} }
@ -194,8 +190,6 @@ func (hs *HTTPServer) PostGraphiteAnnotation(c *models.ReqContext) response.Resp
if err := web.Bind(c.Req, &cmd); err != nil { if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
repo := annotations.GetRepository()
if cmd.What == "" { if cmd.What == "" {
err := &AnnotationError{"what field should not be empty"} err := &AnnotationError{"what field should not be empty"}
return response.Error(400, "Failed to save Graphite annotation", err) return response.Error(400, "Failed to save Graphite annotation", err)
@ -234,7 +228,7 @@ func (hs *HTTPServer) PostGraphiteAnnotation(c *models.ReqContext) response.Resp
Tags: tagsArray, Tags: tagsArray,
} }
if err := repo.Save(&item); err != nil { if err := hs.annotationsRepo.Save(context.Background(), &item); err != nil {
return response.Error(500, "Failed to save Graphite annotation", err) return response.Error(500, "Failed to save Graphite annotation", err)
} }
@ -267,9 +261,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "annotationId is invalid", err) return response.Error(http.StatusBadRequest, "annotationId is invalid", err)
} }
repo := annotations.GetRepository() annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil { if resp != nil {
return resp return resp
} }
@ -288,7 +280,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response {
Tags: cmd.Tags, Tags: cmd.Tags,
} }
if err := repo.Update(c.Req.Context(), &item); err != nil { if err := hs.annotationsRepo.Update(c.Req.Context(), &item); err != nil {
return response.Error(500, "Failed to update annotation", err) return response.Error(500, "Failed to update annotation", err)
} }
@ -319,9 +311,7 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "annotationId is invalid", err) return response.Error(http.StatusBadRequest, "annotationId is invalid", err)
} }
repo := annotations.GetRepository() annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil { if resp != nil {
return resp return resp
} }
@ -356,7 +346,7 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response {
existing.EpochEnd = cmd.TimeEnd existing.EpochEnd = cmd.TimeEnd
} }
if err := repo.Update(c.Req.Context(), &existing); err != nil { if err := hs.annotationsRepo.Update(c.Req.Context(), &existing); err != nil {
return response.Error(500, "Failed to update annotation", err) return response.Error(500, "Failed to update annotation", err)
} }
@ -391,7 +381,6 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
repo := annotations.GetRepository()
var deleteParams *annotations.DeleteParams var deleteParams *annotations.DeleteParams
// validations only for RBAC. A user can mass delete all annotations in a (dashboard + panel) or a specific annotation // validations only for RBAC. A user can mass delete all annotations in a (dashboard + panel) or a specific annotation
@ -400,7 +389,7 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo
var dashboardId int64 var dashboardId int64
if cmd.AnnotationId != 0 { if cmd.AnnotationId != 0 {
annotation, respErr := findAnnotationByID(c.Req.Context(), repo, cmd.AnnotationId, c.SignedInUser) annotation, respErr := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, cmd.AnnotationId, c.SignedInUser)
if respErr != nil { if respErr != nil {
return respErr return respErr
} }
@ -431,7 +420,7 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo
} }
} }
err = repo.Delete(c.Req.Context(), deleteParams) err = hs.annotationsRepo.Delete(c.Req.Context(), deleteParams)
if err != nil { if err != nil {
return response.Error(500, "Failed to delete annotations", err) return response.Error(500, "Failed to delete annotations", err)
@ -454,9 +443,7 @@ func (hs *HTTPServer) GetAnnotationByID(c *models.ReqContext) response.Response
return response.Error(http.StatusBadRequest, "annotationId is invalid", err) return response.Error(http.StatusBadRequest, "annotationId is invalid", err)
} }
repo := annotations.GetRepository() annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil { if resp != nil {
return resp return resp
} }
@ -485,9 +472,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon
return response.Error(http.StatusBadRequest, "annotationId is invalid", err) return response.Error(http.StatusBadRequest, "annotationId is invalid", err)
} }
repo := annotations.GetRepository() annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil { if resp != nil {
return resp return resp
} }
@ -496,7 +481,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
err = repo.Delete(c.Req.Context(), &annotations.DeleteParams{ err = hs.annotationsRepo.Delete(c.Req.Context(), &annotations.DeleteParams{
OrgId: c.OrgID, OrgId: c.OrgID,
Id: annotationID, Id: annotationID,
}) })
@ -563,8 +548,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *models.ReqContext) response.Response
Limit: c.QueryInt64("limit"), Limit: c.QueryInt64("limit"),
} }
repo := annotations.GetRepository() result, err := hs.annotationsRepo.FindTags(c.Req.Context(), query)
result, err := repo.FindTags(c.Req.Context(), query)
if err != nil { if err != nil {
return response.Error(500, "Failed to find annotation tags", err) return response.Error(500, "Failed to find annotation tags", err)
} }
@ -575,7 +559,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *models.ReqContext) response.Response
// AnnotationTypeScopeResolver provides an ScopeAttributeResolver able to // AnnotationTypeScopeResolver provides an ScopeAttributeResolver able to
// resolve annotation types. Scope "annotations:id:<id>" will be translated to "annotations:type:<type>, // resolve annotation types. Scope "annotations:id:<id>" will be translated to "annotations:type:<type>,
// where <type> is the type of annotation with id <id>. // where <type> is the type of annotation with id <id>.
func AnnotationTypeScopeResolver() (string, accesscontrol.ScopeAttributeResolver) { func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository) (string, accesscontrol.ScopeAttributeResolver) {
prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("") prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("")
return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) { return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) {
scopeParts := strings.Split(initialScope, ":") scopeParts := strings.Split(initialScope, ":")
@ -601,7 +585,7 @@ func AnnotationTypeScopeResolver() (string, accesscontrol.ScopeAttributeResolver
}, },
} }
annotation, resp := findAnnotationByID(ctx, annotations.GetRepository(), int64(annotationId), tempUser) annotation, resp := findAnnotationByID(ctx, annotationsRepo, int64(annotationId), tempUser)
if resp != nil { if resp != nil {
return nil, errors.New("could not resolve annotation type") return nil, errors.New("could not resolve annotation type")
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -73,8 +74,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
mock := mockstore.NewSQLStoreMock() mock := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) { "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = hs.DeleteAnnotationByID sc.handlerFunc = hs.DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
@ -103,8 +102,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
mock := mockstore.NewSQLStoreMock() mock := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) { "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = hs.DeleteAnnotationByID sc.handlerFunc = hs.DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
@ -178,8 +175,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) { "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUpACL() setUpACL()
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = hs.DeleteAnnotationByID sc.handlerFunc = hs.DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
@ -211,8 +206,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) { "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUpACL() setUpACL()
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = hs.DeleteAnnotationByID sc.handlerFunc = hs.DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
@ -286,59 +279,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
}) })
} }
type fakeAnnotationsRepo struct {
annotations map[int64]annotations.Item
}
func NewFakeAnnotationsRepo() *fakeAnnotationsRepo {
return &fakeAnnotationsRepo{
annotations: map[int64]annotations.Item{},
}
}
func (repo *fakeAnnotationsRepo) Delete(_ context.Context, params *annotations.DeleteParams) error {
if params.Id != 0 {
delete(repo.annotations, params.Id)
} else {
for _, v := range repo.annotations {
if params.DashboardId == v.DashboardId && params.PanelId == v.PanelId {
delete(repo.annotations, v.Id)
}
}
}
return nil
}
func (repo *fakeAnnotationsRepo) Save(item *annotations.Item) error {
if item.Id == 0 {
item.Id = int64(len(repo.annotations) + 1)
}
repo.annotations[item.Id] = *item
return nil
}
func (repo *fakeAnnotationsRepo) Update(_ context.Context, item *annotations.Item) error {
return nil
}
func (repo *fakeAnnotationsRepo) Find(_ context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
if annotation, has := repo.annotations[query.AnnotationId]; has {
return []*annotations.ItemDTO{{Id: annotation.Id, DashboardId: annotation.DashboardId}}, nil
}
annotations := []*annotations.ItemDTO{{Id: 1, DashboardId: 0}}
return annotations, nil
}
func (repo *fakeAnnotationsRepo) FindTags(_ context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
result := annotations.FindTagsResult{
Tags: []*annotations.TagsDTO{},
}
return result, nil
}
func (repo *fakeAnnotationsRepo) LoadItems() {
}
var fakeAnnoRepo *fakeAnnotationsRepo
func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role org.RoleType, func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role org.RoleType,
cmd dtos.PostAnnotationsCmd, store sqlstore.Store, dashSvc dashboards.DashboardService, fn scenarioFunc) { cmd dtos.PostAnnotationsCmd, store sqlstore.Store, dashSvc dashboards.DashboardService, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
@ -358,9 +298,6 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern
return hs.PostAnnotation(c) return hs.PostAnnotation(c)
}) })
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.m.Post(routePattern, sc.defaultHandler) sc.m.Post(routePattern, sc.defaultHandler)
fn(sc) fn(sc)
@ -387,9 +324,6 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s
return hs.UpdateAnnotation(c) return hs.UpdateAnnotation(c)
}) })
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.m.Put(routePattern, sc.defaultHandler) sc.m.Put(routePattern, sc.defaultHandler)
fn(sc) fn(sc)
@ -415,9 +349,6 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern
return hs.PatchAnnotation(c) return hs.PatchAnnotation(c)
}) })
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.m.Patch(routePattern, sc.defaultHandler) sc.m.Patch(routePattern, sc.defaultHandler)
fn(sc) fn(sc)
@ -443,9 +374,6 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte
return hs.MassDeleteAnnotations(c) return hs.MassDeleteAnnotations(c)
}) })
fakeAnnoRepo = NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
sc.m.Post(routePattern, sc.defaultHandler) sc.m.Post(routePattern, sc.defaultHandler)
fn(sc) fn(sc)
@ -461,11 +389,8 @@ func TestAPI_Annotations_AccessControl(t *testing.T) {
dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1}
organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0}
fakeAnnoRepo = NewFakeAnnotationsRepo() _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation)
_ = fakeAnnoRepo.Save(dashboardAnnotation) _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation)
_ = fakeAnnoRepo.Save(organizationAnnotation)
annotations.SetRepository(fakeAnnoRepo)
postOrganizationCmd := dtos.PostAnnotationsCmd{ postOrganizationCmd := dtos.PostAnnotationsCmd{
Time: 1000, Time: 1000,
@ -788,7 +713,7 @@ func TestAPI_Annotations_AccessControl(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
setUpRBACGuardian(t) setUpRBACGuardian(t)
sc.acmock. sc.acmock.
RegisterScopeAttributeResolver(AnnotationTypeScopeResolver()) RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(sc.hs.annotationsRepo))
setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID) setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID)
r := callAPI(sc.server, tt.args.method, tt.args.url, tt.args.body, t) r := callAPI(sc.server, tt.args.method, tt.args.url, tt.args.body, t)
@ -835,13 +760,11 @@ func TestService_AnnotationTypeScopeResolver(t *testing.T) {
dashboardAnnotation := annotations.Item{Id: 1, DashboardId: 1} dashboardAnnotation := annotations.Item{Id: 1, DashboardId: 1}
organizationAnnotation := annotations.Item{Id: 2} organizationAnnotation := annotations.Item{Id: 2}
fakeAnnoRepo = NewFakeAnnotationsRepo() fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
_ = fakeAnnoRepo.Save(&dashboardAnnotation) _ = fakeAnnoRepo.Save(context.Background(), &dashboardAnnotation)
_ = fakeAnnoRepo.Save(&organizationAnnotation) _ = fakeAnnoRepo.Save(context.Background(), &organizationAnnotation)
annotations.SetRepository(fakeAnnoRepo) prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo)
prefix, resolver := AnnotationTypeScopeResolver()
require.Equal(t, "annotations:id:", prefix) require.Equal(t, "annotations:id:", prefix)
for _, tc := range testCases { for _, tc := range testCases {
@ -986,11 +909,8 @@ func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) {
dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1}
organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0}
fakeAnnoRepo = NewFakeAnnotationsRepo() _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation)
_ = fakeAnnoRepo.Save(dashboardAnnotation) _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation)
_ = fakeAnnoRepo.Save(organizationAnnotation)
annotations.SetRepository(fakeAnnoRepo)
r := callAPI(sc.server, tt.args.method, tt.args.url, tt.args.body, t) r := callAPI(sc.server, tt.args.method, tt.args.url, tt.args.body, t)
assert.Equalf(t, tt.want, r.Code, "Annotations API(%v)", tt.args.url) assert.Equalf(t, tt.want, r.Code, "Annotations API(%v)", tt.args.url)

View File

@ -26,6 +26,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy" "github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
@ -337,10 +338,11 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabled
return &HTTPServer{ return &HTTPServer{
Cfg: cfg, Cfg: cfg,
Features: features, Features: features,
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
AccessControl: accesscontrolmock.New().WithDisabled(), AccessControl: accesscontrolmock.New().WithDisabled(),
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
} }
} }
@ -408,6 +410,7 @@ func setupHTTPServerWithCfgDb(
), ),
preferenceService: preftest.NewPreferenceServiceFake(), preferenceService: preftest.NewPreferenceServiceFake(),
userService: userMock, userService: userMock,
annotationsRepo: annotationstest.NewFakeAnnotationsRepo(),
} }
for _, o := range options { for _, o := range options {

View File

@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/dashboards/service"
@ -109,7 +110,7 @@ func newTestLive(t *testing.T, store *sqlstore.SQLStore) *live.GrafanaLive {
nil, nil,
&usagestats.UsageStatsMock{T: t}, &usagestats.UsageStatsMock{T: t},
nil, nil,
features, accesscontrolmock.New(), &dashboards.FakeDashboardService{}) features, accesscontrolmock.New(), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo())
require.NoError(t, err) require.NoError(t, err)
return gLive return gLive
} }

View File

@ -37,6 +37,7 @@ import (
"github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/plugins/plugincontext"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cleanup"
"github.com/grafana/grafana/pkg/services/comments" "github.com/grafana/grafana/pkg/services/comments"
@ -189,6 +190,7 @@ type HTTPServer struct {
loginAttemptService loginAttempt.Service loginAttemptService loginAttempt.Service
orgService org.Service orgService org.Service
accesscontrolService accesscontrol.Service accesscontrolService accesscontrol.Service
annotationsRepo annotations.Repository
} }
type ServerOptions struct { type ServerOptions struct {
@ -227,7 +229,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service, secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service,
secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service, loginAttemptService loginAttempt.Service, orgService org.Service, publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service, loginAttemptService loginAttempt.Service, orgService org.Service,
accesscontrolService accesscontrol.Service, dashboardThumbsService dashboardThumbs.Service, accesscontrolService accesscontrol.Service, dashboardThumbsService dashboardThumbs.Service, annotationRepo annotations.Repository,
) (*HTTPServer, error) { ) (*HTTPServer, error) {
web.Env = cfg.Env web.Env = cfg.Env
m := web.New() m := web.New()
@ -323,6 +325,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
loginAttemptService: loginAttemptService, loginAttemptService: loginAttemptService,
orgService: orgService, orgService: orgService,
accesscontrolService: accesscontrolService, accesscontrolService: accesscontrolService,
annotationsRepo: annotationRepo,
} }
if hs.Listener != nil { if hs.Listener != nil {
hs.log.Debug("Using provided listener") hs.log.Debug("Using provided listener")
@ -330,7 +333,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
hs.registerRoutes() hs.registerRoutes()
// Register access control scope resolver for annotations // Register access control scope resolver for annotations
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver()) hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo))
if err := hs.declareFixedRoles(); err != nil { if err := hs.declareFixedRoles(); err != nil {
return nil, err return nil, err

View File

@ -6,6 +6,8 @@ package server
import ( import (
"github.com/google/wire" "github.com/google/wire"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/playlist/playlistimpl" "github.com/grafana/grafana/pkg/services/playlist/playlistimpl"
@ -152,6 +154,8 @@ import (
var wireBasicSet = wire.NewSet( var wireBasicSet = wire.NewSet(
legacydataservice.ProvideService, legacydataservice.ProvideService,
wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)), wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)),
annotationsimpl.ProvideService,
wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)),
alerting.ProvideAlertStore, alerting.ProvideAlertStore,
alerting.ProvideAlertEngine, alerting.ProvideAlertEngine,
wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)), wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)),

View File

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting/metrics" "github.com/grafana/grafana/pkg/services/alerting/metrics"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption"
@ -48,6 +49,7 @@ type AlertEngine struct {
dashAlertExtractor DashAlertExtractor dashAlertExtractor DashAlertExtractor
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
datasourceService datasources.DataSourceService datasourceService datasources.DataSourceService
annotationsRepo annotations.Repository
} }
// IsDisabled returns true if the alerting service is disabled for this instance. // IsDisabled returns true if the alerting service is disabled for this instance.
@ -59,7 +61,7 @@ func (e *AlertEngine) IsDisabled() bool {
func ProvideAlertEngine(renderer rendering.Service, requestValidator models.PluginRequestValidator, func ProvideAlertEngine(renderer rendering.Service, requestValidator models.PluginRequestValidator,
dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal, dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal,
notificationService *notifications.NotificationService, tracer tracing.Tracer, store AlertStore, cfg *setting.Cfg, notificationService *notifications.NotificationService, tracer tracing.Tracer, store AlertStore, cfg *setting.Cfg,
dashAlertExtractor DashAlertExtractor, dashboardService dashboards.DashboardService, cacheService *localcache.CacheService, dsService datasources.DataSourceService) *AlertEngine { dashAlertExtractor DashAlertExtractor, dashboardService dashboards.DashboardService, cacheService *localcache.CacheService, dsService datasources.DataSourceService, annotationsRepo annotations.Repository) *AlertEngine {
e := &AlertEngine{ e := &AlertEngine{
Cfg: cfg, Cfg: cfg,
RenderService: renderer, RenderService: renderer,
@ -71,6 +73,7 @@ func ProvideAlertEngine(renderer rendering.Service, requestValidator models.Plug
dashAlertExtractor: dashAlertExtractor, dashAlertExtractor: dashAlertExtractor,
dashboardService: dashboardService, dashboardService: dashboardService,
datasourceService: dsService, datasourceService: dsService,
annotationsRepo: annotationsRepo,
} }
e.execQueue = make(chan *Job, 1000) e.execQueue = make(chan *Job, 1000)
e.scheduler = newScheduler() e.scheduler = newScheduler()
@ -193,7 +196,7 @@ func (e *AlertEngine) processJob(attemptID int, attemptChan chan int, cancelChan
alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout) alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout)
cancelChan <- cancelFn cancelChan <- cancelFn
alertCtx, span := e.tracer.Start(alertCtx, "alert execution") alertCtx, span := e.tracer.Start(alertCtx, "alert execution")
evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator, e.AlertStore, e.dashboardService, e.datasourceService) evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator, e.AlertStore, e.dashboardService, e.datasourceService, e.annotationsRepo)
evalContext.Ctx = alertCtx evalContext.Ctx = alertCtx
go func() { go func() {

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
datasources "github.com/grafana/grafana/pkg/services/datasources/fakes" datasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider" encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
@ -35,7 +36,8 @@ func TestIntegrationEngineTimeouts(t *testing.T) {
tracer := tracing.InitializeTracerForTest() tracer := tracing.InitializeTracerForTest()
dsMock := &datasources.FakeDataSourceService{} dsMock := &datasources.FakeDataSourceService{}
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, nil, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock) annotationsRepo := annotationstest.NewFakeAnnotationsRepo()
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, nil, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock, annotationsRepo)
setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3
engine.resultHandler = &FakeResultHandler{} engine.resultHandler = &FakeResultHandler{}

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
fd "github.com/grafana/grafana/pkg/services/datasources/fakes" fd "github.com/grafana/grafana/pkg/services/datasources/fakes"
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider" encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
@ -128,7 +129,7 @@ func TestEngineProcessJob(t *testing.T) {
dsMock := &fd.FakeDataSourceService{ dsMock := &fd.FakeDataSourceService{
DataSources: []*datasources.DataSource{{Id: 1, Type: datasources.DS_PROMETHEUS}}, DataSources: []*datasources.DataSource{{Id: 1, Type: datasources.DS_PROMETHEUS}},
} }
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, store, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock) engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, store, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock, annotationstest.NewFakeAnnotationsRepo())
setting.AlertingEvaluationTimeout = 30 * time.Second setting.AlertingEvaluationTimeout = 30 * time.Second
setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -42,11 +43,12 @@ type EvalContext struct {
Store AlertStore Store AlertStore
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
DatasourceService datasources.DataSourceService DatasourceService datasources.DataSourceService
annotationRepo annotations.Repository
} }
// NewEvalContext is the EvalContext constructor. // NewEvalContext is the EvalContext constructor.
func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator, func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator,
alertStore AlertStore, dashboardService dashboards.DashboardService, dsService datasources.DataSourceService) *EvalContext { alertStore AlertStore, dashboardService dashboards.DashboardService, dsService datasources.DataSourceService, annotationRepo annotations.Repository) *EvalContext {
return &EvalContext{ return &EvalContext{
Ctx: alertCtx, Ctx: alertCtx,
StartTime: time.Now(), StartTime: time.Now(),
@ -60,6 +62,7 @@ func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator model
Store: alertStore, Store: alertStore,
dashboardService: dashboardService, dashboardService: dashboardService,
DatasourceService: dsService, DatasourceService: dsService,
annotationRepo: annotationRepo,
} }
} }

View File

@ -10,11 +10,12 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
) )
func TestStateIsUpdatedWhenNeeded(t *testing.T) { func TestStateIsUpdatedWhenNeeded(t *testing.T) {
ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
t.Run("ok -> alerting", func(t *testing.T) { t.Run("ok -> alerting", func(t *testing.T) {
ctx.PrevAlertState = models.AlertStateOK ctx.PrevAlertState = models.AlertStateOK
@ -199,7 +200,7 @@ func TestGetStateFromEvalContext(t *testing.T) {
} }
for _, tc := range tcs { for _, tc := range tcs {
evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
tc.applyFn(evalContext) tc.applyFn(evalContext)
newState := evalContext.GetNewState() newState := evalContext.GetNewState()
@ -391,7 +392,7 @@ func TestEvaluateNotificationTemplateFields(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(tt *testing.T) { t.Run(test.name, func(tt *testing.T) {
evalContext := NewEvalContext(context.Background(), &Rule{Name: "Rule name: ${value1}", Message: "Rule message: ${value2}", evalContext := NewEvalContext(context.Background(), &Rule{Name: "Rule name: ${value1}", Message: "Rule message: ${value2}",
Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.EvalMatches = test.evalMatches evalContext.EvalMatches = test.evalMatches
evalContext.AllMatches = test.allMatches evalContext.AllMatches = test.allMatches

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/tsdb/legacydata" "github.com/grafana/grafana/pkg/tsdb/legacydata"
@ -29,7 +30,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
Conditions: []Condition{&conditionStub{ Conditions: []Condition{&conditionStub{
firing: true, firing: true,
}}, }},
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, true, context.Firing) require.Equal(t, true, context.Firing)
@ -39,7 +40,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
t.Run("Show return triggered with single passing condition2", func(t *testing.T) { t.Run("Show return triggered with single passing condition2", func(t *testing.T) {
context := NewEvalContext(context.Background(), &Rule{ context := NewEvalContext(context.Background(), &Rule{
Conditions: []Condition{&conditionStub{firing: true, operator: "and"}}, Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, true, context.Firing) require.Equal(t, true, context.Firing)
@ -52,7 +53,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}}, &conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
&conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "and"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -65,7 +66,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: true, operator: "and"}, &conditionStub{firing: true, operator: "and"},
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, true, context.Firing) require.Equal(t, true, context.Firing)
@ -78,7 +79,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: true, operator: "and"}, &conditionStub{firing: true, operator: "and"},
&conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "and"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -92,7 +93,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: true, operator: "and"}, &conditionStub{firing: true, operator: "and"},
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, true, context.Firing) require.Equal(t, true, context.Firing)
@ -106,7 +107,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "and"},
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -120,7 +121,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "and"},
&conditionStub{firing: true, operator: "and"}, &conditionStub{firing: true, operator: "and"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -134,7 +135,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
&conditionStub{firing: true, operator: "or"}, &conditionStub{firing: true, operator: "or"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, true, context.Firing) require.Equal(t, true, context.Firing)
@ -148,7 +149,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
&conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -163,7 +164,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{operator: "or", noData: false}, &conditionStub{operator: "or", noData: false},
&conditionStub{operator: "or", noData: false}, &conditionStub{operator: "or", noData: false},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.False(t, context.NoDataFound) require.False(t, context.NoDataFound)
@ -174,7 +175,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
Conditions: []Condition{ Conditions: []Condition{
&conditionStub{operator: "and", noData: true}, &conditionStub{operator: "and", noData: true},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.Equal(t, false, context.Firing) require.Equal(t, false, context.Firing)
@ -187,7 +188,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{operator: "and", noData: true}, &conditionStub{operator: "and", noData: true},
&conditionStub{operator: "and", noData: false}, &conditionStub{operator: "and", noData: false},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.True(t, context.NoDataFound) require.True(t, context.NoDataFound)
@ -199,7 +200,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
&conditionStub{operator: "or", noData: true}, &conditionStub{operator: "or", noData: true},
&conditionStub{operator: "or", noData: false}, &conditionStub{operator: "or", noData: false},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
handler.Eval(context) handler.Eval(context)
require.True(t, context.NoDataFound) require.True(t, context.NoDataFound)

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/imguploader" "github.com/grafana/grafana/pkg/components/imguploader"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -20,18 +21,18 @@ import (
func TestNotificationService(t *testing.T) { func TestNotificationService(t *testing.T) {
testRule := &Rule{Name: "Test", Message: "Something is bad"} testRule := &Rule{Name: "Test", Message: "Something is bad"}
store := &AlertStoreMock{} store := &AlertStoreMock{}
evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}, store, nil, nil) evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}, store, nil, nil, annotationstest.NewFakeAnnotationsRepo())
testRuleTemplated := &Rule{Name: "Test latency ${quantile}", Message: "Something is bad on instance ${instance}"} testRuleTemplated := &Rule{Name: "Test latency ${quantile}", Message: "Something is bad on instance ${instance}"}
evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil) evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalCtxWithMatch.EvalMatches = []*EvalMatch{{ evalCtxWithMatch.EvalMatches = []*EvalMatch{{
Tags: map[string]string{ Tags: map[string]string{
"instance": "localhost:3000", "instance": "localhost:3000",
"quantile": "0.99", "quantile": "0.99",
}, },
}} }}
evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil) evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil, annotationstest.NewFakeAnnotationsRepo())
notificationServiceScenario(t, "Given alert rule with upload image enabled should render and upload image and send notification", notificationServiceScenario(t, "Given alert rule with upload image enabled should render and upload image and send notification",
evalCtx, true, func(sc *scenarioContext) { evalCtx, true, func(sc *scenarioContext) {

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -68,7 +69,7 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) {
am := &AlertmanagerNotifier{log: log.New("test.logger")} am := &AlertmanagerNotifier{log: log.New("test.logger")}
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
State: tc.prevState, State: tc.prevState,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.Rule.State = tc.newState evalContext.Rule.State = tc.newState

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -170,7 +171,7 @@ func TestShouldSendAlertNotification(t *testing.T) {
for _, tc := range tcs { for _, tc := range tcs {
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
State: tc.prevState, State: tc.prevState,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
if tc.state == nil { if tc.state == nil {
tc.state = &models.AlertNotificationState{} tc.state = &models.AlertNotificationState{}

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -52,7 +53,7 @@ func TestDingDingNotifier(t *testing.T) {
&alerting.Rule{ &alerting.Rule{
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
Message: `{host="localhost"}`, Message: `{host="localhost"}`,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
_, err = notifier.genBody(evalContext, "") _, err = notifier.genBody(evalContext, "")
require.Nil(t, err) require.Nil(t, err)
}) })

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -105,7 +106,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Message: "someMessage", Message: "someMessage",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
AlertRuleTags: tagPairs, AlertRuleTags: tagPairs,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
tags := make([]string, 0) tags := make([]string, 0)
@ -154,7 +155,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Message: "someMessage", Message: "someMessage",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
AlertRuleTags: tagPairs, AlertRuleTags: tagPairs,
}, nil, nil, nil, nil) }, nil, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
tags := make([]string, 0) tags := make([]string, 0)
@ -203,7 +204,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Message: "someMessage", Message: "someMessage",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
AlertRuleTags: tagPairs, AlertRuleTags: tagPairs,
}, nil, nil, nil, nil) }, nil, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
tags := make([]string, 0) tags := make([]string, 0)

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -142,7 +143,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Name: "someRule", Name: "someRule",
Message: "someMessage", Message: "someMessage",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
@ -198,7 +199,7 @@ func TestPagerdutyNotifier(t *testing.T) {
ID: 0, ID: 0,
Name: "someRule", Name: "someRule",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
@ -256,7 +257,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Name: "someRule", Name: "someRule",
Message: "someMessage", Message: "someMessage",
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
evalContext.EvalMatches = []*alerting.EvalMatch{ evalContext.EvalMatches = []*alerting.EvalMatch{
{ {
@ -335,7 +336,7 @@ func TestPagerdutyNotifier(t *testing.T) {
{Key: "severity", Value: "warning"}, {Key: "severity", Value: "warning"},
{Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)}, {Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
evalContext.IsTestRun = true evalContext.IsTestRun = true
@ -414,7 +415,7 @@ func TestPagerdutyNotifier(t *testing.T) {
{Key: "component", Value: "aComponent"}, {Key: "component", Value: "aComponent"},
{Key: "severity", Value: "info"}, {Key: "severity", Value: "info"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
evalContext.IsTestRun = true evalContext.IsTestRun = true
@ -493,7 +494,7 @@ func TestPagerdutyNotifier(t *testing.T) {
{Key: "component", Value: "aComponent"}, {Key: "component", Value: "aComponent"},
{Key: "severity", Value: "llama"}, {Key: "severity", Value: "llama"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
evalContext.IsTestRun = true evalContext.IsTestRun = true

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -76,7 +77,7 @@ func TestGenPushoverBody(t *testing.T) {
evalContext := alerting.NewEvalContext(context.Background(), evalContext := alerting.NewEvalContext(context.Background(),
&alerting.Rule{ &alerting.Rule{
State: models.AlertStateAlerting, State: models.AlertStateAlerting,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
require.Nil(t, err) require.Nil(t, err)
@ -87,7 +88,7 @@ func TestGenPushoverBody(t *testing.T) {
evalContext := alerting.NewEvalContext(context.Background(), evalContext := alerting.NewEvalContext(context.Background(),
&alerting.Rule{ &alerting.Rule{
State: models.AlertStateOK, State: models.AlertStateOK,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
require.Nil(t, err) require.Nil(t, err)

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -61,7 +62,7 @@ func TestTelegramNotifier(t *testing.T) {
Name: "This is an alarm", Name: "This is an alarm",
Message: "Some kind of message.", Message: "Some kind of message.",
State: models.AlertStateOK, State: models.AlertStateOK,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "") caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "")
require.LessOrEqual(t, len(caption), 1024) require.LessOrEqual(t, len(caption), 1024)
@ -77,7 +78,7 @@ func TestTelegramNotifier(t *testing.T) {
Name: "This is an alarm", Name: "This is an alarm",
Message: "Some kind of message.", Message: "Some kind of message.",
State: models.AlertStateOK, State: models.AlertStateOK,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
caption := generateImageCaption(evalContext, caption := generateImageCaption(evalContext,
"http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
@ -95,7 +96,7 @@ func TestTelegramNotifier(t *testing.T) {
Name: "This is an alarm", Name: "This is an alarm",
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.", Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.",
State: models.AlertStateOK, State: models.AlertStateOK,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
caption := generateImageCaption(evalContext, caption := generateImageCaption(evalContext,
"http://grafa.url/foo", "http://grafa.url/foo",
@ -112,7 +113,7 @@ func TestTelegramNotifier(t *testing.T) {
Name: "This is an alarm", Name: "This is an alarm",
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri", Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri",
State: models.AlertStateOK, State: models.AlertStateOK,
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
caption := generateImageCaption(evalContext, caption := generateImageCaption(evalContext,
"http://grafa.url/foo", "http://grafa.url/foo",

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
"github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/services/validations"
@ -93,7 +94,7 @@ func TestVictoropsNotifier(t *testing.T) {
{Key: "keyOnly"}, {Key: "keyOnly"},
{Key: "severity", Value: "warning"}, {Key: "severity", Value: "warning"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
payload, err := victoropsNotifier.buildEventPayload(evalContext) payload, err := victoropsNotifier.buildEventPayload(evalContext)
@ -141,7 +142,7 @@ func TestVictoropsNotifier(t *testing.T) {
{Key: "keyOnly"}, {Key: "keyOnly"},
{Key: "severity", Value: "warning"}, {Key: "severity", Value: "warning"},
}, },
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil) }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
evalContext.IsTestRun = true evalContext.IsTestRun = true
payload, err := victoropsNotifier.buildEventPayload(evalContext) payload, err := victoropsNotifier.buildEventPayload(evalContext)

View File

@ -95,8 +95,7 @@ func (handler *defaultResultHandler) handle(evalContext *EvalContext) error {
Data: annotationData, Data: annotationData,
} }
annotationRepo := annotations.GetRepository() if err := evalContext.annotationRepo.Save(evalContext.Ctx, &item); err != nil {
if err := annotationRepo.Save(&item); err != nil {
handler.log.Error("Failed to save annotation for new alert state", "error", err) handler.log.Error("Failed to save annotation for new alert state", "error", err)
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
) )
// NotificationTestCommand initiates an test // NotificationTestCommand initiates an test
@ -57,7 +58,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext {
ID: rand.Int63(), ID: rand.Int63(),
} }
ctx := NewEvalContext(context.Background(), testRule, fakeRequestValidator{}, nil, nil, nil) ctx := NewEvalContext(context.Background(), testRule, fakeRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo())
if cmd.Settings.Get("uploadImage").MustBool(true) { if cmd.Settings.Get("uploadImage").MustBool(true) {
ctx.ImagePublicURL = "https://grafana.com/assets/img/blog/mixed_styles.png" ctx.ImagePublicURL = "https://grafana.com/assets/img/blog/mixed_styles.png"
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
@ -33,7 +34,7 @@ func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID
handler := NewEvalHandler(e.DataService) handler := NewEvalHandler(e.DataService)
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, e.AlertStore, nil, e.datasourceService) context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, e.AlertStore, nil, e.datasourceService, annotationstest.NewFakeAnnotationsRepo())
context.IsTestRun = true context.IsTestRun = true
context.IsDebug = true context.IsDebug = true

View File

@ -4,8 +4,6 @@ import (
"context" "context"
"errors" "errors"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -14,7 +12,7 @@ var (
) )
type Repository interface { type Repository interface {
Save(item *Item) error Save(ctx context.Context, item *Item) error
Update(ctx context.Context, item *Item) error Update(ctx context.Context, item *Item) error
Find(ctx context.Context, query *ItemQuery) ([]*ItemDTO, error) Find(ctx context.Context, query *ItemQuery) ([]*ItemDTO, error)
Delete(ctx context.Context, params *DeleteParams) error Delete(ctx context.Context, params *DeleteParams) error
@ -26,63 +24,7 @@ type AnnotationCleaner interface {
CleanAnnotations(ctx context.Context, cfg *setting.Cfg) (int64, int64, error) CleanAnnotations(ctx context.Context, cfg *setting.Cfg) (int64, int64, error)
} }
type ItemQuery struct { // var repositoryInstance Repository
OrgId int64 `json:"orgId"`
From int64 `json:"from"`
To int64 `json:"to"`
UserId int64 `json:"userId"`
AlertId int64 `json:"alertId"`
DashboardId int64 `json:"dashboardId"`
DashboardUid string `json:"dashboardUID"`
PanelId int64 `json:"panelId"`
AnnotationId int64 `json:"annotationId"`
Tags []string `json:"tags"`
Type string `json:"type"`
MatchAny bool `json:"matchAny"`
SignedInUser *user.SignedInUser
Limit int64 `json:"limit"`
}
// TagsQuery is the query for a tags search.
type TagsQuery struct {
OrgID int64 `json:"orgId"`
Tag string `json:"tag"`
Limit int64 `json:"limit"`
}
// Tag is the DB result of a tags search.
type Tag struct {
Key string
Value string
Count int64
}
// TagsDTO is the frontend DTO for Tag.
type TagsDTO struct {
Tag string `json:"tag"`
Count int64 `json:"count"`
}
// FindTagsResult is the result of a tags search.
type FindTagsResult struct {
Tags []*TagsDTO `json:"tags"`
}
// GetAnnotationTagsResponse is a response struct for FindTagsResult.
type GetAnnotationTagsResponse struct {
Result FindTagsResult `json:"result"`
}
type DeleteParams struct {
OrgId int64
Id int64
DashboardId int64
PanelId int64
}
var repositoryInstance Repository
var cleanerInstance AnnotationCleaner var cleanerInstance AnnotationCleaner
func GetAnnotationCleaner() AnnotationCleaner { func GetAnnotationCleaner() AnnotationCleaner {
@ -92,84 +34,3 @@ func GetAnnotationCleaner() AnnotationCleaner {
func SetAnnotationCleaner(rep AnnotationCleaner) { func SetAnnotationCleaner(rep AnnotationCleaner) {
cleanerInstance = rep cleanerInstance = rep
} }
func GetRepository() Repository {
return repositoryInstance
}
func SetRepository(rep Repository) {
repositoryInstance = rep
}
type Item struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Text string `json:"text"`
AlertId int64 `json:"alertId"`
PrevState string `json:"prevState"`
NewState string `json:"newState"`
Epoch int64 `json:"epoch"`
EpochEnd int64 `json:"epochEnd"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Tags []string `json:"tags"`
Data *simplejson.Json `json:"data"`
// needed until we remove it from db
Type string
Title string
}
func (i Item) TableName() string {
return "annotation"
}
type ItemDTO struct {
Id int64 `json:"id"`
AlertId int64 `json:"alertId"`
AlertName string `json:"alertName"`
DashboardId int64 `json:"dashboardId"`
DashboardUID *string `json:"dashboardUID"`
PanelId int64 `json:"panelId"`
UserId int64 `json:"userId"`
NewState string `json:"newState"`
PrevState string `json:"prevState"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Time int64 `json:"time"`
TimeEnd int64 `json:"timeEnd"`
Text string `json:"text"`
Tags []string `json:"tags"`
Login string `json:"login"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
Data *simplejson.Json `json:"data"`
}
type annotationType int
const (
Organization annotationType = iota
Dashboard
)
func (a annotationType) String() string {
switch a {
case Organization:
return "organization"
case Dashboard:
return "dashboard"
default:
return ""
}
}
func (annotation *ItemDTO) GetType() annotationType {
if annotation.DashboardId != 0 {
return Dashboard
}
return Organization
}

View File

@ -0,0 +1,44 @@
package annotationsimpl
import (
"context"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting"
)
type RepositoryImpl struct {
store store
}
func ProvideService(db db.DB, cfg *setting.Cfg) *RepositoryImpl {
return &RepositoryImpl{
store: &SQLAnnotationRepo{
cfg: cfg,
db: db,
log: log.New("annotations"),
},
}
}
func (r *RepositoryImpl) Save(ctx context.Context, item *annotations.Item) error {
return r.store.Add(ctx, item)
}
func (r *RepositoryImpl) Update(ctx context.Context, item *annotations.Item) error {
return r.store.Update(ctx, item)
}
func (r *RepositoryImpl) Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
return r.store.Get(ctx, query)
}
func (r *RepositoryImpl) Delete(ctx context.Context, params *annotations.DeleteParams) error {
return r.store.Delete(ctx, params)
}
func (r *RepositoryImpl) FindTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
return r.store.GetTags(ctx, query)
}

View File

@ -0,0 +1,15 @@
package annotationsimpl
import (
"context"
"github.com/grafana/grafana/pkg/services/annotations"
)
type store interface {
Add(ctx context.Context, item *annotations.Item) error
Update(ctx context.Context, item *annotations.Item) error
Get(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error)
Delete(ctx context.Context, params *annotations.DeleteParams) error
GetTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error)
}

View File

@ -1,4 +1,4 @@
package sqlstore package annotationsimpl
import ( import (
"bytes" "bytes"
@ -8,14 +8,21 @@ import (
"strings" "strings"
"time" "time"
"github.com/davecgh/go-spew/spew"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
) )
var timeNow = time.Now
// Update the item so that EpochEnd >= Epoch // Update the item so that EpochEnd >= Epoch
func validateTimeRange(item *annotations.Item) error { func validateTimeRange(item *annotations.Item) error {
if item.EpochEnd == 0 { if item.EpochEnd == 0 {
@ -34,15 +41,13 @@ func validateTimeRange(item *annotations.Item) error {
} }
type SQLAnnotationRepo struct { type SQLAnnotationRepo struct {
sql *SQLStore cfg *setting.Cfg
db db.DB
log log.Logger
} }
func NewSQLAnnotationRepo(sql *SQLStore) SQLAnnotationRepo { func (r *SQLAnnotationRepo) Add(ctx context.Context, item *annotations.Item) error {
return SQLAnnotationRepo{sql: sql} return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
}
func (r *SQLAnnotationRepo) Save(item *annotations.Item) error {
return r.sql.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
tags := models.ParseTagPairs(item.Tags) tags := models.ParseTagPairs(item.Tags)
item.Tags = models.JoinTagPairs(tags) item.Tags = models.JoinTagPairs(tags)
item.Created = timeNow().UnixNano() / int64(time.Millisecond) item.Created = timeNow().UnixNano() / int64(time.Millisecond)
@ -59,7 +64,7 @@ func (r *SQLAnnotationRepo) Save(item *annotations.Item) error {
} }
if item.Tags != nil { if item.Tags != nil {
tags, err := EnsureTagsExist(sess, tags) tags, err := sqlstore.EnsureTagsExist(sess, tags)
if err != nil { if err != nil {
return err return err
} }
@ -75,7 +80,7 @@ func (r *SQLAnnotationRepo) Save(item *annotations.Item) error {
} }
func (r *SQLAnnotationRepo) Update(ctx context.Context, item *annotations.Item) error { func (r *SQLAnnotationRepo) Update(ctx context.Context, item *annotations.Item) error {
return r.sql.WithTransactionalDbSession(ctx, func(sess *DBSession) error { return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
var ( var (
isExist bool isExist bool
err error err error
@ -106,7 +111,7 @@ func (r *SQLAnnotationRepo) Update(ctx context.Context, item *annotations.Item)
} }
if item.Tags != nil { if item.Tags != nil {
tags, err := EnsureTagsExist(sess, models.ParseTagPairs(item.Tags)) tags, err := sqlstore.EnsureTagsExist(sess, models.ParseTagPairs(item.Tags))
if err != nil { if err != nil {
return err return err
} }
@ -127,11 +132,11 @@ func (r *SQLAnnotationRepo) Update(ctx context.Context, item *annotations.Item)
}) })
} }
func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) { func (r *SQLAnnotationRepo) Get(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
var sql bytes.Buffer var sql bytes.Buffer
params := make([]interface{}, 0) params := make([]interface{}, 0)
items := make([]*annotations.ItemDTO, 0) items := make([]*annotations.ItemDTO, 0)
err := r.sql.WithDbSession(ctx, func(sess *DBSession) error { err := r.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
sql.WriteString(` sql.WriteString(`
SELECT SELECT
annotation.id, annotation.id,
@ -151,7 +156,7 @@ func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQue
usr.login, usr.login,
alert.name as alert_name alert.name as alert_name
FROM annotation FROM annotation
LEFT OUTER JOIN ` + dialect.Quote("user") + ` as usr on usr.id = annotation.user_id LEFT OUTER JOIN ` + r.db.GetDialect().Quote("user") + ` as usr on usr.id = annotation.user_id
LEFT OUTER JOIN alert on alert.id = annotation.alert_id LEFT OUTER JOIN alert on alert.id = annotation.alert_id
INNER JOIN ( INNER JOIN (
SELECT a.id from annotation a SELECT a.id from annotation a
@ -203,10 +208,10 @@ func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQue
tags := models.ParseTagPairs(query.Tags) tags := models.ParseTagPairs(query.Tags)
for _, tag := range tags { for _, tag := range tags {
if tag.Value == "" { if tag.Value == "" {
keyValueFilters = append(keyValueFilters, "(tag."+dialect.Quote("key")+" = ?)") keyValueFilters = append(keyValueFilters, "(tag."+r.db.GetDialect().Quote("key")+" = ?)")
params = append(params, tag.Key) params = append(params, tag.Key)
} else { } else {
keyValueFilters = append(keyValueFilters, "(tag."+dialect.Quote("key")+" = ? AND tag."+dialect.Quote("value")+" = ?)") keyValueFilters = append(keyValueFilters, "(tag."+r.db.GetDialect().Quote("key")+" = ? AND tag."+r.db.GetDialect().Quote("value")+" = ?)")
params = append(params, tag.Key, tag.Value) params = append(params, tag.Key, tag.Value)
} }
} }
@ -229,7 +234,7 @@ func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQue
} }
} }
if !ac.IsDisabled(r.sql.Cfg) { if !ac.IsDisabled(r.cfg) {
acFilter, acArgs, err := getAccessControlFilter(query.SignedInUser) acFilter, acArgs, err := getAccessControlFilter(query.SignedInUser)
if err != nil { if err != nil {
return err return err
@ -243,8 +248,9 @@ func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQue
} }
// order of ORDER BY arguments match the order of a sql index for performance // order of ORDER BY arguments match the order of a sql index for performance
sql.WriteString(" ORDER BY a.org_id, a.epoch_end DESC, a.epoch DESC" + dialect.Limit(query.Limit) + " ) dt on dt.id = annotation.id") sql.WriteString(" ORDER BY a.org_id, a.epoch_end DESC, a.epoch DESC" + r.db.GetDialect().Limit(query.Limit) + " ) dt on dt.id = annotation.id")
spew.Dump(">>>> query: ", sql.String())
if err := sess.SQL(sql.String(), params...).Find(&items); err != nil { if err := sess.SQL(sql.String(), params...).Find(&items); err != nil {
items = nil items = nil
return err return err
@ -288,13 +294,13 @@ func getAccessControlFilter(user *user.SignedInUser) (string, []interface{}, err
} }
func (r *SQLAnnotationRepo) Delete(ctx context.Context, params *annotations.DeleteParams) error { func (r *SQLAnnotationRepo) Delete(ctx context.Context, params *annotations.DeleteParams) error {
return r.sql.WithTransactionalDbSession(ctx, func(sess *DBSession) error { return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
var ( var (
sql string sql string
annoTagSQL string annoTagSQL string
) )
sqlog.Info("delete", "orgId", params.OrgId) r.log.Info("delete", "orgId", params.OrgId)
if params.Id != 0 { if params.Id != 0 {
annoTagSQL = "DELETE FROM annotation_tag WHERE annotation_id IN (SELECT id FROM annotation WHERE id = ? AND org_id = ?)" annoTagSQL = "DELETE FROM annotation_tag WHERE annotation_id IN (SELECT id FROM annotation WHERE id = ? AND org_id = ?)"
sql = "DELETE FROM annotation WHERE id = ? AND org_id = ?" sql = "DELETE FROM annotation WHERE id = ? AND org_id = ?"
@ -323,17 +329,17 @@ func (r *SQLAnnotationRepo) Delete(ctx context.Context, params *annotations.Dele
}) })
} }
func (r *SQLAnnotationRepo) FindTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) { func (r *SQLAnnotationRepo) GetTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
var items []*annotations.Tag var items []*annotations.Tag
err := r.sql.WithDbSession(ctx, func(dbSession *DBSession) error { err := r.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
if query.Limit == 0 { if query.Limit == 0 {
query.Limit = 100 query.Limit = 100
} }
var sql bytes.Buffer var sql bytes.Buffer
params := make([]interface{}, 0) params := make([]interface{}, 0)
tagKey := `tag.` + dialect.Quote("key") tagKey := `tag.` + r.db.GetDialect().Quote("key")
tagValue := `tag.` + dialect.Quote("value") tagValue := `tag.` + r.db.GetDialect().Quote("value")
sql.WriteString(` sql.WriteString(`
SELECT SELECT
@ -347,12 +353,12 @@ func (r *SQLAnnotationRepo) FindTags(ctx context.Context, query *annotations.Tag
sql.WriteString(`WHERE EXISTS(SELECT 1 FROM annotation WHERE annotation.id = annotation_tag.annotation_id AND annotation.org_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) params = append(params, query.OrgID)
sql.WriteString(` AND (` + tagKey + ` ` + dialect.LikeStr() + ` ? OR ` + tagValue + ` ` + dialect.LikeStr() + ` ?)`) sql.WriteString(` AND (` + tagKey + ` ` + r.db.GetDialect().LikeStr() + ` ? OR ` + tagValue + ` ` + r.db.GetDialect().LikeStr() + ` ?)`)
params = append(params, `%`+query.Tag+`%`, `%`+query.Tag+`%`) params = append(params, `%`+query.Tag+`%`, `%`+query.Tag+`%`)
sql.WriteString(` GROUP BY ` + tagKey + `,` + tagValue) sql.WriteString(` GROUP BY ` + tagKey + `,` + tagValue)
sql.WriteString(` ORDER BY ` + tagKey + `,` + tagValue) sql.WriteString(` ORDER BY ` + tagKey + `,` + tagValue)
sql.WriteString(` ` + dialect.Limit(query.Limit)) sql.WriteString(` ` + r.db.GetDialect().Limit(query.Limit))
err := dbSession.SQL(sql.String(), params...).Find(&items) err := dbSession.SQL(sql.String(), params...).Find(&items)
return err return err

View File

@ -1,12 +1,14 @@
package sqlstore_test package annotationsimpl
import ( import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -25,7 +27,7 @@ func TestIntegrationAnnotations(t *testing.T) {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
sql := sqlstore.InitTestDB(t) sql := sqlstore.InitTestDB(t)
repo := sqlstore.NewSQLAnnotationRepo(sql) repo := SQLAnnotationRepo{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test")}
testUser := &user.SignedInUser{ testUser := &user.SignedInUser{
OrgID: 1, OrgID: 1,
@ -81,7 +83,7 @@ func TestIntegrationAnnotations(t *testing.T) {
Epoch: 10, Epoch: 10,
Tags: []string{"outage", "error", "type:outage", "server:server-1"}, Tags: []string{"outage", "error", "type:outage", "server:server-1"},
} }
err = repo.Save(annotation) err = repo.Add(context.Background(), annotation)
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, annotation.Id, int64(0)) assert.Greater(t, annotation.Id, int64(0))
assert.Equal(t, annotation.Epoch, annotation.EpochEnd) assert.Equal(t, annotation.Epoch, annotation.EpochEnd)
@ -96,7 +98,7 @@ func TestIntegrationAnnotations(t *testing.T) {
EpochEnd: 20, EpochEnd: 20,
Tags: []string{"outage", "error", "type:outage", "server:server-1"}, Tags: []string{"outage", "error", "type:outage", "server:server-1"},
} }
err = repo.Save(annotation2) err = repo.Add(context.Background(), annotation2)
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, annotation2.Id, int64(0)) assert.Greater(t, annotation2.Id, int64(0))
assert.Equal(t, int64(20), annotation2.Epoch) assert.Equal(t, int64(20), annotation2.Epoch)
@ -110,7 +112,7 @@ func TestIntegrationAnnotations(t *testing.T) {
Epoch: 15, Epoch: 15,
Tags: []string{"deploy"}, Tags: []string{"deploy"},
} }
err = repo.Save(organizationAnnotation1) err = repo.Add(context.Background(), organizationAnnotation1)
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, organizationAnnotation1.Id, int64(0)) assert.Greater(t, organizationAnnotation1.Id, int64(0))
@ -122,11 +124,11 @@ func TestIntegrationAnnotations(t *testing.T) {
Epoch: 17, Epoch: 17,
Tags: []string{"rollback"}, Tags: []string{"rollback"},
} }
err = repo.Save(globalAnnotation2) err = repo.Add(context.Background(), globalAnnotation2)
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, globalAnnotation2.Id, int64(0)) assert.Greater(t, globalAnnotation2.Id, int64(0))
t.Run("Can query for annotation by dashboard id", func(t *testing.T) { t.Run("Can query for annotation by dashboard id", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: dashboard.Id, DashboardId: dashboard.Id,
From: 0, From: 0,
@ -145,7 +147,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Can query for annotation by id", func(t *testing.T) { t.Run("Can query for annotation by id", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
AnnotationId: annotation2.Id, AnnotationId: annotation2.Id,
SignedInUser: testUser, SignedInUser: testUser,
@ -156,7 +158,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should not find any when item is outside time range", func(t *testing.T) { t.Run("Should not find any when item is outside time range", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: 1, DashboardId: 1,
From: 12, From: 12,
@ -168,7 +170,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should not find one when tag filter does not match", func(t *testing.T) { t.Run("Should not find one when tag filter does not match", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: 1, DashboardId: 1,
From: 1, From: 1,
@ -181,7 +183,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should not find one when type filter does not match", func(t *testing.T) { t.Run("Should not find one when type filter does not match", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: 1, DashboardId: 1,
From: 1, From: 1,
@ -194,7 +196,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should find one when all tag filters does match", func(t *testing.T) { t.Run("Should find one when all tag filters does match", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: 1, DashboardId: 1,
From: 1, From: 1,
@ -207,7 +209,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should find two annotations using partial match", func(t *testing.T) { t.Run("Should find two annotations using partial match", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
From: 1, From: 1,
To: 25, To: 25,
@ -220,7 +222,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should find one when all key value tag filters does match", func(t *testing.T) { t.Run("Should find one when all key value tag filters does match", func(t *testing.T) {
items, err := repo.Find(context.Background(), &annotations.ItemQuery{ items, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
DashboardId: 1, DashboardId: 1,
From: 1, From: 1,
@ -240,7 +242,7 @@ func TestIntegrationAnnotations(t *testing.T) {
To: 15, To: 15,
SignedInUser: testUser, SignedInUser: testUser,
} }
items, err := repo.Find(context.Background(), query) items, err := repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
annotationId := items[0].Id annotationId := items[0].Id
@ -252,7 +254,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
items, err = repo.Find(context.Background(), query) items, err = repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, annotationId, items[0].Id) assert.Equal(t, annotationId, items[0].Id)
@ -268,7 +270,7 @@ func TestIntegrationAnnotations(t *testing.T) {
To: 15, To: 15,
SignedInUser: testUser, SignedInUser: testUser,
} }
items, err := repo.Find(context.Background(), query) items, err := repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
annotationId := items[0].Id annotationId := items[0].Id
@ -280,7 +282,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
items, err = repo.Find(context.Background(), query) items, err = repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, annotationId, items[0].Id) assert.Equal(t, annotationId, items[0].Id)
@ -297,14 +299,14 @@ func TestIntegrationAnnotations(t *testing.T) {
To: 15, To: 15,
SignedInUser: testUser, SignedInUser: testUser,
} }
items, err := repo.Find(context.Background(), query) items, err := repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
annotationId := items[0].Id annotationId := items[0].Id
err = repo.Delete(context.Background(), &annotations.DeleteParams{Id: annotationId, OrgId: 1}) err = repo.Delete(context.Background(), &annotations.DeleteParams{Id: annotationId, OrgId: 1})
require.NoError(t, err) require.NoError(t, err)
items, err = repo.Find(context.Background(), query) items, err = repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, items) assert.Empty(t, items)
}) })
@ -320,7 +322,7 @@ func TestIntegrationAnnotations(t *testing.T) {
Tags: []string{"test"}, Tags: []string{"test"},
PanelId: 20, PanelId: 20,
} }
err = repo.Save(annotation3) err = repo.Add(context.Background(), annotation3)
require.NoError(t, err) require.NoError(t, err)
query := &annotations.ItemQuery{ query := &annotations.ItemQuery{
@ -328,7 +330,7 @@ func TestIntegrationAnnotations(t *testing.T) {
AnnotationId: annotation3.Id, AnnotationId: annotation3.Id,
SignedInUser: testUser, SignedInUser: testUser,
} }
items, err := repo.Find(context.Background(), query) items, err := repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
dashboardId := items[0].DashboardId dashboardId := items[0].DashboardId
@ -336,13 +338,13 @@ func TestIntegrationAnnotations(t *testing.T) {
err = repo.Delete(context.Background(), &annotations.DeleteParams{DashboardId: dashboardId, PanelId: panelId, OrgId: 1}) err = repo.Delete(context.Background(), &annotations.DeleteParams{DashboardId: dashboardId, PanelId: panelId, OrgId: 1})
require.NoError(t, err) require.NoError(t, err)
items, err = repo.Find(context.Background(), query) items, err = repo.Get(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, items) assert.Empty(t, items)
}) })
t.Run("Should find tags by key", func(t *testing.T) { t.Run("Should find tags by key", func(t *testing.T) {
result, err := repo.FindTags(context.Background(), &annotations.TagsQuery{ result, err := repo.GetTags(context.Background(), &annotations.TagsQuery{
OrgID: 1, OrgID: 1,
Tag: "server", Tag: "server",
}) })
@ -353,7 +355,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should find tags by value", func(t *testing.T) { t.Run("Should find tags by value", func(t *testing.T) {
result, err := repo.FindTags(context.Background(), &annotations.TagsQuery{ result, err := repo.GetTags(context.Background(), &annotations.TagsQuery{
OrgID: 1, OrgID: 1,
Tag: "outage", Tag: "outage",
}) })
@ -366,7 +368,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should not find tags in other org", func(t *testing.T) { t.Run("Should not find tags in other org", func(t *testing.T) {
result, err := repo.FindTags(context.Background(), &annotations.TagsQuery{ result, err := repo.GetTags(context.Background(), &annotations.TagsQuery{
OrgID: 0, OrgID: 0,
Tag: "server-1", Tag: "server-1",
}) })
@ -375,7 +377,7 @@ func TestIntegrationAnnotations(t *testing.T) {
}) })
t.Run("Should not find tags that do not exist", func(t *testing.T) { t.Run("Should not find tags that do not exist", func(t *testing.T) {
result, err := repo.FindTags(context.Background(), &annotations.TagsQuery{ result, err := repo.GetTags(context.Background(), &annotations.TagsQuery{
OrgID: 0, OrgID: 0,
Tag: "unknown:tag", Tag: "unknown:tag",
}) })
@ -390,7 +392,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
sql := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{}) sql := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{})
repo := sqlstore.NewSQLAnnotationRepo(sql) repo := SQLAnnotationRepo{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test")}
dashboardStore := dashboardstore.ProvideDashboardStore(sql, featuremgmt.WithFeatures()) dashboardStore := dashboardstore.ProvideDashboardStore(sql, featuremgmt.WithFeatures())
testDashboard1 := models.SaveDashboardCommand{ testDashboard1 := models.SaveDashboardCommand{
@ -419,7 +421,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
DashboardId: 1, DashboardId: 1,
Epoch: 10, Epoch: 10,
} }
err = repo.Save(dash1Annotation) err = repo.Add(context.Background(), dash1Annotation)
require.NoError(t, err) require.NoError(t, err)
dash2Annotation := &annotations.Item{ dash2Annotation := &annotations.Item{
@ -427,14 +429,14 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
DashboardId: 2, DashboardId: 2,
Epoch: 10, Epoch: 10,
} }
err = repo.Save(dash2Annotation) err = repo.Add(context.Background(), dash2Annotation)
require.NoError(t, err) require.NoError(t, err)
organizationAnnotation := &annotations.Item{ organizationAnnotation := &annotations.Item{
OrgId: 1, OrgId: 1,
Epoch: 10, Epoch: 10,
} }
err = repo.Save(organizationAnnotation) err = repo.Add(context.Background(), organizationAnnotation)
require.NoError(t, err) require.NoError(t, err)
user := &user.SignedInUser{ user := &user.SignedInUser{
@ -501,7 +503,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
user.Permissions = map[int64]map[string][]string{1: tc.permissions} user.Permissions = map[int64]map[string][]string{1: tc.permissions}
results, err := repo.Find(context.Background(), &annotations.ItemQuery{ results, err := repo.Get(context.Background(), &annotations.ItemQuery{
OrgId: 1, OrgId: 1,
SignedInUser: user, SignedInUser: user,
}) })

View File

@ -0,0 +1,83 @@
package annotationstest
import (
"context"
"sync"
"github.com/grafana/grafana/pkg/services/annotations"
)
type fakeAnnotationsRepo struct {
mtx sync.Mutex
annotations map[int64]annotations.Item
}
func NewFakeAnnotationsRepo() *fakeAnnotationsRepo {
return &fakeAnnotationsRepo{
annotations: map[int64]annotations.Item{},
}
}
func (repo *fakeAnnotationsRepo) Delete(_ context.Context, params *annotations.DeleteParams) error {
repo.mtx.Lock()
defer repo.mtx.Unlock()
if params.Id != 0 {
delete(repo.annotations, params.Id)
} else {
for _, v := range repo.annotations {
if params.DashboardId == v.DashboardId && params.PanelId == v.PanelId {
delete(repo.annotations, v.Id)
}
}
}
return nil
}
func (repo *fakeAnnotationsRepo) Save(ctx context.Context, item *annotations.Item) error {
repo.mtx.Lock()
defer repo.mtx.Unlock()
if item.Id == 0 {
item.Id = int64(len(repo.annotations) + 1)
}
repo.annotations[item.Id] = *item
return nil
}
func (repo *fakeAnnotationsRepo) Update(_ context.Context, item *annotations.Item) error {
return nil
}
func (repo *fakeAnnotationsRepo) Find(_ context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
repo.mtx.Lock()
defer repo.mtx.Unlock()
if annotation, has := repo.annotations[query.AnnotationId]; has {
return []*annotations.ItemDTO{{Id: annotation.Id, DashboardId: annotation.DashboardId}}, nil
}
annotations := []*annotations.ItemDTO{{Id: 1, DashboardId: 0}}
return annotations, nil
}
func (repo *fakeAnnotationsRepo) FindTags(_ context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
result := annotations.FindTagsResult{
Tags: []*annotations.TagsDTO{},
}
return result, nil
}
func (repo *fakeAnnotationsRepo) Len() int {
repo.mtx.Lock()
defer repo.mtx.Unlock()
return len(repo.annotations)
}
func (repo *fakeAnnotationsRepo) Items() map[int64]annotations.Item {
repo.mtx.Lock()
defer repo.mtx.Unlock()
return repo.annotations
}

View File

@ -0,0 +1,135 @@
package annotations
import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/user"
)
type ItemQuery struct {
OrgId int64 `json:"orgId"`
From int64 `json:"from"`
To int64 `json:"to"`
UserId int64 `json:"userId"`
AlertId int64 `json:"alertId"`
DashboardId int64 `json:"dashboardId"`
DashboardUid string `json:"dashboardUID"`
PanelId int64 `json:"panelId"`
AnnotationId int64 `json:"annotationId"`
Tags []string `json:"tags"`
Type string `json:"type"`
MatchAny bool `json:"matchAny"`
SignedInUser *user.SignedInUser
Limit int64 `json:"limit"`
}
// TagsQuery is the query for a tags search.
type TagsQuery struct {
OrgID int64 `json:"orgId"`
Tag string `json:"tag"`
Limit int64 `json:"limit"`
}
// Tag is the DB result of a tags search.
type Tag struct {
Key string
Value string
Count int64
}
// TagsDTO is the frontend DTO for Tag.
type TagsDTO struct {
Tag string `json:"tag"`
Count int64 `json:"count"`
}
// FindTagsResult is the result of a tags search.
type FindTagsResult struct {
Tags []*TagsDTO `json:"tags"`
}
// GetAnnotationTagsResponse is a response struct for FindTagsResult.
type GetAnnotationTagsResponse struct {
Result FindTagsResult `json:"result"`
}
type DeleteParams struct {
OrgId int64
Id int64
DashboardId int64
PanelId int64
}
type Item struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Text string `json:"text"`
AlertId int64 `json:"alertId"`
PrevState string `json:"prevState"`
NewState string `json:"newState"`
Epoch int64 `json:"epoch"`
EpochEnd int64 `json:"epochEnd"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Tags []string `json:"tags"`
Data *simplejson.Json `json:"data"`
// needed until we remove it from db
Type string
Title string
}
func (i Item) TableName() string {
return "annotation"
}
type ItemDTO struct {
Id int64 `json:"id"`
AlertId int64 `json:"alertId"`
AlertName string `json:"alertName"`
DashboardId int64 `json:"dashboardId"`
DashboardUID *string `json:"dashboardUID"`
PanelId int64 `json:"panelId"`
UserId int64 `json:"userId"`
NewState string `json:"newState"`
PrevState string `json:"prevState"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Time int64 `json:"time"`
TimeEnd int64 `json:"timeEnd"`
Text string `json:"text"`
Tags []string `json:"tags"`
Login string `json:"login"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
Data *simplejson.Json `json:"data"`
}
type annotationType int
const (
Organization annotationType = iota
Dashboard
)
func (a annotationType) String() string {
switch a {
case Organization:
return "organization"
case Dashboard:
return "dashboard"
default:
return ""
}
}
func (annotation *ItemDTO) GetType() annotationType {
if annotation.DashboardId != 0 {
return Dashboard
}
return Organization
}

View File

@ -19,12 +19,14 @@ type PermissionChecker struct {
features featuremgmt.FeatureToggles features featuremgmt.FeatureToggles
accessControl accesscontrol.AccessControl accessControl accesscontrol.AccessControl
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
annotationsRepo annotations.Repository
} }
func NewPermissionChecker(sqlStore *sqlstore.SQLStore, features featuremgmt.FeatureToggles, func NewPermissionChecker(sqlStore *sqlstore.SQLStore, features featuremgmt.FeatureToggles,
accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService, accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
annotationsRepo annotations.Repository,
) *PermissionChecker { ) *PermissionChecker {
return &PermissionChecker{sqlStore: sqlStore, features: features, accessControl: accessControl} return &PermissionChecker{sqlStore: sqlStore, features: features, accessControl: accessControl, annotationsRepo: annotationsRepo}
} }
func (c *PermissionChecker) getDashboardByUid(ctx context.Context, orgID int64, uid string) (*models.Dashboard, error) { func (c *PermissionChecker) getDashboardByUid(ctx context.Context, orgID int64, uid string) (*models.Dashboard, error) {
@ -63,12 +65,11 @@ func (c *PermissionChecker) CheckReadPermissions(ctx context.Context, orgId int6
if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) { if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) {
return false, nil return false, nil
} }
repo := annotations.GetRepository()
annotationID, err := strconv.ParseInt(objectID, 10, 64) annotationID, err := strconv.ParseInt(objectID, 10, 64)
if err != nil { if err != nil {
return false, nil return false, nil
} }
items, err := repo.Find(ctx, &annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId, SignedInUser: signedInUser}) items, err := c.annotationsRepo.Find(ctx, &annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId, SignedInUser: signedInUser})
if err != nil || len(items) != 1 { if err != nil || len(items) != 1 {
return false, nil return false, nil
} }
@ -116,12 +117,11 @@ func (c *PermissionChecker) CheckWritePermissions(ctx context.Context, orgId int
return canEdit, err return canEdit, err
} }
} }
repo := annotations.GetRepository()
annotationID, err := strconv.ParseInt(objectID, 10, 64) annotationID, err := strconv.ParseInt(objectID, 10, 64)
if err != nil { if err != nil {
return false, nil return false, nil
} }
items, err := repo.Find(ctx, &annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId, SignedInUser: signedInUser}) items, err := c.annotationsRepo.Find(ctx, &annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId, SignedInUser: signedInUser})
if err != nil || len(items) != 1 { if err != nil || len(items) != 1 {
return false, nil return false, nil
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/comments/commentmodel" "github.com/grafana/grafana/pkg/services/comments/commentmodel"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -24,7 +25,7 @@ type Service struct {
func ProvideService(cfg *setting.Cfg, store *sqlstore.SQLStore, live *live.GrafanaLive, func ProvideService(cfg *setting.Cfg, store *sqlstore.SQLStore, live *live.GrafanaLive,
features featuremgmt.FeatureToggles, accessControl accesscontrol.AccessControl, features featuremgmt.FeatureToggles, accessControl accesscontrol.AccessControl,
dashboardService dashboards.DashboardService, userService user.Service) *Service { dashboardService dashboards.DashboardService, userService user.Service, annotationsRepo annotations.Repository) *Service {
s := &Service{ s := &Service{
cfg: cfg, cfg: cfg,
live: live, live: live,
@ -32,7 +33,7 @@ func ProvideService(cfg *setting.Cfg, store *sqlstore.SQLStore, live *live.Grafa
storage: &sqlStorage{ storage: &sqlStorage{
sql: store, sql: store,
}, },
permissions: commentmodel.NewPermissionChecker(store, features, accessControl, dashboardService), permissions: commentmodel.NewPermissionChecker(store, features, accessControl, dashboardService, annotationsRepo),
userService: userService, userService: userService,
} }
return s return s

View File

@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/plugins/plugincontext"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/comments/commentmodel" "github.com/grafana/grafana/pkg/services/comments/commentmodel"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
@ -75,7 +76,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
pluginStore plugins.Store, cacheService *localcache.CacheService, pluginStore plugins.Store, cacheService *localcache.CacheService,
dataSourceCache datasources.CacheService, sqlStore *sqlstore.SQLStore, secretsService secrets.Service, dataSourceCache datasources.CacheService, sqlStore *sqlstore.SQLStore, secretsService secrets.Service,
usageStatsService usagestats.Service, queryDataService *query.Service, toggles featuremgmt.FeatureToggles, usageStatsService usagestats.Service, queryDataService *query.Service, toggles featuremgmt.FeatureToggles,
accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService) (*GrafanaLive, error) { accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService, annotationsRepo annotations.Repository) (*GrafanaLive, error) {
g := &GrafanaLive{ g := &GrafanaLive{
Cfg: cfg, Cfg: cfg,
Features: toggles, Features: toggles,
@ -247,7 +248,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
g.GrafanaScope.Dashboards = dash g.GrafanaScope.Dashboards = dash
g.GrafanaScope.Features["dashboard"] = dash g.GrafanaScope.Features["dashboard"] = dash
g.GrafanaScope.Features["broadcast"] = features.NewBroadcastRunner(g.storage) g.GrafanaScope.Features["broadcast"] = features.NewBroadcastRunner(g.storage)
g.GrafanaScope.Features["comment"] = features.NewCommentHandler(commentmodel.NewPermissionChecker(g.SQLStore, g.Features, accessControl, dashboardService)) g.GrafanaScope.Features["comment"] = features.NewCommentHandler(commentmodel.NewPermissionChecker(g.SQLStore, g.Features, accessControl, dashboardService, annotationsRepo))
g.surveyCaller = survey.NewCaller(managedStreamRunner, node) g.surveyCaller = survey.NewCaller(managedStreamRunner, node)
err = g.surveyCaller.SetupHandlers() err = g.surveyCaller.SetupHandlers()

View File

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
@ -41,7 +42,7 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService,
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, expressionService *expr.Service, dataProxy *datasourceproxy.DataSourceProxyService, sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, expressionService *expr.Service, dataProxy *datasourceproxy.DataSourceProxyService,
quotaService quota.Service, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert, quotaService quota.Service, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert,
folderService dashboards.FolderService, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, renderService rendering.Service, folderService dashboards.FolderService, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, renderService rendering.Service,
bus bus.Bus, accesscontrolService accesscontrol.Service) (*AlertNG, error) { bus bus.Bus, accesscontrolService accesscontrol.Service, annotationsRepo annotations.Repository) (*AlertNG, error) {
ng := &AlertNG{ ng := &AlertNG{
Cfg: cfg, Cfg: cfg,
DataSourceCache: dataSourceCache, DataSourceCache: dataSourceCache,
@ -62,6 +63,7 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService,
renderService: renderService, renderService: renderService,
bus: bus, bus: bus,
accesscontrolService: accesscontrolService, accesscontrolService: accesscontrolService,
annotationsRepo: annotationsRepo,
} }
if ng.IsDisabled() { if ng.IsDisabled() {
@ -102,6 +104,7 @@ type AlertNG struct {
AlertsRouter *sender.AlertsRouter AlertsRouter *sender.AlertsRouter
accesscontrol accesscontrol.AccessControl accesscontrol accesscontrol.AccessControl
accesscontrolService accesscontrol.Service accesscontrolService accesscontrol.Service
annotationsRepo annotations.Repository
bus bus.Bus bus bus.Bus
} }
@ -165,7 +168,7 @@ func (ng *AlertNG) init() error {
AlertSender: alertsRouter, AlertSender: alertsRouter,
} }
stateManager := state.NewManager(ng.Log, ng.Metrics.GetStateMetrics(), appUrl, store, store, ng.dashboardService, ng.imageService, clk) stateManager := state.NewManager(ng.Log, ng.Metrics.GetStateMetrics(), appUrl, store, store, ng.dashboardService, ng.imageService, clk, ng.annotationsRepo)
scheduler := schedule.NewScheduler(schedCfg, appUrl, stateManager) scheduler := schedule.NewScheduler(schedCfg, appUrl, stateManager)
// if it is required to include folder title to the alerts, we need to subscribe to changes of alert title // if it is required to include folder title to the alerts, we need to subscribe to changes of alert title

View File

@ -19,6 +19,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/image" "github.com/grafana/grafana/pkg/services/ngalert/image"
@ -112,7 +113,7 @@ func TestWarmStateCache(t *testing.T) {
InstanceStore: dbstore, InstanceStore: dbstore,
Metrics: testMetrics.GetSchedulerMetrics(), Metrics: testMetrics.GetSchedulerMetrics(),
} }
st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.NewMock()) st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.NewMock(), annotationstest.NewFakeAnnotationsRepo())
st.Warm(ctx) st.Warm(ctx)
t.Run("instance cache has expected entries", func(t *testing.T) { t.Run("instance cache has expected entries", func(t *testing.T) {
@ -166,7 +167,7 @@ func TestAlertingTicker(t *testing.T) {
Metrics: testMetrics.GetSchedulerMetrics(), Metrics: testMetrics.GetSchedulerMetrics(),
AlertSender: notifier, AlertSender: notifier,
} }
st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.NewMock()) st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.NewMock(), annotationstest.NewFakeAnnotationsRepo())
appUrl := &url.URL{ appUrl := &url.URL{
Scheme: "http", Scheme: "http",
Host: "localhost", Host: "localhost",

View File

@ -21,7 +21,7 @@ import (
"github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
@ -484,8 +484,6 @@ func TestSchedule_DeleteAlertRule(t *testing.T) {
func setupScheduler(t *testing.T, rs *store.FakeRuleStore, is *store.FakeInstanceStore, registry *prometheus.Registry, senderMock *AlertsSenderMock, evalMock *eval.FakeEvaluator) *schedule { func setupScheduler(t *testing.T, rs *store.FakeRuleStore, is *store.FakeInstanceStore, registry *prometheus.Registry, senderMock *AlertsSenderMock, evalMock *eval.FakeEvaluator) *schedule {
t.Helper() t.Helper()
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
mockedClock := clock.NewMock() mockedClock := clock.NewMock()
logger := log.New("ngalert schedule test") logger := log.New("ngalert schedule test")
@ -531,7 +529,7 @@ func setupScheduler(t *testing.T, rs *store.FakeRuleStore, is *store.FakeInstanc
Metrics: m.GetSchedulerMetrics(), Metrics: m.GetSchedulerMetrics(),
AlertSender: senderMock, AlertSender: senderMock,
} }
st := state.NewManager(schedCfg.Logger, m.GetStateMetrics(), nil, rs, is, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, mockedClock) st := state.NewManager(schedCfg.Logger, m.GetStateMetrics(), nil, rs, is, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, mockedClock, annotationstest.NewFakeAnnotationsRepo())
return NewScheduler(schedCfg, appUrl, st) return NewScheduler(schedCfg, appUrl, st)
} }

View File

@ -45,11 +45,12 @@ type Manager struct {
instanceStore store.InstanceStore instanceStore store.InstanceStore
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
imageService image.ImageService imageService image.ImageService
AnnotationsRepo annotations.Repository
} }
func NewManager(logger log.Logger, metrics *metrics.State, externalURL *url.URL, func NewManager(logger log.Logger, metrics *metrics.State, externalURL *url.URL,
ruleStore store.RuleStore, instanceStore store.InstanceStore, ruleStore store.RuleStore, instanceStore store.InstanceStore,
dashboardService dashboards.DashboardService, imageService image.ImageService, clock clock.Clock) *Manager { dashboardService dashboards.DashboardService, imageService image.ImageService, clock clock.Clock, annotationsRepo annotations.Repository) *Manager {
manager := &Manager{ manager := &Manager{
cache: newCache(logger, metrics, externalURL), cache: newCache(logger, metrics, externalURL),
quit: make(chan struct{}), quit: make(chan struct{}),
@ -61,6 +62,7 @@ func NewManager(logger log.Logger, metrics *metrics.State, externalURL *url.URL,
dashboardService: dashboardService, dashboardService: dashboardService,
imageService: imageService, imageService: imageService,
clock: clock, clock: clock,
AnnotationsRepo: annotationsRepo,
} }
go manager.recordMetrics() go manager.recordMetrics()
return manager return manager
@ -419,8 +421,7 @@ func (st *Manager) annotateState(ctx context.Context, alertRule *ngModels.AlertR
item.DashboardId = query.Result.Id item.DashboardId = query.Result.Id
} }
annotationRepo := annotations.GetRepository() if err := st.AnnotationsRepo.Save(ctx, item); err != nil {
if err := annotationRepo.Save(item); err != nil {
st.log.Error("error saving alert annotation", "alertRuleUID", alertRule.UID, "err", err.Error()) st.log.Error("error saving alert annotation", "alertRuleUID", alertRule.UID, "err", err.Error())
return return
} }

View File

@ -13,7 +13,7 @@ import (
"github.com/benbjohnson/clock" "github.com/benbjohnson/clock"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/image" "github.com/grafana/grafana/pkg/services/ngalert/image"
@ -98,7 +98,7 @@ func Test_maybeNewImage(t *testing.T) {
imageService := &CountingImageService{} imageService := &CountingImageService{}
mgr := NewManager(log.NewNopLogger(), &metrics.State{}, nil, mgr := NewManager(log.NewNopLogger(), &metrics.State{}, nil,
&store.FakeRuleStore{}, &store.FakeInstanceStore{}, &store.FakeRuleStore{}, &store.FakeInstanceStore{},
&dashboards.FakeDashboardService{}, imageService, clock.NewMock()) &dashboards.FakeDashboardService{}, imageService, clock.NewMock(), annotationstest.NewFakeAnnotationsRepo())
err := mgr.maybeTakeScreenshot(context.Background(), &ngmodels.AlertRule{}, test.state, test.oldState) err := mgr.maybeTakeScreenshot(context.Background(), &ngmodels.AlertRule{}, test.state, test.oldState)
require.NoError(t, err) require.NoError(t, err)
if !test.shouldScreenshot { if !test.shouldScreenshot {
@ -156,9 +156,7 @@ func TestIsItStale(t *testing.T) {
func TestClose(t *testing.T) { func TestClose(t *testing.T) {
instanceStore := &store.FakeInstanceStore{} instanceStore := &store.FakeInstanceStore{}
clk := clock.New() clk := clock.New()
st := NewManager(log.New("test_state_manager"), metrics.NewNGAlert(prometheus.NewPedanticRegistry()).GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk) st := NewManager(log.New("test_state_manager"), metrics.NewNGAlert(prometheus.NewPedanticRegistry()).GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk, annotationstest.NewFakeAnnotationsRepo())
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
_, rules := ngmodels.GenerateUniqueAlertRules(10, ngmodels.AlertRuleGen()) _, rules := ngmodels.GenerateUniqueAlertRules(10, ngmodels.AlertRuleGen())
for _, rule := range rules { for _, rule := range rules {

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/image" "github.com/grafana/grafana/pkg/services/ngalert/image"
@ -37,10 +38,8 @@ func TestDashboardAnnotations(t *testing.T) {
ctx := context.Background() ctx := context.Background()
_, dbstore := tests.SetupTestEnv(t, 1) _, dbstore := tests.SetupTestEnv(t, 1)
st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.New()) fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.New(), fakeAnnoRepo)
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
const mainOrgID int64 = 1 const mainOrgID int64 = 1
@ -62,7 +61,7 @@ func TestDashboardAnnotations(t *testing.T) {
sort.Strings(expected) sort.Strings(expected)
require.Eventuallyf(t, func() bool { require.Eventuallyf(t, func() bool {
var actual []string var actual []string
for _, next := range fakeAnnoRepo.Items { for _, next := range fakeAnnoRepo.Items() {
actual = append(actual, next.Text) actual = append(actual, next.Text)
} }
sort.Strings(actual) sort.Strings(actual)
@ -1981,11 +1980,9 @@ func TestProcessEvalResults(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, &store.FakeInstanceStore{}, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clock.New()) fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, &store.FakeInstanceStore{}, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clock.New(), fakeAnnoRepo)
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
for _, res := range tc.evalResults { for _, res := range tc.evalResults {
_ = st.ProcessEvalResults(context.Background(), evaluationTime, tc.alertRule, res, data.Labels{ _ = st.ProcessEvalResults(context.Background(), evaluationTime, tc.alertRule, res, data.Labels{
"alertname": tc.alertRule.Title, "alertname": tc.alertRule.Title,
@ -2005,16 +2002,14 @@ func TestProcessEvalResults(t *testing.T) {
require.Eventuallyf(t, func() bool { require.Eventuallyf(t, func() bool {
return tc.expectedAnnotations == fakeAnnoRepo.Len() return tc.expectedAnnotations == fakeAnnoRepo.Len()
}, time.Second, 100*time.Millisecond, "%d annotations are present, expected %d. We have %+v", fakeAnnoRepo.Len(), tc.expectedAnnotations, printAllAnnotations(fakeAnnoRepo.Items)) }, time.Second, 100*time.Millisecond, "%d annotations are present, expected %d. We have %+v", fakeAnnoRepo.Len(), tc.expectedAnnotations, printAllAnnotations(fakeAnnoRepo.Items()))
}) })
} }
t.Run("should save state to database", func(t *testing.T) { t.Run("should save state to database", func(t *testing.T) {
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
instanceStore := &store.FakeInstanceStore{} instanceStore := &store.FakeInstanceStore{}
clk := clock.New() clk := clock.New()
st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk) st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk, annotationstest.NewFakeAnnotationsRepo())
rule := models.AlertRuleGen()() rule := models.AlertRuleGen()()
var results = eval.GenerateResults(rand.Intn(4)+1, eval.ResultGen(eval.WithEvaluatedAt(clk.Now()))) var results = eval.GenerateResults(rand.Intn(4)+1, eval.ResultGen(eval.WithEvaluatedAt(clk.Now())))
@ -2038,10 +2033,10 @@ func TestProcessEvalResults(t *testing.T) {
}) })
} }
func printAllAnnotations(annos []*annotations.Item) string { func printAllAnnotations(annos map[int64]annotations.Item) string {
str := "[" str := "["
for _, anno := range annos { for _, anno := range annos {
str += fmt.Sprintf("%+v, ", *anno) str += fmt.Sprintf("%+v, ", anno)
} }
str += "]" str += "]"
@ -2131,7 +2126,7 @@ func TestStaleResultsHandler(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
ctx := context.Background() ctx := context.Background()
st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.New()) st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.New(), annotationstest.NewFakeAnnotationsRepo())
st.Warm(ctx) st.Warm(ctx)
existingStatesForRule := st.GetStatesForRuleUID(rule.OrgID, rule.UID) existingStatesForRule := st.GetStatesForRuleUID(rule.OrgID, rule.UID)

View File

@ -7,7 +7,6 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -481,47 +480,3 @@ func (f *FakeAdminConfigStore) UpdateAdminConfiguration(cmd UpdateAdminConfigura
return nil return nil
} }
type FakeAnnotationsRepo struct {
mtx sync.Mutex
Items []*annotations.Item
}
func NewFakeAnnotationsRepo() *FakeAnnotationsRepo {
return &FakeAnnotationsRepo{
Items: make([]*annotations.Item, 0),
}
}
func (repo *FakeAnnotationsRepo) Len() int {
repo.mtx.Lock()
defer repo.mtx.Unlock()
return len(repo.Items)
}
func (repo *FakeAnnotationsRepo) Delete(_ context.Context, params *annotations.DeleteParams) error {
return nil
}
func (repo *FakeAnnotationsRepo) Save(item *annotations.Item) error {
repo.mtx.Lock()
defer repo.mtx.Unlock()
repo.Items = append(repo.Items, item)
return nil
}
func (repo *FakeAnnotationsRepo) Update(_ context.Context, item *annotations.Item) error {
return nil
}
func (repo *FakeAnnotationsRepo) Find(_ context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
annotations := []*annotations.ItemDTO{{Id: 1}}
return annotations, nil
}
func (repo *FakeAnnotationsRepo) FindTags(_ context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
result := annotations.FindTagsResult{
Tags: []*annotations.TagsDTO{},
}
return result, nil
}

View File

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
databasestore "github.com/grafana/grafana/pkg/services/dashboards/database" databasestore "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
@ -81,7 +82,7 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
ng, err := ngalert.ProvideService( ng, err := ngalert.ProvideService(
cfg, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil, cfg, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil,
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac, secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac, annotationstest.NewFakeAnnotationsRepo(),
) )
require.NoError(t, err) require.NoError(t, err)
return ng, &store.DBstore{ return ng, &store.DBstore{

View File

@ -120,7 +120,6 @@ func newSQLStore(cfg *setting.Cfg, cacheService *localcache.CacheService, engine
dialect = ss.Dialect dialect = ss.Dialect
// Init repo instances // Init repo instances
annotations.SetRepository(&SQLAnnotationRepo{sql: ss})
annotations.SetAnnotationCleaner(&AnnotationCleanupService{batchSize: ss.Cfg.AnnotationCleanupJobBatchSize, log: log.New("annotationcleaner"), sqlstore: ss}) annotations.SetAnnotationCleaner(&AnnotationCleanupService{batchSize: ss.Cfg.AnnotationCleanupJobBatchSize, log: log.New("annotationcleaner"), sqlstore: ss})
// if err := ss.Reset(); err != nil { // if err := ss.Reset(); err != nil {

View File

@ -2,7 +2,6 @@ package sqlstore
import ( import (
"context" "context"
"time"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -10,8 +9,6 @@ import (
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
var timeNow = time.Now
type Store interface { type Store interface {
GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error
GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error