[v9.3.x] Alerting: Update migration to put alerts to the default folder if dashboard folder is missing (#66592)

* Alerting: Update migration to put alerts to the default folder if dashboard folder is missing (#65577)

* extract function

* use context logger

* put alert to general folder if folder is missing

* move folderHelper init

* add test

* Update pkg/services/sqlstore/migrations/ualert/ualert.go

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>

---------

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
(cherry picked from commit 7b2f44762e)

* rename ID to Id and dashboards.Dashboard to models.Dashboard

---------

Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
Grot (@grafanabot)
2023-04-15 08:09:15 +02:00
committed by GitHub
co-authored by Yuri Tseretyan
parent bf1d57d97c
commit 5a35850514
2 changed files with 74 additions and 26 deletions
@@ -542,6 +542,43 @@ func TestDashAlertMigration(t *testing.T) {
}
}
})
t.Run("when folder is missing put alert in General folder", func(t *testing.T) {
o := createOrg(t, 1)
folder1 := createDashboard(t, 1, o.Id, "folder-1")
folder1.IsFolder = true
dash1 := createDashboard(t, 3, o.Id, "dash1")
dash1.FolderId = folder1.Id
dash2 := createDashboard(t, 4, o.Id, "dash2")
dash2.FolderId = 22 // missing folder
a1 := createAlert(t, o.Id, dash1.Id, int64(1), "alert-1", []string{})
a2 := createAlert(t, o.Id, dash2.Id, int64(1), "alert-2", []string{})
_, err := x.Insert(o, folder1, dash1, dash2, a1, a2)
require.NoError(t, err)
runDashAlertMigrationTestRun(t, x)
rules := getAlertRules(t, x, o.Id)
require.Len(t, rules, 2)
var generalFolder models.Dashboard
_, err = x.Table(&models.Dashboard{}).Where("title = ? AND org_id = ?", ualert.GENERAL_FOLDER, o.Id).Get(&generalFolder)
require.NoError(t, err)
require.NotNil(t, generalFolder)
for _, rule := range rules {
var expectedFolder models.Dashboard
if rule.Title == a1.Name {
expectedFolder = *folder1
} else {
expectedFolder = generalFolder
}
require.Equal(t, expectedFolder.Uid, rule.NamespaceUID)
}
})
}
const (
@@ -262,10 +262,35 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
// cache for the general folders
generalFolderCache := make(map[int64]*dashboard)
folderHelper := folderHelper{
sess: sess,
mg: mg,
}
gf := func(dash dashboard, da dashAlert) (*dashboard, error) {
f, ok := generalFolderCache[dash.OrgId]
if !ok {
// get or create general folder
f, err = folderHelper.getOrCreateGeneralFolder(dash.OrgId)
if err != nil {
return nil, MigrationError{
Err: fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
AlertId: da.Id,
}
}
generalFolderCache[dash.OrgId] = f
}
// No need to assign default permissions to general folder
// because they are included to the query result if it's a folder with no permissions
// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
return f, nil
}
// Per org map of newly created rules to which notification channels it should send to.
rulesPerOrg := make(map[int64]map[*alertRule][]uidOrID)
for _, da := range dashAlerts {
l := mg.Logger.New("ruleID", da.Id, "ruleName", da.Name, "dashboardUID", da.DashboardUID, "orgID", da.OrgId)
newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
if err != nil {
return err
@@ -289,18 +314,13 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
}
}
folderHelper := folderHelper{
sess: sess,
mg: mg,
}
var folder *dashboard
switch {
case dash.HasACL:
folderName := getAlertFolderNameFromDashboard(&dash)
f, ok := folderCache[folderName]
if !ok {
mg.Logger.Info("create a new folder for alerts that belongs to dashboard because it has custom permissions", "org", dash.OrgId, "dashboard_uid", dash.Uid, "folder", folderName)
l.Info("create a new folder for alerts that belongs to dashboard because it has custom permissions", "folder", folderName)
// create folder and assign the permissions of the dashboard (included default and inherited)
f, err = folderHelper.createFolder(dash.OrgId, folderName)
if err != nil {
@@ -330,29 +350,20 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
// get folder if exists
f, err := folderHelper.getFolder(dash, da)
if err != nil {
return MigrationError{
Err: err,
AlertId: da.Id,
}
}
folder = &f
default:
f, ok := generalFolderCache[dash.OrgId]
if !ok {
// get or create general folder
f, err = folderHelper.getOrCreateGeneralFolder(dash.OrgId)
// If folder does not exist then the dashboard is an orphan and we migrate the alert to the general folder.
l.Warn("Failed to find folder for dashboard. Migrate rule to the default folder", "rule_name", da.Name, "dashboard_uid", da.DashboardUID, "missing_folder_id", dash.FolderId)
folder, err = gf(dash, da)
if err != nil {
return MigrationError{
Err: fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
AlertId: da.Id,
}
return err
}
generalFolderCache[dash.OrgId] = f
} else {
folder = &f
}
default:
folder, err = gf(dash, da)
if err != nil {
return err
}
// No need to assign default permissions to general folder
// because they are included to the query result if it's a folder with no permissions
// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
folder = f
}
if folder.Uid == "" {