mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Chore: SQL store split for annotations (#55089)
* Chore: SQL store split for annotations * Apply suggestion from code review
This commit is contained in:
parent
469f915b8c
commit
754eea20b3
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)),
|
||||
|
@ -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() {
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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{}
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
44
pkg/services/annotations/annotationsimpl/annotations.go
Normal file
44
pkg/services/annotations/annotationsimpl/annotations.go
Normal 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)
|
||||
}
|
15
pkg/services/annotations/annotationsimpl/store.go
Normal file
15
pkg/services/annotations/annotationsimpl/store.go
Normal 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)
|
||||
}
|
@ -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
|
@ -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,
|
||||
})
|
83
pkg/services/annotations/annotationstest/fake.go
Normal file
83
pkg/services/annotations/annotationstest/fake.go
Normal 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
|
||||
}
|
135
pkg/services/annotations/models.go
Normal file
135
pkg/services/annotations/models.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user