diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 3ae5c362926..145d294d930 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -63,9 +63,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response { } } - repo := annotations.GetRepository() - - items, err := repo.Find(c.Req.Context(), query) + items, err := hs.annotationsRepo.Find(c.Req.Context(), query) if err != nil { 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) } - repo := annotations.GetRepository() - if cmd.Text == "" { err := &AnnotationError{"text field should not be empty"} 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, } - if err := repo.Save(&item); err != nil { + if err := hs.annotationsRepo.Save(c.Req.Context(), &item); err != nil { if errors.Is(err, annotations.ErrTimerangeMissing) { 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 { return response.Error(http.StatusBadRequest, "bad request data", err) } - repo := annotations.GetRepository() - if cmd.What == "" { err := &AnnotationError{"what field should not be empty"} 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, } - 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) } @@ -267,9 +261,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "annotationId is invalid", err) } - repo := annotations.GetRepository() - - annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser) + annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser) if resp != nil { return resp } @@ -288,7 +280,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response { 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) } @@ -319,9 +311,7 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "annotationId is invalid", err) } - repo := annotations.GetRepository() - - annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser) + annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser) if resp != nil { return resp } @@ -356,7 +346,7 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response { 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) } @@ -391,7 +381,6 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo return response.Error(http.StatusBadRequest, "bad request data", err) } - repo := annotations.GetRepository() var deleteParams *annotations.DeleteParams // 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 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 { 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 { 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) } - repo := annotations.GetRepository() - - annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser) + annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser) if resp != nil { return resp } @@ -485,9 +472,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon return response.Error(http.StatusBadRequest, "annotationId is invalid", err) } - repo := annotations.GetRepository() - - annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser) + annotation, resp := findAnnotationByID(c.Req.Context(), hs.annotationsRepo, annotationID, c.SignedInUser) if resp != nil { return resp } @@ -496,7 +481,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon return dashboardGuardianResponse(err) } - err = repo.Delete(c.Req.Context(), &annotations.DeleteParams{ + err = hs.annotationsRepo.Delete(c.Req.Context(), &annotations.DeleteParams{ OrgId: c.OrgID, Id: annotationID, }) @@ -563,8 +548,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *models.ReqContext) response.Response Limit: c.QueryInt64("limit"), } - repo := annotations.GetRepository() - result, err := repo.FindTags(c.Req.Context(), query) + result, err := hs.annotationsRepo.FindTags(c.Req.Context(), query) if err != nil { 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 // resolve annotation types. Scope "annotations:id:" will be translated to "annotations:type:, // where is the type of annotation with id . -func AnnotationTypeScopeResolver() (string, accesscontrol.ScopeAttributeResolver) { +func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository) (string, accesscontrol.ScopeAttributeResolver) { prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("") return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) { 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 { return nil, errors.New("could not resolve annotation type") } diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index a2cf920cc3e..6337eafe1fc 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "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/guardian" "github.com/grafana/grafana/pkg/services/org" @@ -73,8 +74,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) sc.handlerFunc = hs.DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 403, sc.resp.Code) @@ -103,8 +102,6 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) sc.handlerFunc = hs.DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() 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", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { setUpACL() - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) sc.handlerFunc = hs.DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() 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", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { setUpACL() - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) sc.handlerFunc = hs.DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() 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, cmd dtos.PostAnnotationsCmd, store sqlstore.Store, dashSvc dashboards.DashboardService, fn scenarioFunc) { 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) }) - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) - sc.m.Post(routePattern, sc.defaultHandler) fn(sc) @@ -387,9 +324,6 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s return hs.UpdateAnnotation(c) }) - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) - sc.m.Put(routePattern, sc.defaultHandler) fn(sc) @@ -415,9 +349,6 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern return hs.PatchAnnotation(c) }) - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) - sc.m.Patch(routePattern, sc.defaultHandler) fn(sc) @@ -443,9 +374,6 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte return hs.MassDeleteAnnotations(c) }) - fakeAnnoRepo = NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) - sc.m.Post(routePattern, sc.defaultHandler) fn(sc) @@ -461,11 +389,8 @@ func TestAPI_Annotations_AccessControl(t *testing.T) { dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} - fakeAnnoRepo = NewFakeAnnotationsRepo() - _ = fakeAnnoRepo.Save(dashboardAnnotation) - _ = fakeAnnoRepo.Save(organizationAnnotation) - - annotations.SetRepository(fakeAnnoRepo) + _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation) + _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation) postOrganizationCmd := dtos.PostAnnotationsCmd{ Time: 1000, @@ -788,7 +713,7 @@ func TestAPI_Annotations_AccessControl(t *testing.T) { t.Run(tt.name, func(t *testing.T) { setUpRBACGuardian(t) sc.acmock. - RegisterScopeAttributeResolver(AnnotationTypeScopeResolver()) + RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(sc.hs.annotationsRepo)) setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID) 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} organizationAnnotation := annotations.Item{Id: 2} - fakeAnnoRepo = NewFakeAnnotationsRepo() - _ = fakeAnnoRepo.Save(&dashboardAnnotation) - _ = fakeAnnoRepo.Save(&organizationAnnotation) - - annotations.SetRepository(fakeAnnoRepo) + fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() + _ = fakeAnnoRepo.Save(context.Background(), &dashboardAnnotation) + _ = fakeAnnoRepo.Save(context.Background(), &organizationAnnotation) - prefix, resolver := AnnotationTypeScopeResolver() + prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo) require.Equal(t, "annotations:id:", prefix) for _, tc := range testCases { @@ -986,11 +909,8 @@ func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) { dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} - fakeAnnoRepo = NewFakeAnnotationsRepo() - _ = fakeAnnoRepo.Save(dashboardAnnotation) - _ = fakeAnnoRepo.Save(organizationAnnotation) - - annotations.SetRepository(fakeAnnoRepo) + _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation) + _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation) 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) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index c6399bd0da3..68a5b46ce65 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "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/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler/authproxy" @@ -337,10 +338,11 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer { cfg.IsFeatureToggleEnabled = features.IsEnabled return &HTTPServer{ - Cfg: cfg, - Features: features, - License: &licensing.OSSLicensingService{}, - AccessControl: accesscontrolmock.New().WithDisabled(), + Cfg: cfg, + Features: features, + License: &licensing.OSSLicensingService{}, + AccessControl: accesscontrolmock.New().WithDisabled(), + annotationsRepo: annotationstest.NewFakeAnnotationsRepo(), } } @@ -408,6 +410,7 @@ func setupHTTPServerWithCfgDb( ), preferenceService: preftest.NewPreferenceServiceFake(), userService: userMock, + annotationsRepo: annotationstest.NewFakeAnnotationsRepo(), } for _, o := range options { diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index a266a4b4b77..d39723dc979 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/models" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "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/database" "github.com/grafana/grafana/pkg/services/dashboards/service" @@ -109,7 +110,7 @@ func newTestLive(t *testing.T, store *sqlstore.SQLStore) *live.GrafanaLive { nil, &usagestats.UsageStatsMock{T: t}, nil, - features, accesscontrolmock.New(), &dashboards.FakeDashboardService{}) + features, accesscontrolmock.New(), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo()) require.NoError(t, err) return gLive } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index d2adafee387..84f789f26e1 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -37,6 +37,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/services/accesscontrol" "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/cleanup" "github.com/grafana/grafana/pkg/services/comments" @@ -189,6 +190,7 @@ type HTTPServer struct { loginAttemptService loginAttempt.Service orgService org.Service accesscontrolService accesscontrol.Service + annotationsRepo annotations.Repository } 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, secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, 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) { web.Env = cfg.Env m := web.New() @@ -323,6 +325,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi loginAttemptService: loginAttemptService, orgService: orgService, accesscontrolService: accesscontrolService, + annotationsRepo: annotationRepo, } if hs.Listener != nil { hs.log.Debug("Using provided listener") @@ -330,7 +333,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi hs.registerRoutes() // Register access control scope resolver for annotations - hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver()) + hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo)) if err := hs.declareFixedRoles(); err != nil { return nil, err diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 223f1427f92..df89c225aae 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -6,6 +6,8 @@ package server import ( "github.com/google/wire" 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/playlist/playlistimpl" @@ -152,6 +154,8 @@ import ( var wireBasicSet = wire.NewSet( legacydataservice.ProvideService, wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)), + annotationsimpl.ProvideService, + wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), alerting.ProvideAlertStore, alerting.ProvideAlertEngine, wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)), diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 5ffe528735e..67a8c31c055 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" "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/datasources" "github.com/grafana/grafana/pkg/services/encryption" @@ -48,6 +49,7 @@ type AlertEngine struct { dashAlertExtractor DashAlertExtractor dashboardService dashboards.DashboardService datasourceService datasources.DataSourceService + annotationsRepo annotations.Repository } // 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, dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal, 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{ Cfg: cfg, RenderService: renderer, @@ -71,6 +73,7 @@ func ProvideAlertEngine(renderer rendering.Service, requestValidator models.Plug dashAlertExtractor: dashAlertExtractor, dashboardService: dashboardService, datasourceService: dsService, + annotationsRepo: annotationsRepo, } e.execQueue = make(chan *Job, 1000) 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) cancelChan <- cancelFn 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 go func() { diff --git a/pkg/services/alerting/engine_integration_test.go b/pkg/services/alerting/engine_integration_test.go index 669c11d226f..6daf0c29b43 100644 --- a/pkg/services/alerting/engine_integration_test.go +++ b/pkg/services/alerting/engine_integration_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" datasources "github.com/grafana/grafana/pkg/services/datasources/fakes" encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" @@ -35,7 +36,8 @@ func TestIntegrationEngineTimeouts(t *testing.T) { tracer := tracing.InitializeTracerForTest() 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.AlertingMaxAttempts = 3 engine.resultHandler = &FakeResultHandler{} diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index 5ba059b8d68..e281537902f 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/datasources" fd "github.com/grafana/grafana/pkg/services/datasources/fakes" encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider" @@ -128,7 +129,7 @@ func TestEngineProcessJob(t *testing.T) { dsMock := &fd.FakeDataSourceService{ 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.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 15745b49b16..e4b3a8a2fbf 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "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/datasources" "github.com/grafana/grafana/pkg/setting" @@ -42,11 +43,12 @@ type EvalContext struct { Store AlertStore dashboardService dashboards.DashboardService DatasourceService datasources.DataSourceService + annotationRepo annotations.Repository } // NewEvalContext is the EvalContext constructor. 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{ Ctx: alertCtx, StartTime: time.Now(), @@ -60,6 +62,7 @@ func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator model Store: alertStore, dashboardService: dashboardService, DatasourceService: dsService, + annotationRepo: annotationRepo, } } diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index c906f9ea5fe..d05ea3b995c 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -10,11 +10,12 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/validations" ) 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) { ctx.PrevAlertState = models.AlertStateOK @@ -199,7 +200,7 @@ func TestGetStateFromEvalContext(t *testing.T) { } 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) newState := evalContext.GetNewState() @@ -391,7 +392,7 @@ func TestEvaluateNotificationTemplateFields(t *testing.T) { for _, test := range tests { t.Run(test.name, func(tt *testing.T) { 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.AllMatches = test.allMatches diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 7cb45874fe4..67baf26cab9 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/tsdb/legacydata" @@ -29,7 +30,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { Conditions: []Condition{&conditionStub{ firing: true, }}, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) 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) { context := NewEvalContext(context.Background(), &Rule{ Conditions: []Condition{&conditionStub{firing: true, operator: "and"}}, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, true, context.Firing) @@ -52,7 +53,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}}, &conditionStub{firing: false, operator: "and"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, false, context.Firing) @@ -65,7 +66,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, true, context.Firing) @@ -78,7 +79,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "and"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, false, context.Firing) @@ -92,7 +93,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, true, context.Firing) @@ -106,7 +107,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, false, context.Firing) @@ -120,7 +121,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "and"}, &conditionStub{firing: true, operator: "and"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, false, context.Firing) @@ -134,7 +135,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "or"}, &conditionStub{firing: true, operator: "or"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) 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"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) 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}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.False(t, context.NoDataFound) @@ -174,7 +175,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { Conditions: []Condition{ &conditionStub{operator: "and", noData: true}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.Equal(t, false, context.Firing) @@ -187,7 +188,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{operator: "and", noData: true}, &conditionStub{operator: "and", noData: false}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.True(t, context.NoDataFound) @@ -199,7 +200,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{operator: "or", noData: true}, &conditionStub{operator: "or", noData: false}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) handler.Eval(context) require.True(t, context.NoDataFound) diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index 92d18d32842..334bac71823 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/imguploader" "github.com/grafana/grafana/pkg/components/simplejson" "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/rendering" "github.com/grafana/grafana/pkg/services/validations" @@ -20,18 +21,18 @@ import ( func TestNotificationService(t *testing.T) { testRule := &Rule{Name: "Test", Message: "Something is bad"} 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}"} - 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{{ Tags: map[string]string{ "instance": "localhost:3000", "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", evalCtx, true, func(sc *scenarioContext) { diff --git a/pkg/services/alerting/notifiers/alertmanager_test.go b/pkg/services/alerting/notifiers/alertmanager_test.go index fcbaf1909ef..57d52c4872c 100644 --- a/pkg/services/alerting/notifiers/alertmanager_test.go +++ b/pkg/services/alerting/notifiers/alertmanager_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -68,7 +69,7 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) { am := &AlertmanagerNotifier{log: log.New("test.logger")} evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: tc.prevState, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.Rule.State = tc.newState diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 60ff43f068a..8931f7433b6 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/validations" "github.com/stretchr/testify/assert" @@ -170,7 +171,7 @@ func TestShouldSendAlertNotification(t *testing.T) { for _, tc := range tcs { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: tc.prevState, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) if tc.state == nil { tc.state = &models.AlertNotificationState{} diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index 27255a6d89b..9f36f05c5fc 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -52,7 +53,7 @@ func TestDingDingNotifier(t *testing.T) { &alerting.Rule{ State: models.AlertStateAlerting, Message: `{host="localhost"}`, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) _, err = notifier.genBody(evalContext, "") require.Nil(t, err) }) diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index e6d3389ba8a..7def9ebac72 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/validations" @@ -105,7 +106,7 @@ func TestOpsGenieNotifier(t *testing.T) { Message: "someMessage", State: models.AlertStateAlerting, AlertRuleTags: tagPairs, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true tags := make([]string, 0) @@ -154,7 +155,7 @@ func TestOpsGenieNotifier(t *testing.T) { Message: "someMessage", State: models.AlertStateAlerting, AlertRuleTags: tagPairs, - }, nil, nil, nil, nil) + }, nil, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true tags := make([]string, 0) @@ -203,7 +204,7 @@ func TestOpsGenieNotifier(t *testing.T) { Message: "someMessage", State: models.AlertStateAlerting, AlertRuleTags: tagPairs, - }, nil, nil, nil, nil) + }, nil, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true tags := make([]string, 0) diff --git a/pkg/services/alerting/notifiers/pagerduty_test.go b/pkg/services/alerting/notifiers/pagerduty_test.go index 170d76adbb1..2fe27ad962b 100644 --- a/pkg/services/alerting/notifiers/pagerduty_test.go +++ b/pkg/services/alerting/notifiers/pagerduty_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -142,7 +143,7 @@ func TestPagerdutyNotifier(t *testing.T) { Name: "someRule", Message: "someMessage", State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) @@ -198,7 +199,7 @@ func TestPagerdutyNotifier(t *testing.T) { ID: 0, Name: "someRule", State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) @@ -256,7 +257,7 @@ func TestPagerdutyNotifier(t *testing.T) { Name: "someRule", Message: "someMessage", State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true evalContext.EvalMatches = []*alerting.EvalMatch{ { @@ -335,7 +336,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "severity", Value: "warning"}, {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.IsTestRun = true @@ -414,7 +415,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "component", Value: "aComponent"}, {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.IsTestRun = true @@ -493,7 +494,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "component", Value: "aComponent"}, {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.IsTestRun = true diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index 9d342696493..6286e0892bb 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -76,7 +77,7 @@ func TestGenPushoverBody(t *testing.T) { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") require.Nil(t, err) @@ -87,7 +88,7 @@ func TestGenPushoverBody(t *testing.T) { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") require.Nil(t, err) diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index e2f91a9a180..26ce91576c2 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -61,7 +62,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message.", State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "") require.LessOrEqual(t, len(caption), 1024) @@ -77,7 +78,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message.", State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) caption := generateImageCaption(evalContext, "http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -95,7 +96,7 @@ func TestTelegramNotifier(t *testing.T) { 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.", State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) caption := generateImageCaption(evalContext, "http://grafa.url/foo", @@ -112,7 +113,7 @@ func TestTelegramNotifier(t *testing.T) { 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", State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) caption := generateImageCaption(evalContext, "http://grafa.url/foo", diff --git a/pkg/services/alerting/notifiers/victorops_test.go b/pkg/services/alerting/notifiers/victorops_test.go index 12cb2639a68..676031b03a5 100644 --- a/pkg/services/alerting/notifiers/victorops_test.go +++ b/pkg/services/alerting/notifiers/victorops_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/validations" @@ -93,7 +94,7 @@ func TestVictoropsNotifier(t *testing.T) { {Key: "keyOnly"}, {Key: "severity", Value: "warning"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true payload, err := victoropsNotifier.buildEventPayload(evalContext) @@ -141,7 +142,7 @@ func TestVictoropsNotifier(t *testing.T) { {Key: "keyOnly"}, {Key: "severity", Value: "warning"}, }, - }, &validations.OSSPluginRequestValidator{}, nil, nil, nil) + }, &validations.OSSPluginRequestValidator{}, nil, nil, nil, annotationstest.NewFakeAnnotationsRepo()) evalContext.IsTestRun = true payload, err := victoropsNotifier.buildEventPayload(evalContext) diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 3bb01b92d0e..366b6512058 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -95,8 +95,7 @@ func (handler *defaultResultHandler) handle(evalContext *EvalContext) error { Data: annotationData, } - annotationRepo := annotations.GetRepository() - if err := annotationRepo.Save(&item); err != nil { + if err := evalContext.annotationRepo.Save(evalContext.Ctx, &item); err != nil { handler.log.Error("Failed to save annotation for new alert state", "error", err) } } diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index 4c80f7c9153..0119fc167fc 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" ) // NotificationTestCommand initiates an test @@ -57,7 +58,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext { 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) { ctx.ImagePublicURL = "https://grafana.com/assets/img/blog/mixed_styles.png" } diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go index cce5128c015..9e3c3e1e4e7 100644 --- a/pkg/services/alerting/test_rule.go +++ b/pkg/services/alerting/test_rule.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "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) - 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.IsDebug = true diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index 7c40c18e7da..965a8743e59 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -4,8 +4,6 @@ import ( "context" "errors" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -14,7 +12,7 @@ var ( ) type Repository interface { - Save(item *Item) error + Save(ctx context.Context, item *Item) error Update(ctx context.Context, item *Item) error Find(ctx context.Context, query *ItemQuery) ([]*ItemDTO, 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) } -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 -} - -var repositoryInstance Repository +// var repositoryInstance Repository var cleanerInstance AnnotationCleaner func GetAnnotationCleaner() AnnotationCleaner { @@ -92,84 +34,3 @@ func GetAnnotationCleaner() AnnotationCleaner { func SetAnnotationCleaner(rep AnnotationCleaner) { 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 -} diff --git a/pkg/services/annotations/annotationsimpl/annotations.go b/pkg/services/annotations/annotationsimpl/annotations.go new file mode 100644 index 00000000000..8beb61ee7df --- /dev/null +++ b/pkg/services/annotations/annotationsimpl/annotations.go @@ -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) +} diff --git a/pkg/services/annotations/annotationsimpl/store.go b/pkg/services/annotations/annotationsimpl/store.go new file mode 100644 index 00000000000..db642fd8978 --- /dev/null +++ b/pkg/services/annotations/annotationsimpl/store.go @@ -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) +} diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/annotations/annotationsimpl/xorm_store.go similarity index 82% rename from pkg/services/sqlstore/annotation.go rename to pkg/services/annotations/annotationsimpl/xorm_store.go index 3b8901a8fe6..c2e4d21c0f6 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/annotations/annotationsimpl/xorm_store.go @@ -1,4 +1,4 @@ -package sqlstore +package annotationsimpl import ( "bytes" @@ -8,14 +8,21 @@ import ( "strings" "time" + "github.com/davecgh/go-spew/spew" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "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/searchstore" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" ) +var timeNow = time.Now + // Update the item so that EpochEnd >= Epoch func validateTimeRange(item *annotations.Item) error { if item.EpochEnd == 0 { @@ -34,15 +41,13 @@ func validateTimeRange(item *annotations.Item) error { } type SQLAnnotationRepo struct { - sql *SQLStore -} - -func NewSQLAnnotationRepo(sql *SQLStore) SQLAnnotationRepo { - return SQLAnnotationRepo{sql: sql} + cfg *setting.Cfg + db db.DB + log log.Logger } -func (r *SQLAnnotationRepo) Save(item *annotations.Item) error { - return r.sql.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error { +func (r *SQLAnnotationRepo) Add(ctx context.Context, item *annotations.Item) error { + return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { tags := models.ParseTagPairs(item.Tags) item.Tags = models.JoinTagPairs(tags) item.Created = timeNow().UnixNano() / int64(time.Millisecond) @@ -59,7 +64,7 @@ func (r *SQLAnnotationRepo) Save(item *annotations.Item) error { } if item.Tags != nil { - tags, err := EnsureTagsExist(sess, tags) + tags, err := sqlstore.EnsureTagsExist(sess, tags) if err != nil { 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 { - return r.sql.WithTransactionalDbSession(ctx, func(sess *DBSession) error { + return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { var ( isExist bool err error @@ -106,7 +111,7 @@ func (r *SQLAnnotationRepo) Update(ctx context.Context, item *annotations.Item) } if item.Tags != nil { - tags, err := EnsureTagsExist(sess, models.ParseTagPairs(item.Tags)) + tags, err := sqlstore.EnsureTagsExist(sess, models.ParseTagPairs(item.Tags)) if err != nil { 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 params := make([]interface{}, 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(` SELECT annotation.id, @@ -151,7 +156,7 @@ func (r *SQLAnnotationRepo) Find(ctx context.Context, query *annotations.ItemQue usr.login, alert.name as alert_name 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 INNER JOIN ( 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) for _, tag := range tags { if tag.Value == "" { - keyValueFilters = append(keyValueFilters, "(tag."+dialect.Quote("key")+" = ?)") + keyValueFilters = append(keyValueFilters, "(tag."+r.db.GetDialect().Quote("key")+" = ?)") params = append(params, tag.Key) } 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) } } @@ -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) if err != nil { 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 - 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 { items = nil 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 { - return r.sql.WithTransactionalDbSession(ctx, func(sess *DBSession) error { + return r.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { var ( sql string annoTagSQL string ) - sqlog.Info("delete", "orgId", params.OrgId) + r.log.Info("delete", "orgId", params.OrgId) if params.Id != 0 { 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 = ?" @@ -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 - err := r.sql.WithDbSession(ctx, func(dbSession *DBSession) error { + err := r.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { if query.Limit == 0 { query.Limit = 100 } var sql bytes.Buffer params := make([]interface{}, 0) - tagKey := `tag.` + dialect.Quote("key") - tagValue := `tag.` + dialect.Quote("value") + tagKey := `tag.` + r.db.GetDialect().Quote("key") + tagValue := `tag.` + r.db.GetDialect().Quote("value") sql.WriteString(` 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 = ?)`) 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+`%`) sql.WriteString(` GROUP 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) return err diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/annotations/annotationsimpl/xorm_store_test.go similarity index 86% rename from pkg/services/sqlstore/annotation_test.go rename to pkg/services/annotations/annotationsimpl/xorm_store_test.go index cc146bce3a2..1e77a3dfa6c 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/annotations/annotationsimpl/xorm_store_test.go @@ -1,12 +1,14 @@ -package sqlstore_test +package annotationsimpl import ( "context" "fmt" "testing" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,7 +27,7 @@ func TestIntegrationAnnotations(t *testing.T) { t.Skip("skipping integration test") } sql := sqlstore.InitTestDB(t) - repo := sqlstore.NewSQLAnnotationRepo(sql) + repo := SQLAnnotationRepo{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test")} testUser := &user.SignedInUser{ OrgID: 1, @@ -81,7 +83,7 @@ func TestIntegrationAnnotations(t *testing.T) { Epoch: 10, Tags: []string{"outage", "error", "type:outage", "server:server-1"}, } - err = repo.Save(annotation) + err = repo.Add(context.Background(), annotation) require.NoError(t, err) assert.Greater(t, annotation.Id, int64(0)) assert.Equal(t, annotation.Epoch, annotation.EpochEnd) @@ -96,7 +98,7 @@ func TestIntegrationAnnotations(t *testing.T) { EpochEnd: 20, Tags: []string{"outage", "error", "type:outage", "server:server-1"}, } - err = repo.Save(annotation2) + err = repo.Add(context.Background(), annotation2) require.NoError(t, err) assert.Greater(t, annotation2.Id, int64(0)) assert.Equal(t, int64(20), annotation2.Epoch) @@ -110,7 +112,7 @@ func TestIntegrationAnnotations(t *testing.T) { Epoch: 15, Tags: []string{"deploy"}, } - err = repo.Save(organizationAnnotation1) + err = repo.Add(context.Background(), organizationAnnotation1) require.NoError(t, err) assert.Greater(t, organizationAnnotation1.Id, int64(0)) @@ -122,11 +124,11 @@ func TestIntegrationAnnotations(t *testing.T) { Epoch: 17, Tags: []string{"rollback"}, } - err = repo.Save(globalAnnotation2) + err = repo.Add(context.Background(), globalAnnotation2) require.NoError(t, err) assert.Greater(t, globalAnnotation2.Id, int64(0)) 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, DashboardId: dashboard.Id, From: 0, @@ -145,7 +147,7 @@ func TestIntegrationAnnotations(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, AnnotationId: annotation2.Id, 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, DashboardId: 1, 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, DashboardId: 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, DashboardId: 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, DashboardId: 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, From: 1, 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) { - items, err := repo.Find(context.Background(), &annotations.ItemQuery{ + items, err := repo.Get(context.Background(), &annotations.ItemQuery{ OrgId: 1, DashboardId: 1, From: 1, @@ -240,7 +242,7 @@ func TestIntegrationAnnotations(t *testing.T) { To: 15, SignedInUser: testUser, } - items, err := repo.Find(context.Background(), query) + items, err := repo.Get(context.Background(), query) require.NoError(t, err) annotationId := items[0].Id @@ -252,7 +254,7 @@ func TestIntegrationAnnotations(t *testing.T) { }) require.NoError(t, err) - items, err = repo.Find(context.Background(), query) + items, err = repo.Get(context.Background(), query) require.NoError(t, err) assert.Equal(t, annotationId, items[0].Id) @@ -268,7 +270,7 @@ func TestIntegrationAnnotations(t *testing.T) { To: 15, SignedInUser: testUser, } - items, err := repo.Find(context.Background(), query) + items, err := repo.Get(context.Background(), query) require.NoError(t, err) annotationId := items[0].Id @@ -280,7 +282,7 @@ func TestIntegrationAnnotations(t *testing.T) { }) require.NoError(t, err) - items, err = repo.Find(context.Background(), query) + items, err = repo.Get(context.Background(), query) require.NoError(t, err) assert.Equal(t, annotationId, items[0].Id) @@ -297,14 +299,14 @@ func TestIntegrationAnnotations(t *testing.T) { To: 15, SignedInUser: testUser, } - items, err := repo.Find(context.Background(), query) + items, err := repo.Get(context.Background(), query) require.NoError(t, err) annotationId := items[0].Id err = repo.Delete(context.Background(), &annotations.DeleteParams{Id: annotationId, OrgId: 1}) require.NoError(t, err) - items, err = repo.Find(context.Background(), query) + items, err = repo.Get(context.Background(), query) require.NoError(t, err) assert.Empty(t, items) }) @@ -320,7 +322,7 @@ func TestIntegrationAnnotations(t *testing.T) { Tags: []string{"test"}, PanelId: 20, } - err = repo.Save(annotation3) + err = repo.Add(context.Background(), annotation3) require.NoError(t, err) query := &annotations.ItemQuery{ @@ -328,7 +330,7 @@ func TestIntegrationAnnotations(t *testing.T) { AnnotationId: annotation3.Id, SignedInUser: testUser, } - items, err := repo.Find(context.Background(), query) + items, err := repo.Get(context.Background(), query) require.NoError(t, err) 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}) require.NoError(t, err) - items, err = repo.Find(context.Background(), query) + items, err = repo.Get(context.Background(), query) require.NoError(t, err) assert.Empty(t, items) }) 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, Tag: "server", }) @@ -353,7 +355,7 @@ func TestIntegrationAnnotations(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, Tag: "outage", }) @@ -366,7 +368,7 @@ func TestIntegrationAnnotations(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, 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) { - result, err := repo.FindTags(context.Background(), &annotations.TagsQuery{ + result, err := repo.GetTags(context.Background(), &annotations.TagsQuery{ OrgID: 0, Tag: "unknown:tag", }) @@ -390,7 +392,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { t.Skip("skipping integration test") } 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()) testDashboard1 := models.SaveDashboardCommand{ @@ -419,7 +421,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { DashboardId: 1, Epoch: 10, } - err = repo.Save(dash1Annotation) + err = repo.Add(context.Background(), dash1Annotation) require.NoError(t, err) dash2Annotation := &annotations.Item{ @@ -427,14 +429,14 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { DashboardId: 2, Epoch: 10, } - err = repo.Save(dash2Annotation) + err = repo.Add(context.Background(), dash2Annotation) require.NoError(t, err) organizationAnnotation := &annotations.Item{ OrgId: 1, Epoch: 10, } - err = repo.Save(organizationAnnotation) + err = repo.Add(context.Background(), organizationAnnotation) require.NoError(t, err) user := &user.SignedInUser{ @@ -501,7 +503,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { 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, SignedInUser: user, }) diff --git a/pkg/services/annotations/annotationstest/fake.go b/pkg/services/annotations/annotationstest/fake.go new file mode 100644 index 00000000000..1c9b367c891 --- /dev/null +++ b/pkg/services/annotations/annotationstest/fake.go @@ -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 +} diff --git a/pkg/services/annotations/models.go b/pkg/services/annotations/models.go new file mode 100644 index 00000000000..6f393342790 --- /dev/null +++ b/pkg/services/annotations/models.go @@ -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 +} diff --git a/pkg/services/comments/commentmodel/permissions.go b/pkg/services/comments/commentmodel/permissions.go index d8a5f009d98..df55fff5e11 100644 --- a/pkg/services/comments/commentmodel/permissions.go +++ b/pkg/services/comments/commentmodel/permissions.go @@ -19,12 +19,14 @@ type PermissionChecker struct { features featuremgmt.FeatureToggles accessControl accesscontrol.AccessControl dashboardService dashboards.DashboardService + annotationsRepo annotations.Repository } func NewPermissionChecker(sqlStore *sqlstore.SQLStore, features featuremgmt.FeatureToggles, accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService, + annotationsRepo annotations.Repository, ) *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) { @@ -63,12 +65,11 @@ func (c *PermissionChecker) CheckReadPermissions(ctx context.Context, orgId int6 if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) { return false, nil } - repo := annotations.GetRepository() annotationID, err := strconv.ParseInt(objectID, 10, 64) if err != 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 { return false, nil } @@ -116,12 +117,11 @@ func (c *PermissionChecker) CheckWritePermissions(ctx context.Context, orgId int return canEdit, err } } - repo := annotations.GetRepository() annotationID, err := strconv.ParseInt(objectID, 10, 64) if err != 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 { return false, nil } diff --git a/pkg/services/comments/service.go b/pkg/services/comments/service.go index 682681be948..ee746ce03a7 100644 --- a/pkg/services/comments/service.go +++ b/pkg/services/comments/service.go @@ -4,6 +4,7 @@ import ( "context" "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/dashboards" "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, 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{ cfg: cfg, live: live, @@ -32,7 +33,7 @@ func ProvideService(cfg *setting.Cfg, store *sqlstore.SQLStore, live *live.Grafa storage: &sqlStorage{ sql: store, }, - permissions: commentmodel.NewPermissionChecker(store, features, accessControl, dashboardService), + permissions: commentmodel.NewPermissionChecker(store, features, accessControl, dashboardService, annotationsRepo), userService: userService, } return s diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 06b64a81850..163e514cedd 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/plugincontext" "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/dashboards" "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, dataSourceCache datasources.CacheService, sqlStore *sqlstore.SQLStore, secretsService secrets.Service, 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{ Cfg: cfg, Features: toggles, @@ -247,7 +248,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r g.GrafanaScope.Dashboards = dash g.GrafanaScope.Features["dashboard"] = dash 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) err = g.surveyCaller.SetupHandlers() diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index ea89adf8563..5d6a2614f48 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "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/datasourceproxy" "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, quotaService quota.Service, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert, 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{ Cfg: cfg, DataSourceCache: dataSourceCache, @@ -62,6 +63,7 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, renderService: renderService, bus: bus, accesscontrolService: accesscontrolService, + annotationsRepo: annotationsRepo, } if ng.IsDisabled() { @@ -102,6 +104,7 @@ type AlertNG struct { AlertsRouter *sender.AlertsRouter accesscontrol accesscontrol.AccessControl accesscontrolService accesscontrol.Service + annotationsRepo annotations.Repository bus bus.Bus } @@ -165,7 +168,7 @@ func (ng *AlertNG) init() error { 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) // if it is required to include folder title to the alerts, we need to subscribe to changes of alert title diff --git a/pkg/services/ngalert/schedule/schedule_test.go b/pkg/services/ngalert/schedule/schedule_test.go index dbbb9e171e4..0c1dab2f329 100644 --- a/pkg/services/ngalert/schedule/schedule_test.go +++ b/pkg/services/ngalert/schedule/schedule_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "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/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -112,7 +113,7 @@ func TestWarmStateCache(t *testing.T) { InstanceStore: dbstore, 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) t.Run("instance cache has expected entries", func(t *testing.T) { @@ -166,7 +167,7 @@ func TestAlertingTicker(t *testing.T) { Metrics: testMetrics.GetSchedulerMetrics(), 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{ Scheme: "http", Host: "localhost", diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index dc730ac2ad1..f063b3548fa 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -21,7 +21,7 @@ import ( "github.com/grafana/grafana/pkg/expr" "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/ngalert/api/tooling/definitions" "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 { t.Helper() - fakeAnnoRepo := store.NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) mockedClock := clock.NewMock() logger := log.New("ngalert schedule test") @@ -531,7 +529,7 @@ func setupScheduler(t *testing.T, rs *store.FakeRuleStore, is *store.FakeInstanc Metrics: m.GetSchedulerMetrics(), 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) } diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index d839a56b7ff..06bcc7f51fc 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -45,11 +45,12 @@ type Manager struct { instanceStore store.InstanceStore dashboardService dashboards.DashboardService imageService image.ImageService + AnnotationsRepo annotations.Repository } func NewManager(logger log.Logger, metrics *metrics.State, externalURL *url.URL, 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{ cache: newCache(logger, metrics, externalURL), quit: make(chan struct{}), @@ -61,6 +62,7 @@ func NewManager(logger log.Logger, metrics *metrics.State, externalURL *url.URL, dashboardService: dashboardService, imageService: imageService, clock: clock, + AnnotationsRepo: annotationsRepo, } go manager.recordMetrics() return manager @@ -419,8 +421,7 @@ func (st *Manager) annotateState(ctx context.Context, alertRule *ngModels.AlertR item.DashboardId = query.Result.Id } - annotationRepo := annotations.GetRepository() - if err := annotationRepo.Save(item); err != nil { + if err := st.AnnotationsRepo.Save(ctx, item); err != nil { st.log.Error("error saving alert annotation", "alertRuleUID", alertRule.UID, "err", err.Error()) return } diff --git a/pkg/services/ngalert/state/manager_private_test.go b/pkg/services/ngalert/state/manager_private_test.go index edee75ed404..aec97f7453b 100644 --- a/pkg/services/ngalert/state/manager_private_test.go +++ b/pkg/services/ngalert/state/manager_private_test.go @@ -13,7 +13,7 @@ import ( "github.com/benbjohnson/clock" "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/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -98,7 +98,7 @@ func Test_maybeNewImage(t *testing.T) { imageService := &CountingImageService{} mgr := NewManager(log.NewNopLogger(), &metrics.State{}, nil, &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) require.NoError(t, err) if !test.shouldScreenshot { @@ -156,9 +156,7 @@ func TestIsItStale(t *testing.T) { func TestClose(t *testing.T) { instanceStore := &store.FakeInstanceStore{} clk := clock.New() - st := NewManager(log.New("test_state_manager"), metrics.NewNGAlert(prometheus.NewPedanticRegistry()).GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk) - fakeAnnoRepo := store.NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) + st := NewManager(log.New("test_state_manager"), metrics.NewNGAlert(prometheus.NewPedanticRegistry()).GetStateMetrics(), nil, nil, instanceStore, &dashboards.FakeDashboardService{}, &image.NotAvailableImageService{}, clk, annotationstest.NewFakeAnnotationsRepo()) _, rules := ngmodels.GenerateUniqueAlertRules(10, ngmodels.AlertRuleGen()) for _, rule := range rules { diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 688947e6b94..1382c72ea38 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/expr" "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/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -37,10 +38,8 @@ func TestDashboardAnnotations(t *testing.T) { ctx := context.Background() _, 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 := store.NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) + fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() + st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, &dashboards.FakeDashboardService{}, &image.NoopImageService{}, clock.New(), fakeAnnoRepo) const mainOrgID int64 = 1 @@ -62,7 +61,7 @@ func TestDashboardAnnotations(t *testing.T) { sort.Strings(expected) require.Eventuallyf(t, func() bool { var actual []string - for _, next := range fakeAnnoRepo.Items { + for _, next := range fakeAnnoRepo.Items() { actual = append(actual, next.Text) } sort.Strings(actual) @@ -1981,11 +1980,9 @@ func TestProcessEvalResults(t *testing.T) { } 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) { - fakeAnnoRepo := store.NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) - for _, res := range tc.evalResults { _ = st.ProcessEvalResults(context.Background(), evaluationTime, tc.alertRule, res, data.Labels{ "alertname": tc.alertRule.Title, @@ -2005,16 +2002,14 @@ func TestProcessEvalResults(t *testing.T) { require.Eventuallyf(t, func() bool { 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) { - fakeAnnoRepo := store.NewFakeAnnotationsRepo() - annotations.SetRepository(fakeAnnoRepo) instanceStore := &store.FakeInstanceStore{} 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()() 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 := "[" for _, anno := range annos { - str += fmt.Sprintf("%+v, ", *anno) + str += fmt.Sprintf("%+v, ", anno) } str += "]" @@ -2131,7 +2126,7 @@ func TestStaleResultsHandler(t *testing.T) { for _, tc := range testCases { 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) existingStatesForRule := st.GetStatesForRuleUID(rule.OrgID, rule.UID) diff --git a/pkg/services/ngalert/store/testing.go b/pkg/services/ngalert/store/testing.go index 0a1f4ee82e6..60b8864e9d0 100644 --- a/pkg/services/ngalert/store/testing.go +++ b/pkg/services/ngalert/store/testing.go @@ -7,7 +7,6 @@ import ( "sync" "testing" - "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" @@ -481,47 +480,3 @@ func (f *FakeAdminConfigStore) UpdateAdminConfiguration(cmd UpdateAdminConfigura 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 -} diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 348db1797e9..d4bc4ec4897 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/dashboards" databasestore "github.com/grafana/grafana/pkg/services/dashboards/database" 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( 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) return ng, &store.DBstore{ diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 0e46295cb26..730523c88ae 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -120,7 +120,6 @@ func newSQLStore(cfg *setting.Cfg, cacheService *localcache.CacheService, engine dialect = ss.Dialect // Init repo instances - annotations.SetRepository(&SQLAnnotationRepo{sql: ss}) annotations.SetAnnotationCleaner(&AnnotationCleanupService{batchSize: ss.Cfg.AnnotationCleanupJobBatchSize, log: log.New("annotationcleaner"), sqlstore: ss}) // if err := ss.Reset(); err != nil { diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 99b7991ed17..1a5538d2c74 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -2,7 +2,6 @@ package sqlstore import ( "context" - "time" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" @@ -10,8 +9,6 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) -var timeNow = time.Now - type Store interface { GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error