NestedFolders: Add library panels counting and deletion to folder registry (#69149)

* Expose library element service's folder service
* Register library panels, add count implementation
* Expand folder counts test
* Update registry deletion method interface
* Allow getting library elements from any folder
* Add test for library panel deletion
* Add test for library panel counting
This commit is contained in:
Arati R
2023-07-25 13:05:53 +02:00
committed by GitHub
parent 32e2304f10
commit 20ffbbc41e
16 changed files with 305 additions and 182 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
@@ -28,10 +29,14 @@ 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/libraryelements"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/librarypanels"
"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/store/entity"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@@ -356,7 +361,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
orgID: {
dashboards.ActionFoldersCreate: {},
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
}}
createCmd := folder.CreateFolderCommand{
OrgID: orgID,
@@ -364,6 +371,20 @@ func TestIntegrationNestedFolderService(t *testing.T) {
SignedInUser: &signedInUser,
}
libraryElementCmd := model.CreateLibraryElementCommand{
Model: []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"title": "Text - Library Panel",
"type": "text",
"description": "A description"
}
`),
Kind: int64(model.PanelElement),
}
routeRegister := routing.NewRouteRegister()
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
@@ -371,7 +392,12 @@ func TestIntegrationNestedFolderService(t *testing.T) {
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})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
// CanEditValue is required to create library elements
CanEditValue: true,
})
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
require.NoError(t, err)
@@ -379,6 +405,10 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn)
require.NoError(t, err)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
@@ -390,6 +420,13 @@ func TestIntegrationNestedFolderService(t *testing.T) {
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
libraryElementCmd.FolderID = parent.ID
_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
libraryElementCmd.FolderID = subfolder.ID
_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
countCmd := folder.GetDescendantCountsQuery{
UID: &ancestorUIDs[0],
OrgID: orgID,
@@ -397,9 +434,10 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
require.NoError(t, err)
require.Equal(t, int64(depth-1), m["folder"])
require.Equal(t, int64(2), m["dashboard"])
require.Equal(t, int64(2), m["alertrule"])
require.Equal(t, int64(depth-1), m[entity.StandardKindFolder])
require.Equal(t, int64(2), m[entity.StandardKindDashboard])
require.Equal(t, int64(2), m[entity.StandardKindAlertRule])
require.Equal(t, int64(2), m[entity.StandardKindLibraryPanel])
t.Cleanup(func() {
guardian.New = origNewGuardian
@@ -428,7 +466,12 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
// CanEditValue is required to create library elements
CanEditValue: true,
})
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff,
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
@@ -437,6 +480,10 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff)
require.NoError(t, err)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
@@ -448,6 +495,13 @@ func TestIntegrationNestedFolderService(t *testing.T) {
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
libraryElementCmd.FolderID = parent.ID
_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
libraryElementCmd.FolderID = subfolder.ID
_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
countCmd := folder.GetDescendantCountsQuery{
UID: &ancestorUIDs[0],
OrgID: orgID,
@@ -455,9 +509,10 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
require.NoError(t, err)
require.Equal(t, int64(0), m["folder"])
require.Equal(t, int64(1), m["dashboard"])
require.Equal(t, int64(1), m["alertrule"])
require.Equal(t, int64(0), m[entity.StandardKindFolder])
require.Equal(t, int64(1), m[entity.StandardKindDashboard])
require.Equal(t, int64(1), m[entity.StandardKindAlertRule])
require.Equal(t, int64(1), m[entity.StandardKindLibraryPanel])
t.Cleanup(func() {
guardian.New = origNewGuardian
@@ -470,169 +525,158 @@ 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) {
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
require.NoError(t, err)
featuresFlagOff := featuremgmt.WithFeatures()
serviceWithFlagOff := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardFolderStore: folderStore,
features: featuresFlagOff,
bus: b,
db: db,
registry: make(map[string]folder.RegistryService),
}
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) {
testCases := []struct {
service *Service
featuresFlag *featuremgmt.FeatureManager
prefix string
depth int
forceDelete bool
deletionErr error
dashboardErr error
folderErr error
libPanelParentErr error
libPanelSubErr error
desc string
}{
{
service: serviceWithFlagOn,
featuresFlag: featuresFlagOn,
prefix: "flagon-force",
depth: 3,
forceDelete: true,
dashboardErr: dashboards.ErrFolderNotFound,
folderErr: folder.ErrFolderNotFound,
libPanelParentErr: model.ErrLibraryElementNotFound,
libPanelSubErr: model.ErrLibraryElementNotFound,
desc: "With nested folder feature flag on and force deletion of rules",
},
{
service: serviceWithFlagOn,
featuresFlag: featuresFlagOn,
prefix: "flagon-noforce",
depth: 3,
forceDelete: false,
deletionErr: dashboards.ErrFolderContainsAlertRules,
desc: "With nested folder feature flag on and no force deletion of rules",
},
{
service: serviceWithFlagOff,
featuresFlag: featuresFlagOff,
prefix: "flagoff-force",
depth: 1,
forceDelete: true,
dashboardErr: dashboards.ErrFolderNotFound,
libPanelParentErr: model.ErrLibraryElementNotFound,
desc: "With nested folder feature flag off and force deletion of rules",
},
{
service: serviceWithFlagOff,
featuresFlag: featuresFlagOff,
prefix: "flagoff-noforce",
depth: 1,
forceDelete: false,
deletionErr: dashboards.ErrFolderContainsAlertRules,
desc: "With nested folder feature flag off and no force deletion of rules",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
// CanEditValue is required to create library elements
CanEditValue: true,
})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "with-force", createCmd)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service)
require.NoError(t, err)
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, tc.featuresFlag, tagimpl.ProvideService(db, db.Cfg), quotaService)
require.NoError(t, err)
nestedFolderStore := ProvideStore(db, db.Cfg, tc.featuresFlag)
tc.service.dashboardStore = dashStore
tc.service.store = nestedFolderStore
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service)
require.NoError(t, err)
alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, ac, dashSrv)
require.NoError(t, err)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, 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")
var (
subfolder *folder.Folder
subPanel model.LibraryElementDTO
)
if tc.depth > 1 {
subfolder, err = serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
require.NoError(t, err)
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
libraryElementCmd.FolderID = subfolder.ID
subPanel, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
}
libraryElementCmd.FolderID = parent.ID
parentPanel, err := lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd)
require.NoError(t, err)
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
ForceDeleteRules: true,
ForceDeleteRules: tc.forceDelete,
}
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
require.NoError(t, err)
err = tc.service.Delete(context.Background(), &deleteCmd)
require.ErrorIs(t, err, tc.deletionErr)
for i, uid := range ancestorUIDs {
// dashboard table
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
_, err := tc.service.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
require.ErrorIs(t, err, tc.dashboardErr)
// folder table
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
require.ErrorIs(t, err, folder.ErrFolderNotFound)
_, err = tc.service.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
require.ErrorIs(t, err, tc.folderErr)
}
_, err = lps.LibraryElementService.GetElement(context.Background(), &signedInUser, model.GetLibraryElementCommand{
FolderName: parent.Title,
FolderID: parent.ID,
UID: parentPanel.UID,
})
require.ErrorIs(t, err, tc.libPanelParentErr)
if tc.depth > 1 {
_, err = lps.LibraryElementService.GetElement(context.Background(), &signedInUser, model.GetLibraryElementCommand{
FolderName: subfolder.Title,
FolderID: subfolder.ID,
UID: subPanel.UID,
})
require.ErrorIs(t, err, tc.libPanelSubErr)
}
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) {
featuresFlagOff := featuremgmt.WithFeatures()
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService)
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"),
dashboardStore: dashStore,
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOff,
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})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-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: true,
}
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
require.NoError(t, err)
// dashboard table
_, 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[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.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)
}
})
})
})
}
})
}