NestedFolders: Add folder service registry with dashboard service implementation (#65033)

* Delete folders, dashboards with registry service
Co-authored-by: Serge Zaitsev <hello@zserge.com>
* Update signature of ProvideDashboardServiceImpl
* Regenerate mockery file
* Add test for DeleteInFolder
* Add test for DeleteDashboardsInFolder
* Delete child dashboard associations via registry
* Add validation of folder uid and org id

---------

Co-authored-by: Serge Zaitsev <hello@zserge.com>
pull/66552/head
Arati R 2 years ago committed by GitHub
parent 4abe0249ba
commit cab3ba519a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      pkg/api/dashboard_permission_test.go
  2. 32
      pkg/api/dashboard_test.go
  3. 12
      pkg/api/folder_permission_test.go
  4. 1
      pkg/services/dashboards/dashboard.go
  5. 149
      pkg/services/dashboards/database/database.go
  6. 19
      pkg/services/dashboards/database/database_test.go
  7. 5
      pkg/services/dashboards/models.go
  8. 15
      pkg/services/dashboards/service/dashboard_service.go
  9. 15
      pkg/services/dashboards/service/dashboard_service_integration_test.go
  10. 7
      pkg/services/dashboards/service/dashboard_service_test.go
  11. 810
      pkg/services/dashboards/store_mock.go
  12. 42
      pkg/services/folder/folderimpl/folder.go
  13. 4
      pkg/services/folder/foldertest/foldertest.go
  14. 1
      pkg/services/folder/model.go
  15. 10
      pkg/services/folder/registry.go
  16. 1
      pkg/services/folder/service.go
  17. 6
      pkg/services/libraryelements/libraryelements_test.go
  18. 3
      pkg/services/librarypanels/librarypanels_test.go
  19. 5
      pkg/services/ngalert/store/testing.go

@ -42,15 +42,17 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), settings, dashboardStore, foldertest.NewFakeFolderStore(t), mockSQLStore, featuremgmt.WithFeatures())
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac,
folderSvc,
)
require.NoError(t, err)
hs := &HTTPServer{
Cfg: settings,
SQLStore: mockSQLStore,
Features: features,
DashboardService: dashboardservice.ProvideDashboardServiceImpl(
settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac,
folderSvc,
),
AccessControl: accesscontrolmock.New().WithDisabled(),
Cfg: settings,
SQLStore: mockSQLStore,
Features: features,
DashboardService: dashboardService,
AccessControl: accesscontrolmock.New().WithDisabled(),
}
t.Run("Given user has no admin permissions", func(t *testing.T) {

@ -1096,26 +1096,30 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
cfg, dashboardStore, folderStore, db.InitTestDB(t), featuremgmt.WithFeatures())
if dashboardService == nil {
dashboardService = service.ProvideDashboardServiceImpl(
dashboardService, err = service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
ac, folderSvc,
)
require.NoError(t, err)
}
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
ac, folderSvc,
)
require.NoError(t, err)
hs := &HTTPServer{
Cfg: cfg,
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
SQLStore: sc.sqlStore,
ProvisioningService: provisioningService,
AccessControl: accesscontrolmock.New(),
dashboardProvisioningService: service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
ac, folderSvc,
),
DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
Cfg: cfg,
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
SQLStore: sc.sqlStore,
ProvisioningService: provisioningService,
AccessControl: accesscontrolmock.New(),
dashboardProvisioningService: dashboardProvisioningService,
DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
}
hs.callGetDashboard(sc)

