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:
Arati R 2023-06-02 16:38:02 +02:00 committed by GitHub
parent e6e88aa528
commit 6cb1a5e368
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 377 additions and 184 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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