mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
@@ -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 := "a.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"
|
||||
|
||||
10
pkg/services/folder/registry.go
Normal file
10
pkg/services/folder/registry.go
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user