mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nested folders: Add alert rule counts and deletion to folder registry (#67259)
* Let alert rule service implement registry service * Add count method to RuleStore interface * Add implementation for deletion of alert rules * Rename uid to folderUID in registry methods * Check forceDeleteRule value for registry deletion * Register alerting store with folder service * Move folder test functions to separate package * Add testing for alert rule counting, deletion * Remove redundant count method * Fix deleteChildrenInFolder signature * Update pkg/services/ngalert/store/alert_rule.go Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> * Add tests for nested folder deletion * Refactor TestIntegrationNestedFolderService * Add rules store as parameter for alertng provider --------- Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
This commit is contained in:
parent
e6e88aa528
commit
6cb1a5e368
@ -27,7 +27,7 @@ type DashboardService interface {
|
||||
SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error)
|
||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*DashboardACL) error
|
||||
DeleteACLByUser(ctx context.Context, userID int64) error
|
||||
CountInFolder(ctx context.Context, orgID int64, uid string, user *user.SignedInUser) (int64, error)
|
||||
CountInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) (int64, error)
|
||||
}
|
||||
|
||||
// PluginService is a service for operating on plugin dashboards.
|
||||
|
@ -626,8 +626,8 @@ func (dr *DashboardServiceImpl) DeleteACLByUser(ctx context.Context, userID int6
|
||||
return dr.dashboardStore.DeleteACLByUser(ctx, userID)
|
||||
}
|
||||
|
||||
func (dr DashboardServiceImpl) CountInFolder(ctx context.Context, orgID int64, uid string, u *user.SignedInUser) (int64, error) {
|
||||
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{UID: &uid, OrgID: orgID, SignedInUser: u})
|
||||
func (dr DashboardServiceImpl) CountInFolder(ctx context.Context, orgID int64, folderUID string, u *user.SignedInUser) (int64, error) {
|
||||
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{UID: &folderUID, OrgID: orgID, SignedInUser: u})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -635,8 +635,8 @@ func (dr DashboardServiceImpl) CountInFolder(ctx context.Context, orgID int64, u
|
||||
return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.ID, OrgID: orgID})
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, UID string) error {
|
||||
return dr.dashboardStore.DeleteDashboardsInFolder(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUID: UID, OrgID: orgID})
|
||||
func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error {
|
||||
return dr.dashboardStore.DeleteDashboardsInFolder(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUID: folderUID, OrgID: orgID})
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard }
|
||||
|
@ -484,8 +484,10 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
|
||||
return dashboards.ErrFolderAccessDenied
|
||||
}
|
||||
|
||||
if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID); err != nil {
|
||||
return err
|
||||
if cmd.ForceDeleteRules {
|
||||
if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.legacyDelete(ctx, cmd, dashFolder)
|
||||
@ -499,9 +501,9 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) deleteChildrenInFolder(ctx context.Context, orgID int64, UID string) error {
|
||||
func (s *Service) deleteChildrenInFolder(ctx context.Context, orgID int64, folderUID string) error {
|
||||
for _, v := range s.registry {
|
||||
if err := v.DeleteInFolder(ctx, orgID, UID); err != nil {
|
||||
if err := v.DeleteInFolder(ctx, orgID, folderUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -886,11 +888,6 @@ func (s *Service) RegisterService(r folder.RegistryService) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_, ok := s.registry[r.Kind()]
|
||||
if ok {
|
||||
return folder.ErrTargetRegistrySrvConflict.Errorf("target registry service: %s already exists", r.Kind())
|
||||
}
|
||||
|
||||
s.registry[r.Kind()] = r
|
||||
|
||||
return nil
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -27,6 +28,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
@ -336,6 +339,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
|
||||
|
||||
b := bus.ProvideBus(tracing.InitializeTracerForTest())
|
||||
ac := acimpl.ProvideAccessControl(cfg)
|
||||
|
||||
serviceWithFlagOn := &Service{
|
||||
cfg: cfg,
|
||||
log: log.New("test-folder-service"),
|
||||
@ -343,9 +349,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOn,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
bus: b,
|
||||
db: db,
|
||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
||||
accessControl: ac,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
}
|
||||
|
||||
@ -358,16 +364,19 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
|
||||
t.Run("Should get descendant counts", func(t *testing.T) {
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
depth := 5
|
||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
_, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
|
||||
require.NoError(t, err)
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
|
||||
@ -378,6 +387,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
|
||||
|
||||
countCmd := folder.GetDescendantCountsQuery{
|
||||
UID: &ancestorUIDs[0],
|
||||
@ -386,8 +397,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
}
|
||||
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m["folder"], int64(depth-1))
|
||||
require.Equal(t, m["dashboard"], int64(2))
|
||||
require.Equal(t, int64(depth-1), m["folder"])
|
||||
require.Equal(t, int64(2), m["dashboard"])
|
||||
require.Equal(t, int64(2), m["alertrule"])
|
||||
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
@ -410,7 +422,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOff,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
bus: b,
|
||||
db: db,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
}
|
||||
@ -418,7 +430,11 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
_, err = service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
|
||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff,
|
||||
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv)
|
||||
require.NoError(t, err)
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
|
||||
@ -429,6 +445,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
|
||||
|
||||
countCmd := folder.GetDescendantCountsQuery{
|
||||
UID: &ancestorUIDs[0],
|
||||
@ -437,8 +455,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
}
|
||||
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m["folder"], int64(0))
|
||||
require.Equal(t, m["dashboard"], int64(1))
|
||||
require.Equal(t, int64(0), m["folder"])
|
||||
require.Equal(t, int64(1), m["dashboard"])
|
||||
require.Equal(t, int64(1), m["alertrule"])
|
||||
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
@ -452,29 +471,78 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
|
||||
t.Run("Should delete folders", func(t *testing.T) {
|
||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "", createCmd)
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
|
||||
require.NoError(t, err)
|
||||
t.Run("With force deletion of rules", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "with-force", createCmd)
|
||||
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
|
||||
require.NoError(t, err)
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
ForceDeleteRules: true,
|
||||
}
|
||||
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
})
|
||||
t.Run("Without force deletion of rules", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "without-force", createCmd)
|
||||
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
|
||||
require.NoError(t, err)
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
ForceDeleteRules: false,
|
||||
}
|
||||
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||
require.Error(t, dashboards.ErrFolderContainsAlertRules, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.NoError(t, err)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("With nested folder feature flag off", func(t *testing.T) {
|
||||
@ -483,6 +551,11 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
||||
|
||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||
require.NoError(t, err)
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOn, ac, dashSrv)
|
||||
require.NoError(t, err)
|
||||
|
||||
serviceWithFlagOff := &Service{
|
||||
cfg: cfg,
|
||||
log: log.New("test-folder-service"),
|
||||
@ -490,37 +563,74 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOff,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
bus: b,
|
||||
db: db,
|
||||
}
|
||||
t.Run("With force deletion of rules", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-force", createCmd)
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "", createCmd)
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
ForceDeleteRules: true,
|
||||
}
|
||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
t.Run("Without force deletion of rules", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-no-force", createCmd)
|
||||
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
_ = createRule(t, alertStore, parent.UID, "parent alert")
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
ForceDeleteRules: false,
|
||||
}
|
||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||
require.Error(t, dashboards.ErrFolderContainsAlertRules, err)
|
||||
|
||||
// dashboard table
|
||||
_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1076,3 +1186,25 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func createRule(t *testing.T, store *ngstore.DBstore, folderUID, title string) *models.AlertRule {
|
||||
t.Helper()
|
||||
|
||||
rule := models.AlertRule{
|
||||
OrgID: orgID,
|
||||
NamespaceUID: folderUID,
|
||||
Title: title,
|
||||
Updated: time.Now(),
|
||||
UID: util.GenerateShortUID(),
|
||||
}
|
||||
err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
|
||||
_, err := sess.Table(models.AlertRule{}).InsertOne(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &rule
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RegistryService interface {
|
||||
DeleteInFolder(ctx context.Context, orgID int64, uid string) error
|
||||
CountInFolder(ctx context.Context, orgID int64, uid string, user *user.SignedInUser) (int64, error)
|
||||
DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error
|
||||
CountInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) (int64, error)
|
||||
Kind() string
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ func ProvideService(
|
||||
annotationsRepo annotations.Repository,
|
||||
pluginsStore plugins.Store,
|
||||
tracer tracing.Tracer,
|
||||
ruleStore *store.DBstore,
|
||||
) (*AlertNG, error) {
|
||||
ng := &AlertNG{
|
||||
Cfg: cfg,
|
||||
@ -92,6 +93,7 @@ func ProvideService(
|
||||
annotationsRepo: annotationsRepo,
|
||||
pluginsStore: pluginsStore,
|
||||
tracer: tracer,
|
||||
store: ruleStore,
|
||||
}
|
||||
|
||||
if ng.IsDisabled() {
|
||||
@ -149,25 +151,16 @@ func (ng *AlertNG) init() error {
|
||||
initCtx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancelFunc()
|
||||
|
||||
store := &store.DBstore{
|
||||
Cfg: ng.Cfg.UnifiedAlerting,
|
||||
FeatureToggles: ng.FeatureToggles,
|
||||
SQLStore: ng.SQLStore,
|
||||
Logger: ng.Log,
|
||||
FolderService: ng.folderService,
|
||||
AccessControl: ng.accesscontrol,
|
||||
DashboardService: ng.dashboardService,
|
||||
}
|
||||
ng.store = store
|
||||
ng.store.Logger = ng.Log
|
||||
|
||||
decryptFn := ng.SecretsService.GetDecryptedValue
|
||||
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics()
|
||||
ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, store, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager"), ng.SecretsService)
|
||||
ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, ng.store, ng.store, ng.KVStore, ng.store, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager"), ng.SecretsService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageService, err := image.NewScreenshotImageServiceFromCfg(ng.Cfg, store, ng.dashboardService, ng.renderService, ng.Metrics.Registerer)
|
||||
imageService, err := image.NewScreenshotImageServiceFromCfg(ng.Cfg, ng.store, ng.dashboardService, ng.renderService, ng.Metrics.Registerer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -186,7 +179,7 @@ func (ng *AlertNG) init() error {
|
||||
|
||||
clk := clock.New()
|
||||
|
||||
alertsRouter := sender.NewAlertsRouter(ng.MultiOrgAlertmanager, store, clk, appUrl, ng.Cfg.UnifiedAlerting.DisabledOrgs,
|
||||
alertsRouter := sender.NewAlertsRouter(ng.MultiOrgAlertmanager, ng.store, clk, appUrl, ng.Cfg.UnifiedAlerting.DisabledOrgs,
|
||||
ng.Cfg.UnifiedAlerting.AdminConfigPollInterval, ng.DataSourceService, ng.SecretsService)
|
||||
|
||||
// Make sure we sync at least once as Grafana starts to get the router up and running before we start sending any alerts.
|
||||
@ -205,7 +198,7 @@ func (ng *AlertNG) init() error {
|
||||
DisableGrafanaFolder: ng.Cfg.UnifiedAlerting.ReservedLabels.IsReservedLabelDisabled(models.FolderTitleLabel),
|
||||
AppURL: appUrl,
|
||||
EvaluatorFactory: evalFactory,
|
||||
RuleStore: store,
|
||||
RuleStore: ng.store,
|
||||
Metrics: ng.Metrics.GetSchedulerMetrics(),
|
||||
AlertSender: alertsRouter,
|
||||
Tracer: ng.tracer,
|
||||
@ -221,7 +214,7 @@ func (ng *AlertNG) init() error {
|
||||
cfg := state.ManagerCfg{
|
||||
Metrics: ng.Metrics.GetStateMetrics(),
|
||||
ExternalURL: appUrl,
|
||||
InstanceStore: store,
|
||||
InstanceStore: ng.store,
|
||||
Images: ng.imageService,
|
||||
Clock: clk,
|
||||
Historian: history,
|
||||
@ -232,18 +225,18 @@ func (ng *AlertNG) init() error {
|
||||
|
||||
// if it is required to include folder title to the alerts, we need to subscribe to changes of alert title
|
||||
if !ng.Cfg.UnifiedAlerting.ReservedLabels.IsReservedLabelDisabled(models.FolderTitleLabel) {
|
||||
subscribeToFolderChanges(ng.Log, ng.bus, store)
|
||||
subscribeToFolderChanges(ng.Log, ng.bus, ng.store)
|
||||
}
|
||||
|
||||
ng.stateManager = stateManager
|
||||
ng.schedule = scheduler
|
||||
|
||||
// Provisioning
|
||||
policyService := provisioning.NewNotificationPolicyService(store, store, store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||
contactPointService := provisioning.NewContactPointService(store, ng.SecretsService, store, store, ng.Log)
|
||||
templateService := provisioning.NewTemplateService(store, store, store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(store, store, store, ng.Log)
|
||||
alertRuleService := provisioning.NewAlertRuleService(store, store, ng.dashboardService, ng.QuotaService, store,
|
||||
policyService := provisioning.NewNotificationPolicyService(ng.store, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, ng.Log)
|
||||
templateService := provisioning.NewTemplateService(ng.store, ng.store, ng.store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(ng.store, ng.store, ng.store, ng.Log)
|
||||
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.dashboardService, ng.QuotaService, ng.store,
|
||||
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()), ng.Log)
|
||||
|
||||
@ -254,11 +247,11 @@ func (ng *AlertNG) init() error {
|
||||
RouteRegister: ng.RouteRegister,
|
||||
DataProxy: ng.DataProxy,
|
||||
QuotaService: ng.QuotaService,
|
||||
TransactionManager: store,
|
||||
RuleStore: store,
|
||||
AlertingStore: store,
|
||||
AdminConfigStore: store,
|
||||
ProvenanceStore: store,
|
||||
TransactionManager: ng.store,
|
||||
RuleStore: ng.store,
|
||||
AlertingStore: ng.store,
|
||||
AdminConfigStore: ng.store,
|
||||
ProvenanceStore: ng.store,
|
||||
MultiOrgAlertmanager: ng.MultiOrgAlertmanager,
|
||||
StateManager: ng.stateManager,
|
||||
AccessControl: ng.accesscontrol,
|
||||
|
@ -19,16 +19,15 @@ import (
|
||||
|
||||
func TestAlertRuleService(t *testing.T) {
|
||||
ruleService := createAlertRuleService(t)
|
||||
var orgID int64 = 1
|
||||
|
||||
t.Run("alert rule creation should return the created id", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#1", orgID), models.ProvenanceNone, 0)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, rule.ID, "expected to get the created id and not the zero value")
|
||||
})
|
||||
|
||||
t.Run("alert rule creation should set the right provenance", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#2", orgID), models.ProvenanceAPI, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -38,7 +37,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("group creation should set the right provenance", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
group := createDummyGroup("group-test-1", orgID)
|
||||
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
@ -54,7 +52,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("alert rule group should be updated correctly", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
rule := dummyRule("test#3", orgID)
|
||||
rule.RuleGroup = "a"
|
||||
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone, 0)
|
||||
@ -84,7 +81,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("group creation should propagate group title correctly", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
group := createDummyGroup("group-test-3", orgID)
|
||||
group.Rules[0].RuleGroup = "something different"
|
||||
|
||||
@ -100,7 +96,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("alert rule should get interval from existing rule group", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
rule := dummyRule("test#4", orgID)
|
||||
rule.RuleGroup = "b"
|
||||
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone, 0)
|
||||
@ -147,7 +142,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("updating a group by updating a rule should bump that rule's data and version number", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
group := createDummyGroup("group-test-5", orgID)
|
||||
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
@ -167,7 +161,6 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("updating a group by updating a rule should not remove dashboard and panel ids", func(t *testing.T) {
|
||||
var orgID int64 = 1
|
||||
dashboardUid := "huYnkl7H"
|
||||
panelId := int64(5678)
|
||||
group := createDummyGroup("group-test-5", orgID)
|
||||
@ -316,7 +309,7 @@ func TestAlertRuleService(t *testing.T) {
|
||||
checker.EXPECT().LimitExceeded()
|
||||
ruleService.quotas = checker
|
||||
|
||||
_, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#1", 1), models.ProvenanceNone, 0)
|
||||
_, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#1", orgID), models.ProvenanceNone, 0)
|
||||
|
||||
require.ErrorIs(t, err, models.ErrQuotaReached)
|
||||
})
|
||||
@ -358,10 +351,10 @@ func createAlertRuleService(t *testing.T) AlertRuleService {
|
||||
}
|
||||
|
||||
func dummyRule(title string, orgID int64) models.AlertRule {
|
||||
return createTestRule(title, "my-cool-group", orgID)
|
||||
return createTestRule(title, "my-cool-group", orgID, "my-namespace")
|
||||
}
|
||||
|
||||
func createTestRule(title string, groupTitle string, orgID int64) models.AlertRule {
|
||||
func createTestRule(title string, groupTitle string, orgID int64, namespace string) models.AlertRule {
|
||||
return models.AlertRule{
|
||||
OrgID: orgID,
|
||||
Title: title,
|
||||
@ -379,7 +372,7 @@ func createTestRule(title string, groupTitle string, orgID int64) models.AlertRu
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceUID: "my-namespace",
|
||||
NamespaceUID: namespace,
|
||||
RuleGroup: groupTitle,
|
||||
For: time.Second * 60,
|
||||
NoDataState: models.OK,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -230,13 +231,13 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, rules []ngmodels.UpdateR
|
||||
})
|
||||
}
|
||||
|
||||
// CountAlertRulesInFolder is a handler for retrieving the number of alert rules of
|
||||
// CountInFolder is a handler for retrieving the number of alert rules of
|
||||
// specific organisation associated with a given namespace (parent folder).
|
||||
func (st DBstore) CountAlertRulesInFolder(ctx context.Context, query *ngmodels.CountAlertRulesQuery) (int64, error) {
|
||||
func (st DBstore) CountInFolder(ctx context.Context, orgID int64, folderUID string, u *user.SignedInUser) (int64, error) {
|
||||
var count int64
|
||||
var err error
|
||||
err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
q := sess.Table("alert_rule").Where("org_id = ?", query.OrgID).Where("namespace_uid = ?", query.NamespaceUID)
|
||||
q := sess.Table("alert_rule").Where("org_id = ?", orgID).Where("namespace_uid = ?", folderUID)
|
||||
count, err = q.Count()
|
||||
return err
|
||||
})
|
||||
@ -494,6 +495,32 @@ func (st DBstore) GetAlertRulesForScheduling(ctx context.Context, query *ngmodel
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteInFolder deletes the rules contained in a given folder along with their associated data.
|
||||
func (st DBstore) DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error {
|
||||
rules, err := st.ListAlertRules(ctx, &ngmodels.ListAlertRulesQuery{
|
||||
OrgID: orgID,
|
||||
NamespaceUIDs: []string{folderUID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uids := make([]string, 0, len(rules))
|
||||
for _, tgt := range rules {
|
||||
if tgt != nil {
|
||||
uids = append(uids, tgt.UID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := st.DeleteAlertRulesByUID(ctx, orgID, uids...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kind returns the name of the alert rule type of entity.
|
||||
func (st DBstore) Kind() string { return entity.StandardKindAlertRule }
|
||||
|
||||
// GenerateNewAlertRuleUID generates a unique UID for a rule.
|
||||
// This is set as a variable so that the tests can override it.
|
||||
// The ruleTitle is only used by the mocked functions.
|
||||
|
@ -8,9 +8,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -209,7 +211,8 @@ func TestIntegration_CountAlertRules(t *testing.T) {
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
count, err := store.CountAlertRulesInFolder(context.Background(), test.query)
|
||||
count, err := store.CountInFolder(context.Background(),
|
||||
test.query.OrgID, test.query.NamespaceUID, nil)
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@ -220,6 +223,28 @@ func TestIntegration_CountAlertRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_DeleteInFolder(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
Logger: log.New("test-dbstore"),
|
||||
}
|
||||
rule := createRule(t, store, nil)
|
||||
|
||||
err := store.DeleteInFolder(context.Background(), rule.OrgID, rule.NamespaceUID)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err := store.CountInFolder(context.Background(), rule.OrgID, rule.NamespaceUID, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), c)
|
||||
}
|
||||
|
||||
func createRule(t *testing.T, store *DBstore, generate func() *models.AlertRule) *models.AlertRule {
|
||||
t.Helper()
|
||||
if generate == nil {
|
||||
@ -275,7 +300,7 @@ func setupFolderService(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setting.
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
inProcBus := bus.ProvideBus(tracer)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
_, dashboardStore := SetupDashboardService(t, sqlStore, folderStore, cfg)
|
||||
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
|
||||
|
||||
return SetupFolderService(t, cfg, dashboardStore, folderStore, inProcBus)
|
||||
return testutil.SetupFolderService(t, cfg, dashboardStore, folderStore, inProcBus)
|
||||
}
|
||||
|
@ -45,14 +45,18 @@ type DBstore struct {
|
||||
|
||||
func ProvideDBStore(
|
||||
cfg *setting.Cfg, featureToggles featuremgmt.FeatureToggles, sqlstore db.DB, folderService folder.Service,
|
||||
access accesscontrol.AccessControl, dashboards dashboards.DashboardService) *DBstore {
|
||||
return &DBstore{
|
||||
access accesscontrol.AccessControl, dashboards dashboards.DashboardService) (*DBstore, error) {
|
||||
store := DBstore{
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
FeatureToggles: featureToggles,
|
||||
SQLStore: sqlstore,
|
||||
Logger: log.New("dbstore"),
|
||||
Logger: log.New("ngalert.dbstore"),
|
||||
FolderService: folderService,
|
||||
AccessControl: access,
|
||||
DashboardService: dashboards,
|
||||
}
|
||||
if err := folderService.RegisterService(store); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
|
@ -6,25 +6,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func NewFakeImageStore(t *testing.T) *FakeImageStore {
|
||||
@ -130,46 +112,3 @@ func (f *FakeAdminConfigStore) UpdateAdminConfiguration(cmd UpdateAdminConfigura
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus) folder.Service {
|
||||
tb.Helper()
|
||||
|
||||
ac := acmock.New()
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, nil, features)
|
||||
}
|
||||
|
||||
func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
||||
tb.Helper()
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanSaveValue: true,
|
||||
CanViewValue: true,
|
||||
CanAdminValue: true,
|
||||
})
|
||||
tb.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
|
||||
ac := acmock.New()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(tb, err)
|
||||
|
||||
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, fs, nil,
|
||||
features, folderPermissions, dashboardPermissions, ac,
|
||||
foldertest.NewFakeService(),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return dashboardService, dashboardStore
|
||||
}
|
||||
|
@ -347,3 +347,7 @@ func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, org
|
||||
func (f *RuleStore) Count(ctx context.Context, orgID int64) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) CountInFolder(ctx context.Context, orgID int64, folderUID string, u *user.SignedInUser) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
@ -59,12 +60,15 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
bus := bus.ProvideBus(tracer)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
dashboardService, dashboardStore := store.SetupDashboardService(tb, sqlStore, folderStore, cfg)
|
||||
folderService := store.SetupFolderService(tb, cfg, dashboardStore, folderStore, bus)
|
||||
|
||||
dashboardService, dashboardStore := testutil.SetupDashboardService(tb, sqlStore, folderStore, cfg)
|
||||
folderService := testutil.SetupFolderService(tb, cfg, dashboardStore, folderStore, bus)
|
||||
ruleStore, err := store.ProvideDBStore(cfg, featuremgmt.WithFeatures(), sqlStore, folderService,
|
||||
ac, &dashboards.FakeDashboardService{})
|
||||
require.NoError(tb, err)
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac, annotationstest.NewFakeAnnotationsRepo(), &plugins.FakePluginStore{}, tracer,
|
||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac,
|
||||
annotationstest.NewFakeAnnotationsRepo(), &plugins.FakePluginStore{}, tracer, ruleStore,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
return ng, &store.DBstore{
|
||||
|
66
pkg/services/ngalert/testutil/testutil.go
Normal file
66
pkg/services/ngalert/testutil/testutil.go
Normal file
@ -0,0 +1,66 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus) folder.Service {
|
||||
tb.Helper()
|
||||
|
||||
ac := acmock.New()
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, nil, features)
|
||||
}
|
||||
|
||||
func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
||||
tb.Helper()
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanSaveValue: true,
|
||||
CanViewValue: true,
|
||||
CanAdminValue: true,
|
||||
})
|
||||
tb.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
|
||||
ac := acmock.New()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(tb, err)
|
||||
|
||||
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, fs, nil,
|
||||
features, folderPermissions, dashboardPermissions, ac,
|
||||
foldertest.NewFakeService(),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return dashboardService, dashboardStore
|
||||
}
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
ngalertmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
@ -477,9 +478,13 @@ func setupEnv(t *testing.T, sqlStore *sqlstore.SQLStore, b bus.Bus, quotaService
|
||||
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New().WithDisabled(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
ruleStore, err := ngstore.ProvideDBStore(sqlStore.Cfg, featuremgmt.WithFeatures(), sqlStore, &foldertest.FakeService{},
|
||||
&acmock.Mock{}, &dashboards.FakeDashboardService{})
|
||||
require.NoError(t, err)
|
||||
_, err = ngalert.ProvideService(
|
||||
sqlStore.Cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotaService,
|
||||
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{}, annotationstest.NewFakeAnnotationsRepo(), &plugins.FakePluginStore{}, tracer,
|
||||
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{},
|
||||
annotationstest.NewFakeAnnotationsRepo(), &plugins.FakePluginStore{}, tracer, ruleStore,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), sqlStore.Cfg, quotaService, storesrv.ProvideSystemUsersService())
|
||||
|
@ -42,6 +42,10 @@ const (
|
||||
// the kind may need to change to better encapsulate { targets:[], transforms:[] }
|
||||
StandardKindQuery = "query"
|
||||
|
||||
// KindAlertRule is not a real kind. It's used to refer to alert rules, for instance
|
||||
// in the folder registry service.
|
||||
StandardKindAlertRule = "alertrule"
|
||||
|
||||
//----------------------------------------
|
||||
// References are referenced from objects
|
||||
//----------------------------------------
|
||||
|
@ -19,8 +19,8 @@ export const deleteDashboard = createAsyncThunk('browseDashboards/deleteDashboar
|
||||
|
||||
export const deleteFolder = createAsyncThunk('browseDashboards/deleteFolder', async (folderUID: string) => {
|
||||
return getBackendSrv().delete(`/api/folders/${folderUID}`, undefined, {
|
||||
// TODO: Once backend returns alert rule counts, set this back to true
|
||||
// when this is merged https://github.com/grafana/grafana/pull/67259
|
||||
// TODO: Revisit this field when this permissions issue is resolved
|
||||
// https://github.com/grafana/grafana-enterprise/issues/5144
|
||||
params: { forceDeleteRules: false },
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user