@ -37,6 +37,11 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
ac := accesscontrolmock.New()
folderPermissions := accesscontrolmock.NewMockedPermissionsService()
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
dashboardService, err := service.ProvideDashboardServiceImpl(
settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac,
folderService,
)
require.NoError(t, err)
hs := &HTTPServer{
Cfg: settings,
@ -44,11 +49,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
folderService: folderService,
folderPermissionsService: folderPermissions,
dashboardPermissionsService: dashboardPermissions,
DashboardService: service.ProvideDashboardServiceImpl(
settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac,
folderService,
),
AccessControl: accesscontrolmock.New().WithDisabled(),
DashboardService: dashboardService,
AccessControl: accesscontrolmock.New().WithDisabled(),
}
t.Run("Given folder not exists", func(t *testing.T) {

@ -84,4 +84,5 @@ type Store interface {
// CountDashboardsInFolder returns the number of dashboards associated with
// the given parent folder ID.
CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error)
DeleteDashboardsInFolder(ctx context.Context, request *DeleteDashboardsInFolderRequest) error
}

@ -713,75 +713,19 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
if dashboard.IsFolder {
deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ?")
var dashIds []struct {
Id int64
Uid string
}
err := sess.SQL("SELECT id, uid FROM dashboard WHERE folder_id = ?", dashboard.ID).Find(&dashIds)
if err != nil {
if err := d.deleteChildrenDashboardAssociations(sess, dashboard); err != nil {
return err
}
for _, id := range dashIds {
if err := d.deleteAlertDefinition(id.Id, sess); err != nil {
return err
}
}
// remove all access control permission with folder scope
_, err = sess.Exec("DELETE FROM permission WHERE scope = ?", dashboards.ScopeFoldersProvider.GetResourceScopeUID(dashboard.UID))
if err != nil {
return err
}
for _, dash := range dashIds {
// remove all access control permission with child dashboard scopes
_, err = sess.Exec("DELETE FROM permission WHERE scope = ?", ac.GetResourceScopeUID("dashboards", dash.Uid))
if err != nil {
return err
}
}
if len(dashIds) > 0 {
childrenDeletes := []string{
"DELETE FROM dashboard_tag WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM star WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_version WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM annotation WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_provisioning WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_acl WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
}
for _, sql := range childrenDeletes {
_, err := sess.Exec(sql, dashboard.OrgID, dashboard.ID)
if err != nil {
return err
}
}
}
var existingRuleID int64
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", dashboard.ID).Cols("id").Get(&existingRuleID)
if err != nil {
if err := deleteFolderAlertRules(sess, dashboard, cmd.ForceDeleteFolderRules); err != nil {
return err
}
if exists {
if !cmd.ForceDeleteFolderRules {
return fmt.Errorf("folder cannot be deleted: %w", dashboards.ErrFolderContainsAlertRules)
}
// Delete all rules under this folder.
deleteNGAlertsByFolder := []string{
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
}
for _, sql := range deleteNGAlertsByFolder {
_, err := sess.Exec(sql, dashboard.ID)
if err != nil {
return err
}
}
}
} else {
_, err = sess.Exec("DELETE FROM permission WHERE scope = ?", ac.GetResourceScopeUID("dashboards", dashboard.UID))
if err != nil {
@ -809,6 +753,74 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
return nil
}
func (d *dashboardStore) deleteChildrenDashboardAssociations(sess *db.Session, dashboard dashboards.Dashboard) error {
var dashIds []struct {
Id int64
Uid string
}
err := sess.SQL("SELECT id, uid FROM dashboard WHERE folder_id = ?", dashboard.ID).Find(&dashIds)
if err != nil {
return err
}
if len(dashIds) > 0 {
for _, dash := range dashIds {
if err := d.deleteAlertDefinition(dash.Id, sess); err != nil {
return err
}
// remove all access control permission with child dashboard scopes
_, err = sess.Exec("DELETE FROM permission WHERE scope = ?", ac.GetResourceScopeUID("dashboards", dash.Uid))
if err != nil {
return err
}
}
childrenDeletes := []string{
"DELETE FROM dashboard_tag WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM star WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_version WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM annotation WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_provisioning WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_acl WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
}
for _, sql := range childrenDeletes {
_, err := sess.Exec(sql, dashboard.OrgID, dashboard.ID)
if err != nil {
return err
}
}
}
return nil
}
func deleteFolderAlertRules(sess *db.Session, dashboard dashboards.Dashboard, forceDeleteFolderAlertRules bool) error {
var existingRuleID int64
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", dashboard.ID).Cols("id").Get(&existingRuleID)
if err != nil {
return err
}
if exists {
if !forceDeleteFolderAlertRules {
return fmt.Errorf("folder cannot be deleted: %w", dashboards.ErrFolderContainsAlertRules)
}
// Delete all rules under this folder.
deleteNGAlertsByFolder := []string{
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
}
for _, sql := range deleteNGAlertsByFolder {
_, err := sess.Exec(sql, dashboard.ID)
if err != nil {
return err
}
}
}
return nil
}
func createEntityEvent(dashboard *dashboards.Dashboard, eventType store.EntityEventType) *store.EntityEvent {
var entityEvent *store.EntityEvent
if dashboard.IsFolder {
@ -1045,6 +1057,27 @@ func (d *dashboardStore) CountDashboardsInFolder(
return count, err
}
func (d *dashboardStore) DeleteDashboardsInFolder(
ctx context.Context, req *dashboards.DeleteDashboardsInFolderRequest) error {
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
dashboard := dashboards.Dashboard{OrgID: req.OrgID}
has, err := sess.Where("uid = ? AND org_id = ?", req.FolderUID, req.OrgID).Get(&dashboard)
if err != nil {
return err
}
if !has {
return dashboards.ErrFolderNotFound
}
if err := d.deleteChildrenDashboardAssociations(sess, dashboard); err != nil {
return err
}
_, err = sess.Where("folder_id = ? AND org_id = ? AND is_folder = ?", dashboard.ID, dashboard.OrgID, false).Delete(&dashboards.Dashboard{})
return err
})
}
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
limits := &quota.Map{}

@ -491,6 +491,25 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.NoError(t, err)
require.Equal(t, int64(2), count)
})
t.Run("Can delete dashboards in folder", func(t *testing.T) {
setup()
folder := insertTestDashboard(t, dashboardStore, "dash folder", 1, 0, true, "prod", "webapp")
_ = insertTestDashboard(t, dashboardStore, "delete me 1", 1, folder.ID, false, "delete this 1")
_ = insertTestDashboard(t, dashboardStore, "delete me 2", 1, folder.ID, false, "delete this 2")
err := dashboardStore.DeleteDashboardsInFolder(
context.Background(),
&dashboards.DeleteDashboardsInFolderRequest{
FolderUID: folder.UID,
OrgID: 1,
})
require.NoError(t, err)
count, err := dashboardStore.CountDashboardsInFolder(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderID: 2, OrgID: 1})
require.NoError(t, err)
require.Equal(t, count, int64(0))
})
}
func TestIntegrationDashboardDataAccessGivenPluginWithImportedDashboards(t *testing.T) {

@ -340,6 +340,11 @@ func FromDashboard(dash *Dashboard) *folder.Folder {
}
}
type DeleteDashboardsInFolderRequest struct {
FolderUID string
OrgID int64
}
//
// DASHBOARD ACL
//

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@ -55,7 +56,7 @@ func ProvideDashboardServiceImpl(
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl,
folderSvc folder.Service,
) *DashboardServiceImpl {
) (*DashboardServiceImpl, error) {
dashSvc := &DashboardServiceImpl{
cfg: cfg,
log: log.New("dashboard-service"),
@ -72,7 +73,11 @@ func ProvideDashboardServiceImpl(
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, dashSvc, folderSvc))
return dashSvc
if err := folderSvc.RegisterService(dashSvc); err != nil {
return nil, err
}
return dashSvc, nil
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) {
@ -641,3 +646,9 @@ func (dr DashboardServiceImpl) CountDashboardsInFolder(ctx context.Context, quer
return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.ID, OrgID: u.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) Kind() string { return entity.StandardKindDashboard }

@ -828,7 +828,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := ProvideDashboardServiceImpl(
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
@ -836,6 +836,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
accesscontrolmock.New(),
foldertest.NewFakeService(),
)
require.NoError(t, err)
guardian.InitLegacyGuardian(cfg, sqlStore, service, &teamtest.FakeService{})
savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore)
@ -889,7 +890,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := ProvideDashboardServiceImpl(
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
@ -897,6 +898,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
accesscontrolmock.New(),
foldertest.NewFakeService(),
)
require.NoError(t, err)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
@ -912,7 +914,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := ProvideDashboardServiceImpl(
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
@ -920,6 +922,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
accesscontrolmock.New(),
foldertest.NewFakeService(),
)
require.NoError(t, err)
_, err = service.SaveDashboard(context.Background(), &dto, false)
return err
}
@ -953,7 +956,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := ProvideDashboardServiceImpl(
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
@ -961,6 +964,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
accesscontrolmock.New(),
foldertest.NewFakeService(),
)
require.NoError(t, err)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
@ -995,7 +999,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := ProvideDashboardServiceImpl(
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
@ -1003,6 +1007,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
accesscontrolmock.New(),
foldertest.NewFakeService(),
)
require.NoError(t, err)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)

