From b2af18f129419e67b9d0f78e869db796cee2ab0f Mon Sep 17 00:00:00 2001 From: Selene Date: Tue, 22 Mar 2022 14:36:50 +0100 Subject: [PATCH] Chore: Remove bus from dashboard service (#46829) * Move DeleteDashboard funtion into dashboards store service, remove bus and update tests * Remove bus from folder service and update more tests * Fix mock --- pkg/api/dashboard_permission_test.go | 10 -- pkg/services/dashboards/dashboard.go | 1 + pkg/services/dashboards/database/database.go | 150 +++++++++++++++++- .../database/database_dashboard_test.go | 8 +- .../database/database_provisioning_test.go | 2 +- pkg/services/dashboards/database_mock.go | 14 ++ .../dashboards/manager/dashboard_service.go | 3 +- .../manager/dashboard_service_test.go | 31 +--- .../dashboards/manager/folder_service.go | 3 +- .../dashboards/manager/folder_service_test.go | 9 +- pkg/services/sqlstore/alert.go | 17 -- pkg/services/sqlstore/alert_test.go | 7 +- pkg/services/sqlstore/dashboard.go | 123 -------------- pkg/services/sqlstore/mockstore/mockstore.go | 6 - pkg/services/sqlstore/store.go | 1 - 15 files changed, 183 insertions(+), 202 deletions(-) diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 9df624fb70b..18c9a90e0ca 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -47,12 +47,6 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { }) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) - - getDashboardQueryResult := models.NewDashboard("Dash") - mockSQLStore := mockstore.NewSQLStoreMock() - mockSQLStore.ExpectedDashboard = getDashboardQueryResult - mockSQLStore.ExpectedError = nil - hs.SQLStore = mockSQLStore loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { callGetDashboardPermissions(sc, hs) @@ -96,10 +90,6 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { }, }) - mockSQLStore := mockstore.NewSQLStoreMock() - mockSQLStore.ExpectedDashboard = models.NewDashboard("Dash") - hs.SQLStore = mockSQLStore - loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { callGetDashboardPermissions(sc, hs) diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index c72ab13d295..93716c52713 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -51,6 +51,7 @@ type Store interface { UnprovisionDashboard(ctx context.Context, id int64) error // GetDashboardsByPluginID retrieves dashboards identified by plugin. GetDashboardsByPluginID(ctx context.Context, query *models.GetDashboardsByPluginIdQuery) error + DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error FolderStore } diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index bbca893049f..a59e04b2e8c 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -4,11 +4,14 @@ import ( "context" "errors" "fmt" + "strconv" "time" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/util" @@ -223,7 +226,7 @@ func (d *DashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts [] return err } - if err := deleteMissingAlerts(existingAlerts, alerts, sess, d.log); err != nil { + if err := d.deleteMissingAlerts(existingAlerts, alerts, sess); err != nil { return err } @@ -255,7 +258,7 @@ func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context } for _, deleteDashCommand := range result { - err := d.sqlStore.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId}) + err := d.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId}) if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { return err } @@ -613,7 +616,7 @@ func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess * return nil } -func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *sqlstore.DBSession, log log.Logger) error { +func (d *DashboardStore) deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *sqlstore.DBSession) error { for _, missingAlert := range alerts { missing := true @@ -625,7 +628,7 @@ func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, } if missing { - if err := deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess, log); err != nil { + if err := d.deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess); err != nil { // No use trying to delete more, since we're in a transaction and it will be // rolled back on error. return err @@ -636,8 +639,8 @@ func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, return nil } -func deleteAlertByIdInternal(alertId int64, reason string, sess *sqlstore.DBSession, log log.Logger) error { - log.Debug("Deleting alert", "id", alertId, "reason", reason) +func (d *DashboardStore) deleteAlertByIdInternal(alertId int64, reason string, sess *sqlstore.DBSession) error { + d.log.Debug("Deleting alert", "id", alertId, "reason", reason) if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", alertId); err != nil { return err @@ -690,3 +693,138 @@ func (d *DashboardStore) GetDashboardsByPluginID(ctx context.Context, query *mod return err }) } + +func (d *DashboardStore) DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error { + return d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + return d.deleteDashboard(cmd, sess) + }) +} + +func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, sess *sqlstore.DBSession) error { + dashboard := models.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId} + has, err := sess.Get(&dashboard) + if err != nil { + return err + } else if !has { + return models.ErrDashboardNotFound + } + + deletes := []string{ + "DELETE FROM dashboard_tag WHERE dashboard_id = ? ", + "DELETE FROM star WHERE dashboard_id = ? ", + "DELETE FROM dashboard WHERE id = ?", + "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", + "DELETE FROM dashboard_version WHERE dashboard_id = ?", + "DELETE FROM annotation WHERE dashboard_id = ?", + "DELETE FROM dashboard_provisioning WHERE dashboard_id = ?", + "DELETE FROM dashboard_acl WHERE dashboard_id = ?", + } + + if dashboard.IsFolder { + deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ?") + + var dashIds []struct { + Id int64 + } + err := sess.SQL("SELECT id FROM dashboard WHERE folder_id = ?", dashboard.Id).Find(&dashIds) + if 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.GetResourceScope(strconv.FormatInt(dashboard.Id, 10))) + 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.Scope("dashboards", "id", strconv.FormatInt(dash.Id, 10))) + 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 { + return err + } + if exists { + if !cmd.ForceDeleteFolderRules { + return fmt.Errorf("folder cannot be deleted: %w", models.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.Scope("dashboards", "id", strconv.FormatInt(dashboard.Id, 10))) + if err != nil { + return err + } + } + + if err := d.deleteAlertDefinition(dashboard.Id, sess); err != nil { + return err + } + + for _, sql := range deletes { + _, err := sess.Exec(sql, dashboard.Id) + if err != nil { + return err + } + } + + return nil +} + +func (d *DashboardStore) deleteAlertDefinition(dashboardId int64, sess *sqlstore.DBSession) error { + alerts := make([]*models.Alert, 0) + if err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts); err != nil { + return err + } + + for _, alert := range alerts { + if err := d.deleteAlertByIdInternal(alert.Id, "Dashboard deleted", sess); err != nil { + // If we return an error, the current transaction gets rolled back, so no use + // trying to delete more + return err + } + } + + return nil +} diff --git a/pkg/services/dashboards/database/database_dashboard_test.go b/pkg/services/dashboards/database/database_dashboard_test.go index 90518f6a2f9..f640240fd2b 100644 --- a/pkg/services/dashboards/database/database_dashboard_test.go +++ b/pkg/services/dashboards/database/database_dashboard_test.go @@ -118,7 +118,7 @@ func TestDashboardDataAccess(t *testing.T) { setup() dash := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, false, "delete this") - err := sqlStore.DeleteDashboard(context.Background(), &models.DeleteDashboardCommand{ + err := dashboardStore.DeleteDashboard(context.Background(), &models.DeleteDashboardCommand{ Id: dash.Id, OrgId: 1, }) @@ -193,21 +193,21 @@ func TestDashboardDataAccess(t *testing.T) { emptyFolder := insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod", "webapp") deleteCmd := &models.DeleteDashboardCommand{Id: emptyFolder.Id} - err := sqlStore.DeleteDashboard(context.Background(), deleteCmd) + err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd) require.NoError(t, err) }) t.Run("Should be not able to delete a dashboard if force delete rules is disabled", func(t *testing.T) { setup() deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false} - err := sqlStore.DeleteDashboard(context.Background(), deleteCmd) + err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd) require.True(t, errors.Is(err, models.ErrFolderContainsAlertRules)) }) t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) { setup() deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: true} - err := sqlStore.DeleteDashboard(context.Background(), deleteCmd) + err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd) require.NoError(t, err) query := models.FindPersistedDashboardsQuery{ diff --git a/pkg/services/dashboards/database/database_provisioning_test.go b/pkg/services/dashboards/database/database_provisioning_test.go index 968252d8ce3..ab7104ebcd3 100644 --- a/pkg/services/dashboards/database/database_provisioning_test.go +++ b/pkg/services/dashboards/database/database_provisioning_test.go @@ -120,7 +120,7 @@ func TestDashboardProvisioningTest(t *testing.T) { OrgId: 1, } - require.Nil(t, sqlStore.DeleteDashboard(context.Background(), deleteCmd)) + require.Nil(t, dashboardStore.DeleteDashboard(context.Background(), deleteCmd)) data, err := dashboardStore.GetProvisionedDataByDashboardID(dash.Id) require.Nil(t, err) diff --git a/pkg/services/dashboards/database_mock.go b/pkg/services/dashboards/database_mock.go index 85bf38af163..5e5e3d53d1b 100644 --- a/pkg/services/dashboards/database_mock.go +++ b/pkg/services/dashboards/database_mock.go @@ -14,6 +14,20 @@ type FakeDashboardStore struct { mock.Mock } +// DeleteDashboard provides a mock function with given fields: ctx, cmd +func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error { + ret := _m.Called(ctx, cmd) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *models.DeleteDashboardCommand) error); ok { + r0 = rf(ctx, cmd) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // DeleteOrphanedProvisionedDashboards provides a mock function with given fields: ctx, cmd func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error { ret := _m.Called(ctx, cmd) diff --git a/pkg/services/dashboards/manager/dashboard_service.go b/pkg/services/dashboards/manager/dashboard_service.go index 1fed5acfc68..95273db95bf 100644 --- a/pkg/services/dashboards/manager/dashboard_service.go +++ b/pkg/services/dashboards/manager/dashboard_service.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -408,7 +407,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId } } cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId} - return bus.Dispatch(ctx, cmd) + return dr.dashboardStore.DeleteDashboard(ctx, cmd) } func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *m.SaveDashboardDTO) ( diff --git a/pkg/services/dashboards/manager/dashboard_service_test.go b/pkg/services/dashboards/manager/dashboard_service_test.go index 4f8570a3e1d..7e303d70471 100644 --- a/pkg/services/dashboards/manager/dashboard_service_test.go +++ b/pkg/services/dashboards/manager/dashboard_service_test.go @@ -176,35 +176,34 @@ func TestDashboardService(t *testing.T) { t.Run("Given provisioned dashboard", func(t *testing.T) { t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) { - result := setupDeleteHandlers(t) + args := &models.DeleteDashboardCommand{OrgId: 1, Id: 1} + fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() err := service.DeleteProvisionedDashboard(context.Background(), 1, 1) require.NoError(t, err) - require.True(t, result.deleteWasCalled) }) - t.Run("DeleteDashboard should fail to delete it", func(t *testing.T) { + t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) { fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once() - result := setupDeleteHandlers(t) err := service.DeleteDashboard(context.Background(), 1, 1) require.Equal(t, err, models.ErrDashboardCannotDeleteProvisionedDashboard) - require.False(t, result.deleteWasCalled) }) }) t.Run("Given non provisioned dashboard", func(t *testing.T) { - result := setupDeleteHandlers(t) - t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) { + t.Run("DeleteProvisionedDashboard should delete the dashboard", func(t *testing.T) { + args := &models.DeleteDashboardCommand{OrgId: 1, Id: 1, ForceDeleteFolderRules: false} + fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() err := service.DeleteProvisionedDashboard(context.Background(), 1, 1) require.NoError(t, err) - require.True(t, result.deleteWasCalled) }) t.Run("DeleteDashboard should delete it", func(t *testing.T) { + args := &models.DeleteDashboardCommand{OrgId: 1, Id: 1} + fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(nil, nil).Once() err := service.DeleteDashboard(context.Background(), 1, 1) require.NoError(t, err) - require.True(t, result.deleteWasCalled) }) }) }) @@ -213,17 +212,3 @@ func TestDashboardService(t *testing.T) { type Result struct { deleteWasCalled bool } - -func setupDeleteHandlers(t *testing.T) *Result { - t.Helper() - - result := &Result{} - bus.AddHandler("test", func(ctx context.Context, cmd *models.DeleteDashboardCommand) error { - require.Equal(t, cmd.Id, int64(1)) - require.Equal(t, cmd.OrgId, int64(1)) - result.deleteWasCalled = true - return nil - }) - - return result -} diff --git a/pkg/services/dashboards/manager/folder_service.go b/pkg/services/dashboards/manager/folder_service.go index 787a0be04c1..221ec1196bf 100644 --- a/pkg/services/dashboards/manager/folder_service.go +++ b/pkg/services/dashboards/manager/folder_service.go @@ -233,7 +233,8 @@ func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.Signe } deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules} - if err := bus.Dispatch(ctx, &deleteCmd); err != nil { + + if err := f.dashboardStore.DeleteDashboard(ctx, &deleteCmd); err != nil { return nil, toFolderError(err) } diff --git a/pkg/services/dashboards/manager/folder_service_test.go b/pkg/services/dashboards/manager/folder_service_test.go index 277eab8377d..e5e1c7bc8e6 100644 --- a/pkg/services/dashboards/manager/folder_service_test.go +++ b/pkg/services/dashboards/manager/folder_service_test.go @@ -184,12 +184,11 @@ func TestFolderService(t *testing.T) { f.Id = rand.Int63() f.Uid = util.GenerateShortUID() store.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil) + var actualCmd *models.DeleteDashboardCommand - bus.AddHandler("test", func(ctx context.Context, cmd *models.DeleteDashboardCommand) error { - actualCmd = cmd - return nil - }) - defer bus.ClearBusHandlers() + store.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + actualCmd = args.Get(1).(*models.DeleteDashboardCommand) + }).Return(nil).Once() expectedForceDeleteRules := rand.Int63()%2 == 0 _, err := service.DeleteFolder(context.Background(), user, orgID, f.Uid, expectedForceDeleteRules) diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index 98902413698..7e79cb6d24c 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -155,23 +155,6 @@ func (ss *SQLStore) HandleAlertsQuery(ctx context.Context, query *models.GetAler }) } -func deleteAlertDefinition(dashboardId int64, sess *DBSession) error { - alerts := make([]*models.Alert, 0) - if err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts); err != nil { - return err - } - - for _, alert := range alerts { - if err := deleteAlertByIdInternal(alert.Id, "Dashboard deleted", sess); err != nil { - // If we return an error, the current transaction gets rolled back, so no use - // trying to delete more - return err - } - } - - return nil -} - func (ss *SQLStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error { return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { existingAlerts, err := GetAlertsByDashboardId2(dashID, sess) diff --git a/pkg/services/sqlstore/alert_test.go b/pkg/services/sqlstore/alert_test.go index 9430fa63819..7deb46c8d31 100644 --- a/pkg/services/sqlstore/alert_test.go +++ b/pkg/services/sqlstore/alert_test.go @@ -245,9 +245,10 @@ func TestAlertingDataAccess(t *testing.T) { err := sqlStore.SaveAlerts(context.Background(), testDash.Id, items) require.Nil(t, err) - err = sqlStore.DeleteDashboard(context.Background(), &models.DeleteDashboardCommand{ - OrgId: 1, - Id: testDash.Id, + err = sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error { + dash := models.Dashboard{Id: testDash.Id, OrgId: 1} + _, err := sess.Delete(dash) + return err }) require.Nil(t, err) diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index a1f7fe210bb..c53c577a886 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -2,16 +2,12 @@ package sqlstore import ( "context" - "fmt" - "strconv" "strings" "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" - ac "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" @@ -35,7 +31,6 @@ func (ss *SQLStore) addDashboardQueryAndCommandHandlers() { bus.AddHandler("sql", ss.GetDashboardUIDById) bus.AddHandler("sql", ss.GetDashboardTags) bus.AddHandler("sql", ss.SearchDashboards) - bus.AddHandler("sql", ss.DeleteDashboard) bus.AddHandler("sql", ss.GetDashboards) bus.AddHandler("sql", ss.HasEditPermissionInFolders) bus.AddHandler("sql", ss.GetDashboardPermissionsForUser) @@ -239,124 +234,6 @@ func (ss *SQLStore) GetDashboardTags(ctx context.Context, query *models.GetDashb }) } -func (ss *SQLStore) DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - return deleteDashboard(cmd, sess) - }) -} - -func deleteDashboard(cmd *models.DeleteDashboardCommand, sess *DBSession) error { - dashboard := models.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId} - has, err := sess.Get(&dashboard) - if err != nil { - return err - } else if !has { - return models.ErrDashboardNotFound - } - - deletes := []string{ - "DELETE FROM dashboard_tag WHERE dashboard_id = ? ", - "DELETE FROM star WHERE dashboard_id = ? ", - "DELETE FROM dashboard WHERE id = ?", - "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", - "DELETE FROM dashboard_version WHERE dashboard_id = ?", - "DELETE FROM annotation WHERE dashboard_id = ?", - "DELETE FROM dashboard_provisioning WHERE dashboard_id = ?", - "DELETE FROM dashboard_acl WHERE dashboard_id = ?", - } - - if dashboard.IsFolder { - deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ?") - - dashIds := []struct { - Id int64 - }{} - err := sess.SQL("SELECT id FROM dashboard WHERE folder_id = ?", dashboard.Id).Find(&dashIds) - if err != nil { - return err - } - - for _, id := range dashIds { - if err := 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.GetResourceScope(strconv.FormatInt(dashboard.Id, 10))) - 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.Scope("dashboards", "id", strconv.FormatInt(dash.Id, 10))) - 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 { - return err - } - if exists { - if !cmd.ForceDeleteFolderRules { - return fmt.Errorf("folder cannot be deleted: %w", models.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.Scope("dashboards", "id", strconv.FormatInt(dashboard.Id, 10))) - if err != nil { - return err - } - } - - if err := deleteAlertDefinition(dashboard.Id, sess); err != nil { - return err - } - - for _, sql := range deletes { - _, err := sess.Exec(sql, dashboard.Id) - if err != nil { - return err - } - } - - return nil -} - func (ss *SQLStore) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { if len(query.DashboardIds) == 0 { diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index db6b4278211..a95e5435172 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -482,12 +482,6 @@ func (m *SQLStoreMock) GetDashboardTags(ctx context.Context, query *models.GetDa return nil // TODO: Implement } -func (m *SQLStoreMock) DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error { - cmd.Id = m.ExpectedDashboard.Id - cmd.OrgId = m.ExpectedDashboard.OrgId - return m.ExpectedError -} - func (m *SQLStoreMock) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error { return m.ExpectedError } diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 2256495cf38..05e4a8aafd2 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -105,7 +105,6 @@ type Store interface { GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error - DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error