Dashboard Restore: Remove experimental functionality under feature flag dashboardRestore for now - this will be reworked (#103204)
This commit is contained in:
committed by
GitHub
parent
db1f1c5df9
commit
4918d8720c
@@ -2,7 +2,6 @@ package dashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -32,11 +31,7 @@ type DashboardService interface {
|
||||
CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) (int64, error)
|
||||
GetAllDashboards(ctx context.Context) ([]*Dashboard, error)
|
||||
GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error)
|
||||
SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error
|
||||
RestoreDashboard(ctx context.Context, dashboard *Dashboard, user identity.Requester, optionalFolderUID string) error
|
||||
CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error
|
||||
CleanUpDeletedDashboards(ctx context.Context) (int64, error)
|
||||
GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error)
|
||||
CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error)
|
||||
}
|
||||
|
||||
@@ -98,9 +93,4 @@ type Store interface {
|
||||
|
||||
GetAllDashboards(ctx context.Context) ([]*Dashboard, error)
|
||||
GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error)
|
||||
GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*Dashboard, error)
|
||||
SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error
|
||||
SoftDeleteDashboardsInFolders(ctx context.Context, orgID int64, folderUids []string) error
|
||||
RestoreDashboard(ctx context.Context, orgID int64, dashboardUid string, folder *folder.Folder) error
|
||||
GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error)
|
||||
}
|
||||
|
||||
@@ -46,34 +46,6 @@ func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, d
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CleanUpDeletedDashboards provides a mock function with given fields: ctx
|
||||
func (_m *FakeDashboardService) CleanUpDeletedDashboards(ctx context.Context) (int64, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CleanUpDeletedDashboards")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) int64); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountDashboardsInOrg provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
@@ -376,36 +348,6 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDas
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSoftDeletedDashboard provides a mock function with given fields: ctx, orgID, uid
|
||||
func (_m *FakeDashboardService) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, orgID, uid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSoftDeletedDashboard")
|
||||
}
|
||||
|
||||
var r0 *Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*Dashboard, error)); ok {
|
||||
return rf(ctx, orgID, uid)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *Dashboard); ok {
|
||||
r0 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ImportDashboard provides a mock function with given fields: ctx, dto
|
||||
func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, dto)
|
||||
@@ -436,24 +378,6 @@ func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDa
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RestoreDashboard provides a mock function with given fields: ctx, dashboard, user, optionalFolderUID
|
||||
func (_m *FakeDashboardService) RestoreDashboard(ctx context.Context, dashboard *Dashboard, user identity.Requester, optionalFolderUID string) error {
|
||||
ret := _m.Called(ctx, dashboard, user, optionalFolderUID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RestoreDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *Dashboard, identity.Requester, string) error); ok {
|
||||
r0 = rf(ctx, dashboard, user, optionalFolderUID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SaveDashboard provides a mock function with given fields: ctx, dto, allowUiUpdate
|
||||
func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, dto, allowUiUpdate)
|
||||
@@ -514,24 +438,6 @@ func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *Fin
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SoftDeleteDashboard provides a mock function with given fields: ctx, orgID, dashboardUid
|
||||
func (_m *FakeDashboardService) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error {
|
||||
ret := _m.Called(ctx, orgID, dashboardUid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SoftDeleteDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok {
|
||||
r0 = rf(ctx, orgID, dashboardUid)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, orgId
|
||||
func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error {
|
||||
ret := _m.Called(ctx, dashboardUID, orgId)
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
@@ -564,83 +563,6 @@ func (d *dashboardStore) GetDashboardsByPluginID(ctx context.Context, query *das
|
||||
}
|
||||
return dashboards, nil
|
||||
}
|
||||
func (d *dashboardStore) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.GetSoftDeletedDashboard")
|
||||
defer span.End()
|
||||
|
||||
if orgID == 0 || uid == "" {
|
||||
return nil, dashboards.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
var queryResult *dashboards.Dashboard
|
||||
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
dashboard := dashboards.Dashboard{OrgID: orgID, UID: uid}
|
||||
has, err := sess.Where("deleted IS NOT NULL").Get(&dashboard)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
queryResult = &dashboard
|
||||
return nil
|
||||
})
|
||||
|
||||
return queryResult, err
|
||||
}
|
||||
|
||||
func (d *dashboardStore) RestoreDashboard(ctx context.Context, orgID int64, dashboardUID string, folder *folder.Folder) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.RestoreDashboard")
|
||||
defer span.End()
|
||||
|
||||
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
if folder == nil || folder.UID == "" {
|
||||
_, err := sess.Exec("UPDATE dashboard SET deleted=NULL, folder_id=0, folder_uid=NULL WHERE org_id=? AND uid=?", orgID, dashboardUID)
|
||||
return err
|
||||
}
|
||||
// nolint:staticcheck
|
||||
_, err := sess.Exec("UPDATE dashboard SET deleted=NULL, folder_id = ?, folder_uid=? WHERE org_id=? AND uid=?", folder.ID, folder.UID, orgID, dashboardUID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (d *dashboardStore) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUID string) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.SoftDeleteDashboard")
|
||||
defer span.End()
|
||||
|
||||
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
_, err := sess.Exec("UPDATE dashboard SET deleted=? WHERE org_id=? AND uid=?", time.Now(), orgID, dashboardUID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (d *dashboardStore) SoftDeleteDashboardsInFolders(ctx context.Context, orgID int64, folderUids []string) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.SoftDeleteDashboardsInFolders")
|
||||
defer span.End()
|
||||
|
||||
if len(folderUids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
s := strings.Builder{}
|
||||
s.WriteString("UPDATE dashboard SET deleted=? WHERE ")
|
||||
s.WriteString(fmt.Sprintf("folder_uid IN (%s)", strings.Repeat("?,", len(folderUids)-1)+"?"))
|
||||
s.WriteString(" AND org_id = ? AND is_folder = ?")
|
||||
|
||||
sql := s.String()
|
||||
args := make([]any, 0, 3)
|
||||
args = append(args, sql, time.Now())
|
||||
for _, folderUID := range folderUids {
|
||||
args = append(args, folderUID)
|
||||
}
|
||||
args = append(args, orgID, d.store.GetDialect().BooleanValue(false))
|
||||
|
||||
_, err := sess.Exec(args...)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (d *dashboardStore) DeleteDashboard(ctx context.Context, cmd *dashboards.DeleteDashboardCommand) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.DeleteDashboard")
|
||||
@@ -681,21 +603,13 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
|
||||
}
|
||||
|
||||
if dashboard.IsFolder {
|
||||
if !d.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
sqlStatements = append(sqlStatements, statement{
|
||||
SQL: "DELETE FROM dashboard WHERE org_id = ? AND folder_uid = ? AND is_folder = ? AND deleted IS NULL",
|
||||
args: []any{dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanValue(false)},
|
||||
})
|
||||
sqlStatements = append(sqlStatements, statement{
|
||||
SQL: "DELETE FROM dashboard WHERE org_id = ? AND folder_uid = ? AND is_folder = ? AND deleted IS NULL",
|
||||
args: []any{dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanValue(false)},
|
||||
})
|
||||
|
||||
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// soft delete all dashboards in the folder
|
||||
sqlStatements = append(sqlStatements, statement{
|
||||
SQL: "UPDATE dashboard SET deleted = ? WHERE org_id = ? AND folder_uid = ? AND is_folder = ? ",
|
||||
args: []any{time.Now(), dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanValue(false)},
|
||||
})
|
||||
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove all access control permission with folder scope
|
||||
@@ -1164,18 +1078,3 @@ func (d *dashboardStore) GetAllDashboardsByOrgId(ctx context.Context, orgID int6
|
||||
}
|
||||
return dashs, nil
|
||||
}
|
||||
|
||||
func (d *dashboardStore) GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*dashboards.Dashboard, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.GetSoftDeletedExpiredDashboards")
|
||||
defer span.End()
|
||||
|
||||
var dashboards = make([]*dashboards.Dashboard, 0)
|
||||
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
err := sess.Where("deleted IS NOT NULL AND deleted < ?", time.Now().Add(-duration)).Find(&dashboards)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/services/search/sort"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
@@ -702,7 +701,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
_ = insertTestDashboard(t, dashboardStore, "delete me 1", 1, folder.ID, folder.UID, false, "delete this 1")
|
||||
_ = insertTestDashboard(t, dashboardStore, "delete me 2", 1, folder.ID, folder.UID, false, "delete this 2")
|
||||
|
||||
err := dashboardStore.SoftDeleteDashboardsInFolders(context.Background(), folder.OrgID, []string{folder.UID})
|
||||
err := dashboardStore.DeleteDashboardsInFolders(context.Background(), &dashboards.DeleteDashboardsInFolderRequest{OrgID: folder.OrgID, FolderUIDs: []string{folder.UID}})
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{folder.UID}, OrgID: 1})
|
||||
@@ -711,126 +710,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationGetSoftDeletedDashboard(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var cfg *setting.Cfg
|
||||
var savedFolder, savedDash *dashboards.Dashboard
|
||||
var dashboardStore dashboards.Store
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBWithCfg(t)
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
|
||||
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
|
||||
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, savedFolder.UID, false, "prod")
|
||||
}
|
||||
|
||||
t.Run("Should soft delete a dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
// Confirm there are 2 dashboards in the folder
|
||||
amount, err := dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), amount)
|
||||
|
||||
// Soft delete the dashboard
|
||||
err = dashboardStore.SoftDeleteDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// There is only 1 dashboard in the folder after soft delete
|
||||
amount, err = dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), amount)
|
||||
|
||||
var dash *dashboards.Dashboard
|
||||
// Get the soft deleted dashboard should be empty
|
||||
dash, _ = dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{UID: savedDash.UID, OrgID: savedDash.OrgID})
|
||||
assert.Error(t, dashboards.ErrDashboardNotFound)
|
||||
assert.Nil(t, dash)
|
||||
|
||||
// Get the soft deleted dashboard
|
||||
dash, err = dashboardStore.GetSoftDeletedDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedDash.ID, dash.ID)
|
||||
assert.Equal(t, savedDash.UID, dash.UID)
|
||||
assert.Equal(t, savedDash.Title, dash.Title)
|
||||
})
|
||||
|
||||
t.Run("Should not fail when trying to soft delete a soft deleted dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
// Soft delete the dashboard
|
||||
err := dashboardStore.SoftDeleteDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Soft delete the dashboard
|
||||
err = dashboardStore.SoftDeleteDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get the soft deleted dashboard
|
||||
dash, err := dashboardStore.GetSoftDeletedDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedDash.ID, dash.ID)
|
||||
assert.Equal(t, savedDash.UID, dash.UID)
|
||||
assert.Equal(t, savedDash.Title, dash.Title)
|
||||
})
|
||||
|
||||
t.Run("Should restore a dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
// Confirm there are 2 dashboards in the folder
|
||||
amount, err := dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), amount)
|
||||
|
||||
// Soft delete the dashboard
|
||||
err = dashboardStore.SoftDeleteDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// There is only 1 dashboard in the folder after soft delete
|
||||
amount, err = dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), amount)
|
||||
|
||||
// Get the soft deleted dashboard
|
||||
dash, err := dashboardStore.GetSoftDeletedDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedDash.ID, dash.ID)
|
||||
assert.Equal(t, savedDash.UID, dash.UID)
|
||||
assert.Equal(t, savedDash.Title, dash.Title)
|
||||
|
||||
// Restore deleted dashboard
|
||||
// nolint:staticcheck
|
||||
err = dashboardStore.RestoreDashboard(context.Background(), savedDash.OrgID, savedDash.UID, &folder.Folder{ID: savedDash.FolderID, UID: savedDash.FolderUID})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Restore increases the amount of dashboards in the folder
|
||||
amount, err = dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), amount)
|
||||
|
||||
// Get the soft deleted dashboard should be empty
|
||||
dash, err = dashboardStore.GetSoftDeletedDashboard(context.Background(), savedDash.OrgID, savedDash.UID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, dash)
|
||||
|
||||
// Get the restored dashboard
|
||||
dash, err = dashboardStore.GetDashboard(context.Background(), &dashboards.GetDashboardQuery{UID: savedDash.UID, OrgID: savedDash.OrgID})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, savedDash.ID, dash.ID)
|
||||
assert.Equal(t, savedDash.UID, dash.UID)
|
||||
assert.Equal(t, savedDash.Title, dash.Title)
|
||||
// nolint:staticcheck
|
||||
assert.Equal(t, savedDash.FolderID, dash.FolderID)
|
||||
assert.Equal(t, savedDash.FolderUID, dash.FolderUID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationDashboardDataAccessGivenPluginWithImportedDashboards(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
|
||||
@@ -1036,79 +1036,6 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar
|
||||
return dr.dashboardStore.SaveDashboard(ctx, *cmd)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid})
|
||||
}
|
||||
|
||||
return dr.dashboardStore.GetSoftDeletedDashboard(ctx, orgID, uid)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) RestoreDashboard(ctx context.Context, dashboard *dashboards.Dashboard, user identity.Requester, optionalFolderUID string) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.RestoreDashboard")
|
||||
defer span.End()
|
||||
|
||||
if !dr.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore)
|
||||
}
|
||||
|
||||
// if the optionalFolder is provided we need to check if the folder exists and user has access to it
|
||||
if optionalFolderUID != "" {
|
||||
restoringFolder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
|
||||
UID: &optionalFolderUID,
|
||||
OrgID: dashboard.OrgID,
|
||||
SignedInUser: user,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
return dashboards.ErrFolderRestoreNotFound
|
||||
}
|
||||
return folder.ErrInternal.Errorf("failed to fetch parent folder from store: %w", err)
|
||||
}
|
||||
|
||||
return dr.dashboardStore.RestoreDashboard(ctx, dashboard.OrgID, dashboard.UID, restoringFolder)
|
||||
}
|
||||
|
||||
// if the optionalFolder is not provided we need to restore the dashboard to the original folder
|
||||
// we check for permissions and the folder existence before restoring
|
||||
restoringFolder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
|
||||
UID: &dashboard.FolderUID,
|
||||
OrgID: dashboard.OrgID,
|
||||
SignedInUser: user,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
return dashboards.ErrFolderRestoreNotFound
|
||||
}
|
||||
return folder.ErrInternal.Errorf("failed to fetch parent folder from store: %w", err)
|
||||
}
|
||||
|
||||
// TODO: once restore in k8s is finalized, add functionality here under the feature toggle
|
||||
|
||||
return dr.dashboardStore.RestoreDashboard(ctx, dashboard.OrgID, dashboard.UID, restoringFolder)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUID string) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.SoftDeleteDashboard")
|
||||
defer span.End()
|
||||
|
||||
if !dr.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore)
|
||||
}
|
||||
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// deletes in unistore are soft deletes, so we can just delete in the same way
|
||||
return dr.deleteDashboardThroughK8s(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgID, UID: dashboardUID}, true)
|
||||
}
|
||||
|
||||
provisionedData, _ := dr.GetProvisionedDashboardDataByDashboardUID(ctx, orgID, dashboardUID)
|
||||
if provisionedData != nil && provisionedData.ID != 0 {
|
||||
return dashboards.ErrDashboardCannotDeleteProvisionedDashboard
|
||||
}
|
||||
|
||||
return dr.dashboardStore.SoftDeleteDashboard(ctx, orgID, dashboardUID)
|
||||
}
|
||||
|
||||
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
|
||||
// operations by the user where we want to make sure user does not delete provisioned dashboard.
|
||||
func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
|
||||
@@ -1745,10 +1672,6 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.DeleteInFolders")
|
||||
defer span.End()
|
||||
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
return dr.dashboardStore.SoftDeleteDashboardsInFolders(ctx, orgID, folderUIDs)
|
||||
}
|
||||
|
||||
// We need a list of dashboard uids inside the folder to delete related public dashboards
|
||||
dashes, err := dr.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: u,
|
||||
@@ -1788,27 +1711,6 @@ func (dr *DashboardServiceImpl) CleanUpDashboard(ctx context.Context, dashboardU
|
||||
return dr.dashboardStore.CleanupAfterDelete(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgId, UID: dashboardUID})
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) CleanUpDeletedDashboards(ctx context.Context) (int64, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.CleanUpDeletedDashboards")
|
||||
defer span.End()
|
||||
|
||||
var deletedDashboardsCount int64
|
||||
deletedDashboards, err := dr.dashboardStore.GetSoftDeletedExpiredDashboards(ctx, daysInTrash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, dashboard := range deletedDashboards {
|
||||
err = dr.DeleteDashboard(ctx, dashboard.ID, dashboard.UID, dashboard.OrgID)
|
||||
if err != nil {
|
||||
dr.log.Warn("Failed to cleanup deleted dashboard", "dashboardUid", dashboard.UID, "error", err)
|
||||
break
|
||||
}
|
||||
deletedDashboardsCount++
|
||||
}
|
||||
|
||||
return deletedDashboardsCount, nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Dashboard k8s functions
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
@@ -255,13 +255,6 @@ func TestDashboardService(t *testing.T) {
|
||||
err := service.DeleteInFolders(context.Background(), 1, []string{"uid"}, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Soft Delete dashboards in folder", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagDashboardRestore)
|
||||
fakeStore.On("SoftDeleteDashboardsInFolders", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
err := service.DeleteInFolders(context.Background(), 1, []string{"uid"}, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,9 @@ package dashboards
|
||||
import (
|
||||
context "context"
|
||||
|
||||
folder "github.com/grafana/grafana/pkg/services/folder"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
quota "github.com/grafana/grafana/pkg/services/quota"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// FakeDashboardStore is an autogenerated mock type for the Store type
|
||||
@@ -586,84 +583,6 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Conte
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSoftDeletedDashboard provides a mock function with given fields: ctx, orgID, uid
|
||||
func (_m *FakeDashboardStore) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, orgID, uid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSoftDeletedDashboard")
|
||||
}
|
||||
|
||||
var r0 *Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*Dashboard, error)); ok {
|
||||
return rf(ctx, orgID, uid)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *Dashboard); ok {
|
||||
r0 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSoftDeletedExpiredDashboards provides a mock function with given fields: ctx, duration
|
||||
func (_m *FakeDashboardStore) GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*Dashboard, error) {
|
||||
ret := _m.Called(ctx, duration)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSoftDeletedExpiredDashboards")
|
||||
}
|
||||
|
||||
var r0 []*Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, time.Duration) ([]*Dashboard, error)); ok {
|
||||
return rf(ctx, duration)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, time.Duration) []*Dashboard); ok {
|
||||
r0 = rf(ctx, duration)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, time.Duration) error); ok {
|
||||
r1 = rf(ctx, duration)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RestoreDashboard provides a mock function with given fields: ctx, orgID, dashboardUid, _a3
|
||||
func (_m *FakeDashboardStore) RestoreDashboard(ctx context.Context, orgID int64, dashboardUid string, _a3 *folder.Folder) error {
|
||||
ret := _m.Called(ctx, orgID, dashboardUid, _a3)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RestoreDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string, *folder.Folder) error); ok {
|
||||
r0 = rf(ctx, orgID, dashboardUid, _a3)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SaveDashboard provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakeDashboardStore) SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
@@ -724,42 +643,6 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SoftDeleteDashboard provides a mock function with given fields: ctx, orgID, dashboardUid
|
||||
func (_m *FakeDashboardStore) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error {
|
||||
ret := _m.Called(ctx, orgID, dashboardUid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SoftDeleteDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok {
|
||||
r0 = rf(ctx, orgID, dashboardUid)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SoftDeleteDashboardsInFolders provides a mock function with given fields: ctx, orgID, folderUids
|
||||
func (_m *FakeDashboardStore) SoftDeleteDashboardsInFolders(ctx context.Context, orgID int64, folderUids []string) error {
|
||||
ret := _m.Called(ctx, orgID, folderUids)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SoftDeleteDashboardsInFolders")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, []string) error); ok {
|
||||
r0 = rf(ctx, orgID, folderUids)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UnprovisionDashboard provides a mock function with given fields: ctx, id
|
||||
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
Reference in New Issue
Block a user