Chore: SQL store split for annotations (#55089)

* Chore: SQL store split for annotations

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

View File

@ -63,9 +63,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response {
}
}
repo := annotations.GetRepository()
items, err := 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:<id>" will be translated to "annotations:type:<type>,
// where <type> is the type of annotation with id <id>.
func AnnotationTypeScopeResolver() (string, accesscontrol.ScopeAttributeResolver) {
func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository) (string, accesscontrol.ScopeAttributeResolver) {
prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("")
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")
}

View File

@ -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)
fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
_ = fakeAnnoRepo.Save(context.Background(), &dashboardAnnotation)
_ = fakeAnnoRepo.Save(context.Background(), &organizationAnnotation)
annotations.SetRepository(fakeAnnoRepo)
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)

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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)),

View File

@ -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() {

View File

@ -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{}

View File

@ -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

View File

@ -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,
}
}

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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{}

View File

@ -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)
})

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package sqlstore
package annotationsimpl
import (
"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
cfg *setting.Cfg
db db.DB
log log.Logger
}
func NewSQLAnnotationRepo(sql *SQLStore) SQLAnnotationRepo {
return SQLAnnotationRepo{sql: sql}
}
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

View File

@ -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,
})

View File

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

View File

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

View File

@ -19,12 +19,14 @@ type PermissionChecker struct {
features featuremgmt.FeatureToggles
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
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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",

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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{

View File

@ -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 {

View File

@ -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