Dashboards: Add feature restore dashboards backend (#83131)

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
This commit is contained in:
Ezequiel Victorero
2024-05-16 14:36:26 -03:00
committed by GitHub
co-authored by Sofia Papagiannaki
parent edae5fc791
commit 42d75ac737
30 changed files with 1230 additions and 96 deletions
+92 -12
View File
@@ -7,8 +7,6 @@ import (
"strings"
"time"
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
@@ -16,6 +14,7 @@ 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/migrator"
@@ -513,6 +512,67 @@ 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) {
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 {
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
// 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 {
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 {
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().BooleanStr(false))
_, err := sess.Exec(args...)
return err
})
}
func (d *dashboardStore) DeleteDashboard(ctx context.Context, cmd *dashboards.DeleteDashboardCommand) error {
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
@@ -545,10 +605,13 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
}
if dashboard.IsFolder {
deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ?")
// if this is a soft delete, we need to skip children deletion.
if !d.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) && !cmd.SkipSoftDeletedDashboards {
deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ? AND deleted IS NULL")
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
return err
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
return err
}
}
// remove all access control permission with folder scope
@@ -686,7 +749,7 @@ func (d *dashboardStore) GetDashboard(ctx context.Context, query *dashboards.Get
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
}
has, err := sess.MustCols(mustCols...).Nullable("folder_uid").Get(&dashboard)
has, err := sess.Where("deleted IS NULL").MustCols(mustCols...).Nullable("folder_uid").Get(&dashboard)
if err != nil {
return err
} else if !has {
@@ -726,17 +789,20 @@ func (d *dashboardStore) GetDashboards(ctx context.Context, query *dashboards.Ge
if len(query.DashboardIDs) == 0 && len(query.DashboardUIDs) == 0 {
return star.ErrCommandValidationFailed
}
var session *xorm.Session
// remove soft deleted dashboards from the response
sess.Where("deleted IS NULL")
if len(query.DashboardIDs) > 0 {
session = sess.In("id", query.DashboardIDs)
sess.In("id", query.DashboardIDs)
} else {
session = sess.In("uid", query.DashboardUIDs)
sess.In("uid", query.DashboardUIDs)
}
if query.OrgID > 0 {
session = sess.Where("org_id = ?", query.OrgID)
sess.Where("org_id = ?", query.OrgID)
}
err := session.Find(&dashboards)
err := sess.Find(&dashboards)
return err
})
if err != nil {
@@ -802,6 +868,8 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
})
}
filters = append(filters, searchstore.DeletedFilter{Deleted: query.IsDeleted})
var res []dashboards.DashboardSearchProjection
sb := &searchstore.Builder{Dialect: d.store.GetDialect(), Filters: filters, Features: d.features}
@@ -871,7 +939,7 @@ func (d *dashboardStore) CountDashboardsInFolders(
args = append(args, folderUID)
}
}
s.WriteString(" AND org_id = ? AND is_folder = ?")
s.WriteString(" AND org_id = ? AND is_folder = ? AND deleted IS NULL")
args = append(args, req.OrgID, d.store.GetDialect().BooleanStr(false))
sql := s.String()
_, err := sess.SQL(sql, args...).Get(&count)
@@ -919,6 +987,18 @@ func (d *dashboardStore) GetAllDashboards(ctx context.Context) ([]*dashboards.Da
return dashboards, nil
}
func (d *dashboardStore) GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*dashboards.Dashboard, error) {
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
}
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
limits := &quota.Map{}