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)
|
SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error)
|
||||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*DashboardACL) error
|
UpdateDashboardACL(ctx context.Context, uid int64, items []*DashboardACL) error
|
||||||
DeleteACLByUser(ctx context.Context, userID int64) 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.
|
// 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)
|
return dr.dashboardStore.DeleteACLByUser(ctx, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr DashboardServiceImpl) CountInFolder(ctx context.Context, orgID int64, uid string, u *user.SignedInUser) (int64, error) {
|
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: &uid, OrgID: orgID, SignedInUser: u})
|
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{UID: &folderUID, OrgID: orgID, SignedInUser: u})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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})
|
return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.ID, OrgID: orgID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, UID string) error {
|
func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error {
|
||||||
return dr.dashboardStore.DeleteDashboardsInFolder(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUID: UID, OrgID: orgID})
|
return dr.dashboardStore.DeleteDashboardsInFolder(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUID: folderUID, OrgID: orgID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard }
|
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
|
return dashboards.ErrFolderAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID); err != nil {
|
if cmd.ForceDeleteRules {
|
||||||
return err
|
if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.legacyDelete(ctx, cmd, dashFolder)
|
err = s.legacyDelete(ctx, cmd, dashFolder)
|
||||||
@ -499,9 +501,9 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
|
|||||||
return err
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -886,11 +888,6 @@ func (s *Service) RegisterService(r folder.RegistryService) error {
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
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
|
s.registry[r.Kind()] = r
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@ -27,6 +28,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"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/quota/quotatest"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
@ -336,6 +339,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
|
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
|
||||||
|
|
||||||
|
b := bus.ProvideBus(tracing.InitializeTracerForTest())
|
||||||
|
ac := acimpl.ProvideAccessControl(cfg)
|
||||||
|
|
||||||
serviceWithFlagOn := &Service{
|
serviceWithFlagOn := &Service{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: log.New("test-folder-service"),
|
log: log.New("test-folder-service"),
|
||||||
@ -343,9 +349,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
dashboardFolderStore: folderStore,
|
dashboardFolderStore: folderStore,
|
||||||
store: nestedFolderStore,
|
store: nestedFolderStore,
|
||||||
features: featuresFlagOn,
|
features: featuresFlagOn,
|
||||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
bus: b,
|
||||||
db: db,
|
db: db,
|
||||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
accessControl: ac,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,16 +364,19 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
SignedInUser: &signedInUser,
|
SignedInUser: &signedInUser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folderPermissions := acmock.NewMockedPermissionsService()
|
||||||
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||||
|
|
||||||
t.Run("Should get descendant counts", func(t *testing.T) {
|
t.Run("Should get descendant counts", func(t *testing.T) {
|
||||||
ac := acmock.New()
|
|
||||||
folderPermissions := acmock.NewMockedPermissionsService()
|
|
||||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
|
||||||
depth := 5
|
depth := 5
|
||||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||||
origNewGuardian := guardian.New
|
origNewGuardian := guardian.New
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
|
||||||
@ -378,6 +387,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.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{
|
countCmd := folder.GetDescendantCountsQuery{
|
||||||
UID: &ancestorUIDs[0],
|
UID: &ancestorUIDs[0],
|
||||||
@ -386,8 +397,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
|
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m["folder"], int64(depth-1))
|
require.Equal(t, int64(depth-1), m["folder"])
|
||||||
require.Equal(t, m["dashboard"], int64(2))
|
require.Equal(t, int64(2), m["dashboard"])
|
||||||
|
require.Equal(t, int64(2), m["alertrule"])
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
guardian.New = origNewGuardian
|
guardian.New = origNewGuardian
|
||||||
@ -410,7 +422,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
dashboardFolderStore: folderStore,
|
dashboardFolderStore: folderStore,
|
||||||
store: nestedFolderStore,
|
store: nestedFolderStore,
|
||||||
features: featuresFlagOff,
|
features: featuresFlagOff,
|
||||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
bus: b,
|
||||||
db: db,
|
db: db,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
}
|
}
|
||||||
@ -418,7 +430,11 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
origNewGuardian := guardian.New
|
origNewGuardian := guardian.New
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
|
||||||
@ -429,6 +445,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.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{
|
countCmd := folder.GetDescendantCountsQuery{
|
||||||
UID: &ancestorUIDs[0],
|
UID: &ancestorUIDs[0],
|
||||||
@ -437,8 +455,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
|
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m["folder"], int64(0))
|
require.Equal(t, int64(0), m["folder"])
|
||||||
require.Equal(t, m["dashboard"], int64(1))
|
require.Equal(t, int64(1), m["dashboard"])
|
||||||
|
require.Equal(t, int64(1), m["alertrule"])
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
guardian.New = origNewGuardian
|
guardian.New = origNewGuardian
|
||||||
@ -452,29 +471,78 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should delete folders", func(t *testing.T) {
|
t.Run("Should delete folders", func(t *testing.T) {
|
||||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||||
origNewGuardian := guardian.New
|
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||||
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)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, uid := range ancestorUIDs {
|
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
|
||||||
// dashboard table
|
require.NoError(t, err)
|
||||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
t.Run("With force deletion of rules", func(t *testing.T) {
|
||||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
origNewGuardian := guardian.New
|
||||||
// folder table
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
|
||||||
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "with-force", createCmd)
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||||
guardian.New = origNewGuardian
|
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) {
|
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)
|
require.NoError(t, err)
|
||||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
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{
|
serviceWithFlagOff := &Service{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: log.New("test-folder-service"),
|
log: log.New("test-folder-service"),
|
||||||
@ -490,37 +563,74 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
dashboardFolderStore: folderStore,
|
dashboardFolderStore: folderStore,
|
||||||
store: nestedFolderStore,
|
store: nestedFolderStore,
|
||||||
features: featuresFlagOff,
|
features: featuresFlagOff,
|
||||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
bus: b,
|
||||||
db: db,
|
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
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-force", createCmd)
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
|
||||||
|
|
||||||
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{
|
deleteCmd := folder.DeleteFolderCommand{
|
||||||
UID: ancestorUIDs[0],
|
UID: ancestorUIDs[0],
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
SignedInUser: &signedInUser,
|
SignedInUser: &signedInUser,
|
||||||
}
|
ForceDeleteRules: true,
|
||||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
}
|
||||||
require.NoError(t, err)
|
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, uid := range ancestorUIDs {
|
|
||||||
// dashboard table
|
// 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)
|
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||||
// folder table
|
// 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)
|
require.NoError(t, err)
|
||||||
}
|
t.Cleanup(func() {
|
||||||
t.Cleanup(func() {
|
guardian.New = origNewGuardian
|
||||||
guardian.New = origNewGuardian
|
for _, uid := range ancestorUIDs {
|
||||||
for _, uid := range ancestorUIDs {
|
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
require.NoError(t, err)
|
||||||
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,
|
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 {
|
type RegistryService interface {
|
||||||
DeleteInFolder(ctx context.Context, orgID int64, uid string) error
|
DeleteInFolder(ctx context.Context, orgID int64, folderUID string) 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)
|
||||||
Kind() string
|
Kind() string
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ func ProvideService(
|
|||||||
annotationsRepo annotations.Repository,
|
annotationsRepo annotations.Repository,
|
||||||
pluginsStore plugins.Store,
|
pluginsStore plugins.Store,
|
||||||
tracer tracing.Tracer,
|
tracer tracing.Tracer,
|
||||||
|
ruleStore *store.DBstore,
|
||||||
) (*AlertNG, error) {
|
) (*AlertNG, error) {
|
||||||
ng := &AlertNG{
|
ng := &AlertNG{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
@ -92,6 +93,7 @@ func ProvideService(
|
|||||||
annotationsRepo: annotationsRepo,
|
annotationsRepo: annotationsRepo,
|
||||||
pluginsStore: pluginsStore,
|
pluginsStore: pluginsStore,
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
|
store: ruleStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ng.IsDisabled() {
|
if ng.IsDisabled() {
|
||||||
@ -149,25 +151,16 @@ func (ng *AlertNG) init() error {
|
|||||||
initCtx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
|
initCtx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
store := &store.DBstore{
|
ng.store.Logger = ng.Log
|
||||||
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
|
|
||||||
|
|
||||||
decryptFn := ng.SecretsService.GetDecryptedValue
|
decryptFn := ng.SecretsService.GetDecryptedValue
|
||||||
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics()
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -186,7 +179,7 @@ func (ng *AlertNG) init() error {
|
|||||||
|
|
||||||
clk := clock.New()
|
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)
|
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.
|
// 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),
|
DisableGrafanaFolder: ng.Cfg.UnifiedAlerting.ReservedLabels.IsReservedLabelDisabled(models.FolderTitleLabel),
|
||||||
AppURL: appUrl,
|
AppURL: appUrl,
|
||||||
EvaluatorFactory: evalFactory,
|
EvaluatorFactory: evalFactory,
|
||||||
RuleStore: store,
|
RuleStore: ng.store,
|
||||||
Metrics: ng.Metrics.GetSchedulerMetrics(),
|
Metrics: ng.Metrics.GetSchedulerMetrics(),
|
||||||
AlertSender: alertsRouter,
|
AlertSender: alertsRouter,
|
||||||
Tracer: ng.tracer,
|
Tracer: ng.tracer,
|
||||||
@ -221,7 +214,7 @@ func (ng *AlertNG) init() error {
|
|||||||
cfg := state.ManagerCfg{
|
cfg := state.ManagerCfg{
|
||||||
Metrics: ng.Metrics.GetStateMetrics(),
|
Metrics: ng.Metrics.GetStateMetrics(),
|
||||||
ExternalURL: appUrl,
|
ExternalURL: appUrl,
|
||||||
InstanceStore: store,
|
InstanceStore: ng.store,
|
||||||
Images: ng.imageService,
|
Images: ng.imageService,
|
||||||
Clock: clk,
|
Clock: clk,
|
||||||
Historian: history,
|
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 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) {
|
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.stateManager = stateManager
|
||||||
ng.schedule = scheduler
|
ng.schedule = scheduler
|
||||||
|
|
||||||
// Provisioning
|
// Provisioning
|
||||||
policyService := provisioning.NewNotificationPolicyService(store, store, store, ng.Cfg.UnifiedAlerting, ng.Log)
|
policyService := provisioning.NewNotificationPolicyService(ng.store, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||||
contactPointService := provisioning.NewContactPointService(store, ng.SecretsService, store, store, ng.Log)
|
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, ng.Log)
|
||||||
templateService := provisioning.NewTemplateService(store, store, store, ng.Log)
|
templateService := provisioning.NewTemplateService(ng.store, ng.store, ng.store, ng.Log)
|
||||||
muteTimingService := provisioning.NewMuteTimingService(store, store, store, ng.Log)
|
muteTimingService := provisioning.NewMuteTimingService(ng.store, ng.store, ng.store, ng.Log)
|
||||||
alertRuleService := provisioning.NewAlertRuleService(store, store, ng.dashboardService, ng.QuotaService, store,
|
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.dashboardService, ng.QuotaService, ng.store,
|
||||||
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||||
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()), ng.Log)
|
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()), ng.Log)
|
||||||
|
|
||||||
@ -254,11 +247,11 @@ func (ng *AlertNG) init() error {
|
|||||||
RouteRegister: ng.RouteRegister,
|
RouteRegister: ng.RouteRegister,
|
||||||
DataProxy: ng.DataProxy,
|
DataProxy: ng.DataProxy,
|
||||||
QuotaService: ng.QuotaService,
|
QuotaService: ng.QuotaService,
|
||||||
TransactionManager: store,
|
TransactionManager: ng.store,
|
||||||
RuleStore: store,
|
RuleStore: ng.store,
|
||||||
AlertingStore: store,
|
AlertingStore: ng.store,
|
||||||
AdminConfigStore: store,
|
AdminConfigStore: ng.store,
|
||||||
ProvenanceStore: store,
|
ProvenanceStore: ng.store,
|
||||||
MultiOrgAlertmanager: ng.MultiOrgAlertmanager,
|
MultiOrgAlertmanager: ng.MultiOrgAlertmanager,
|
||||||
StateManager: ng.stateManager,
|
StateManager: ng.stateManager,
|
||||||
AccessControl: ng.accesscontrol,
|
AccessControl: ng.accesscontrol,
|
||||||
|
@ -19,16 +19,15 @@ import (
|
|||||||
|
|
||||||
func TestAlertRuleService(t *testing.T) {
|
func TestAlertRuleService(t *testing.T) {
|
||||||
ruleService := createAlertRuleService(t)
|
ruleService := createAlertRuleService(t)
|
||||||
|
var orgID int64 = 1
|
||||||
|
|
||||||
t.Run("alert rule creation should return the created id", func(t *testing.T) {
|
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)
|
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#1", orgID), models.ProvenanceNone, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, 0, rule.ID, "expected to get the created id and not the zero value")
|
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) {
|
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)
|
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#2", orgID), models.ProvenanceAPI, 0)
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("group creation should set the right provenance", func(t *testing.T) {
|
||||||
var orgID int64 = 1
|
|
||||||
group := createDummyGroup("group-test-1", orgID)
|
group := createDummyGroup("group-test-1", orgID)
|
||||||
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("alert rule group should be updated correctly", func(t *testing.T) {
|
||||||
var orgID int64 = 1
|
|
||||||
rule := dummyRule("test#3", orgID)
|
rule := dummyRule("test#3", orgID)
|
||||||
rule.RuleGroup = "a"
|
rule.RuleGroup = "a"
|
||||||
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone, 0)
|
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) {
|
t.Run("group creation should propagate group title correctly", func(t *testing.T) {
|
||||||
var orgID int64 = 1
|
|
||||||
group := createDummyGroup("group-test-3", orgID)
|
group := createDummyGroup("group-test-3", orgID)
|
||||||
group.Rules[0].RuleGroup = "something different"
|
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) {
|
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 := dummyRule("test#4", orgID)
|
||||||
rule.RuleGroup = "b"
|
rule.RuleGroup = "b"
|
||||||
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone, 0)
|
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) {
|
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)
|
group := createDummyGroup("group-test-5", orgID)
|
||||||
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI)
|
||||||
require.NoError(t, err)
|
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) {
|
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"
|
dashboardUid := "huYnkl7H"
|
||||||
panelId := int64(5678)
|
panelId := int64(5678)
|
||||||
group := createDummyGroup("group-test-5", orgID)
|
group := createDummyGroup("group-test-5", orgID)
|
||||||
@ -316,7 +309,7 @@ func TestAlertRuleService(t *testing.T) {
|
|||||||
checker.EXPECT().LimitExceeded()
|
checker.EXPECT().LimitExceeded()
|
||||||
ruleService.quotas = checker
|
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)
|
require.ErrorIs(t, err, models.ErrQuotaReached)
|
||||||
})
|
})
|
||||||
@ -358,10 +351,10 @@ func createAlertRuleService(t *testing.T) AlertRuleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dummyRule(title string, orgID int64) models.AlertRule {
|
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{
|
return models.AlertRule{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Title: title,
|
Title: title,
|
||||||
@ -379,7 +372,7 @@ func createTestRule(title string, groupTitle string, orgID int64) models.AlertRu
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NamespaceUID: "my-namespace",
|
NamespaceUID: namespace,
|
||||||
RuleGroup: groupTitle,
|
RuleGroup: groupTitle,
|
||||||
For: time.Second * 60,
|
For: time.Second * 60,
|
||||||
NoDataState: models.OK,
|
NoDataState: models.OK,
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/search/model"
|
"github.com/grafana/grafana/pkg/services/search/model"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
"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/services/user"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"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).
|
// 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 count int64
|
||||||
var err error
|
var err error
|
||||||
err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) 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()
|
count, err = q.Count()
|
||||||
return err
|
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.
|
// GenerateNewAlertRuleUID generates a unique UID for a rule.
|
||||||
// This is set as a variable so that the tests can override it.
|
// This is set as a variable so that the tests can override it.
|
||||||
// The ruleTitle is only used by the mocked functions.
|
// The ruleTitle is only used by the mocked functions.
|
||||||
|
@ -8,9 +8,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"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/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
"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/org"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
@ -209,7 +211,8 @@ func TestIntegration_CountAlertRules(t *testing.T) {
|
|||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
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 {
|
if test.expectErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} 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 {
|
func createRule(t *testing.T, store *DBstore, generate func() *models.AlertRule) *models.AlertRule {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if generate == nil {
|
if generate == nil {
|
||||||
@ -275,7 +300,7 @@ func setupFolderService(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setting.
|
|||||||
tracer := tracing.InitializeTracerForTest()
|
tracer := tracing.InitializeTracerForTest()
|
||||||
inProcBus := bus.ProvideBus(tracer)
|
inProcBus := bus.ProvideBus(tracer)
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
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(
|
func ProvideDBStore(
|
||||||
cfg *setting.Cfg, featureToggles featuremgmt.FeatureToggles, sqlstore db.DB, folderService folder.Service,
|
cfg *setting.Cfg, featureToggles featuremgmt.FeatureToggles, sqlstore db.DB, folderService folder.Service,
|
||||||
access accesscontrol.AccessControl, dashboards dashboards.DashboardService) *DBstore {
|
access accesscontrol.AccessControl, dashboards dashboards.DashboardService) (*DBstore, error) {
|
||||||
return &DBstore{
|
store := DBstore{
|
||||||
Cfg: cfg.UnifiedAlerting,
|
Cfg: cfg.UnifiedAlerting,
|
||||||
FeatureToggles: featureToggles,
|
FeatureToggles: featureToggles,
|
||||||
SQLStore: sqlstore,
|
SQLStore: sqlstore,
|
||||||
Logger: log.New("dbstore"),
|
Logger: log.New("ngalert.dbstore"),
|
||||||
FolderService: folderService,
|
FolderService: folderService,
|
||||||
AccessControl: access,
|
AccessControl: access,
|
||||||
DashboardService: dashboards,
|
DashboardService: dashboards,
|
||||||
}
|
}
|
||||||
|
if err := folderService.RegisterService(store); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &store, nil
|
||||||
}
|
}
|
||||||
|
@ -6,25 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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/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 {
|
func NewFakeImageStore(t *testing.T) *FakeImageStore {
|
||||||
@ -130,46 +112,3 @@ func (f *FakeAdminConfigStore) UpdateAdminConfiguration(cmd UpdateAdminConfigura
|
|||||||
|
|
||||||
return nil
|
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) {
|
func (f *RuleStore) Count(ctx context.Context, orgID int64) (int64, error) {
|
||||||
return 0, nil
|
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/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"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/org"
|
||||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
"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()
|
tracer := tracing.InitializeTracerForTest()
|
||||||
bus := bus.ProvideBus(tracer)
|
bus := bus.ProvideBus(tracer)
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||||
dashboardService, dashboardStore := store.SetupDashboardService(tb, sqlStore, folderStore, cfg)
|
dashboardService, dashboardStore := testutil.SetupDashboardService(tb, sqlStore, folderStore, cfg)
|
||||||
folderService := store.SetupFolderService(tb, cfg, dashboardStore, folderStore, bus)
|
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(
|
ng, err := ngalert.ProvideService(
|
||||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
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)
|
require.NoError(tb, err)
|
||||||
return ng, &store.DBstore{
|
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"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||||
ngalertmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
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"
|
||||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"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)
|
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New().WithDisabled(), acmock.NewMockedPermissionsService(), quotaService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
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(
|
_, err = ngalert.ProvideService(
|
||||||
sqlStore.Cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotaService,
|
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)
|
require.NoError(t, err)
|
||||||
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), sqlStore.Cfg, quotaService, storesrv.ProvideSystemUsersService())
|
_, 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:[] }
|
// the kind may need to change to better encapsulate { targets:[], transforms:[] }
|
||||||
StandardKindQuery = "query"
|
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
|
// References are referenced from objects
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
|
@ -19,8 +19,8 @@ export const deleteDashboard = createAsyncThunk('browseDashboards/deleteDashboar
|
|||||||
|
|
||||||
export const deleteFolder = createAsyncThunk('browseDashboards/deleteFolder', async (folderUID: string) => {
|
export const deleteFolder = createAsyncThunk('browseDashboards/deleteFolder', async (folderUID: string) => {
|
||||||
return getBackendSrv().delete(`/api/folders/${folderUID}`, undefined, {
|
return getBackendSrv().delete(`/api/folders/${folderUID}`, undefined, {
|
||||||
// TODO: Once backend returns alert rule counts, set this back to true
|
// TODO: Revisit this field when this permissions issue is resolved
|
||||||
// when this is merged https://github.com/grafana/grafana/pull/67259
|
// https://github.com/grafana/grafana-enterprise/issues/5144
|
||||||
params: { forceDeleteRules: false },
|
params: { forceDeleteRules: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user