@ -240,6 +240,13 @@ func TestDashboardService(t *testing.T) {
require.NoError(t, err)
require.Equal(t, int64(3), count)
})
t.Run("Delete dashboards in folder", func(t *testing.T) {
args := &dashboards.DeleteDashboardsInFolderRequest{OrgID: 1, FolderUID: "uid"}
fakeStore.On("DeleteDashboardsInFolder", mock.Anything, args).Return(nil).Once()
err := service.DeleteInFolder(context.Background(), 1, "uid")
require.NoError(t, err)
})
})
t.Run("Delete user by acl", func(t *testing.T) {

File diff suppressed because it is too large Load Diff

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/grafana/grafana/pkg/bus"
@ -36,6 +37,9 @@ type Service struct {
// bus is currently used to publish event in case of title change
bus bus.Bus
mutex sync.RWMutex
registry map[string]folder.RegistryService
}
func ProvideService(
@ -58,6 +62,7 @@ func ProvideService(
accessControl: ac,
bus: bus,
db: db,
registry: make(map[string]folder.RegistryService),
}
if features.IsEnabled(featuremgmt.FlagNestedFolders) {
srv.DBMigration(db)
@ -437,6 +442,12 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
if cmd.SignedInUser == nil {
return folder.ErrBadRequest.Errorf("missing signed in user")
}
if cmd.UID == "" {
return folder.ErrBadRequest.Errorf("missing UID")
}
if cmd.OrgID < 1 {
return folder.ErrBadRequest.Errorf("invalid orgID")
}
result := []string{cmd.UID}
err := s.db.InTransaction(ctx, func(ctx context.Context) error {
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
@ -466,6 +477,11 @@ 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
}
err = s.legacyDelete(ctx, cmd, dashFolder)
if err != nil {
return err
@ -477,6 +493,15 @@ 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 {
for _, v := range s.registry {
if err := v.DeleteInFolder(ctx, orgID, UID); err != nil {
return err
}
}
return nil
}
func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderCommand, dashFolder *folder.Folder) error {
deleteCmd := dashboards.DeleteDashboardCommand{OrgID: cmd.OrgID, ID: dashFolder.ID, ForceDeleteFolderRules: cmd.ForceDeleteRules}
@ -588,7 +613,8 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
}
result = append(result, subfolders...)
}
logger.Info("deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID)
logger.Info("deleting folder and its contents", "org_id", cmd.OrgID, "uid", cmd.UID)
err = s.store.Delete(ctx, cmd.UID, cmd.OrgID)
if err != nil {
logger.Info("failed deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID, "err", err)
@ -794,3 +820,17 @@ func toFolderError(err error) error {
return err
}
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
}

@ -45,3 +45,7 @@ func (s *FakeService) MakeUserAdmin(ctx context.Context, orgID int64, userID, fo
func (s *FakeService) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
return s.ExpectedFolder, s.ExpectedError
}
func (s *FakeService) RegisterService(service folder.RegistryService) error {
return s.ExpectedError
}

@ -13,6 +13,7 @@ var ErrDatabaseError = errutil.NewBase(errutil.StatusInternal, "folder.database-
var ErrInternal = errutil.NewBase(errutil.StatusInternal, "folder.internal")
var ErrFolderTooDeep = errutil.NewBase(errutil.StatusInternal, "folder.too-deep")
var ErrCircularReference = errutil.NewBase(errutil.StatusBadRequest, "folder.circular-reference", errutil.WithPublicMessage("Circular reference detected"))
var ErrTargetRegistrySrvConflict = errutil.NewBase(errutil.StatusInternal, "folder.target-registry-srv-conflict")
const (
GeneralFolderUID = "general"

@ -0,0 +1,10 @@
package folder
import (
"context"
)
type RegistryService interface {
DeleteInFolder(ctx context.Context, orgID int64, UID string) error
Kind() string
}

@ -25,6 +25,7 @@ type Service interface {
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
// Move changes a folder's parent folder to the requested new parent.
Move(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error)
RegisterService(service RegistryService) error
}
// FolderStore is a folder store.

@ -295,11 +295,12 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := dashboardservice.ProvideDashboardServiceImpl(
service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, dashAlertExtractor,
features, folderPermissions, dashboardPermissions, ac,
foldertest.NewFakeService(),
)
require.NoError(t, err)
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)
@ -441,11 +442,12 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashboardService := dashboardservice.ProvideDashboardServiceImpl(
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
sqlStore.Cfg, dashboardStore, folderStore, nil,
features, folderPermissions, dashboardPermissions, ac,
foldertest.NewFakeService(),
)
require.NoError(t, err)
guardian.InitLegacyGuardian(sqlStore.Cfg, sqlStore, dashboardService, &teamtest.FakeService{})
service := LibraryElementService{
Cfg: sqlStore.Cfg,

@ -707,11 +707,12 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
ac := acmock.New()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service := dashboardservice.ProvideDashboardServiceImpl(
service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, dashAlertService,
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), ac,
foldertest.NewFakeService(),
)
require.NoError(t, err)
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)

@ -150,12 +150,13 @@ func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folde
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
dashboardService := dashboardservice.ProvideDashboardServiceImpl(
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

Loading…
Cancel
Save