diff --git a/pkg/services/cloudmigration/api/dtos.go b/pkg/services/cloudmigration/api/dtos.go
index 6e1154489bf..1efa21fc77c 100644
--- a/pkg/services/cloudmigration/api/dtos.go
+++ b/pkg/services/cloudmigration/api/dtos.go
@@ -127,6 +127,7 @@ const (
FolderDataType MigrateDataType = "FOLDER"
LibraryElementDataType MigrateDataType = "LIBRARY_ELEMENT"
AlertRuleType MigrateDataType = "ALERT_RULE"
+ AlertRuleGroupType MigrateDataType = "ALERT_RULE_GROUP"
ContactPointType MigrateDataType = "CONTACT_POINT"
NotificationPolicyType MigrateDataType = "NOTIFICATION_POLICY"
NotificationTemplateType MigrateDataType = "NOTIFICATION_TEMPLATE"
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
index 257d2e23a07..348bf8b1e98 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
@@ -821,8 +821,11 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool, cfgOverrides ...conf
secretsService := secretsfakes.NewFakeSecretsService()
rr := routing.NewRouteRegister()
tracer := tracing.InitializeTracerForTest()
+
+ fakeFolder := &folder.Folder{UID: "folderUID", Title: "Folder"}
mockFolder := &foldertest.FakeService{
- ExpectedFolder: &folder.Folder{UID: "folderUID", Title: "Folder"},
+ ExpectedFolders: []*folder.Folder{fakeFolder},
+ ExpectedFolder: fakeFolder,
}
cfg := setting.NewCfg()
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
index 870c4949533..034c601f510 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
@@ -43,6 +43,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{
cloudmigration.NotificationTemplateType,
cloudmigration.ContactPointType,
cloudmigration.NotificationPolicyType,
+ cloudmigration.AlertRuleGroupType,
cloudmigration.AlertRuleType,
cloudmigration.PluginDataType,
}
@@ -106,6 +107,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
return nil, err
}
+ // Alerts: Alert Rule Groups
+ alertRuleGroups, err := s.getAlertRuleGroups(ctx, signedInUser)
+ if err != nil {
+ s.log.Error("Failed to get alert rule groups", "err", err)
+ return nil, err
+ }
+
// Alerts: Alert Rules
alertRules, err := s.getAlertRules(ctx, signedInUser)
if err != nil {
@@ -209,6 +217,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
})
}
+ for _, alertRuleGroup := range alertRuleGroups {
+ migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
+ Type: cloudmigration.AlertRuleGroupType,
+ RefID: alertRuleGroup.Title, // no UID available
+ Name: alertRuleGroup.Title,
+ Data: alertRuleGroup,
+ })
+ }
+
for _, alertRule := range alertRules {
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
Type: cloudmigration.AlertRuleType,
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
index a848508b7eb..c75d8a35df0 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
@@ -180,3 +180,61 @@ func (s *Service) getAlertRules(ctx context.Context, signedInUser *user.SignedIn
return provisionedAlertRules, nil
}
+
+type alertRuleGroup struct {
+ Title string `json:"title"`
+ FolderUID string `json:"folderUid"`
+ Interval int64 `json:"interval"`
+ Rules []alertRule `json:"rules"`
+}
+
+func (s *Service) getAlertRuleGroups(ctx context.Context, signedInUser *user.SignedInUser) ([]alertRuleGroup, error) {
+ alertRuleGroupsWithFolder, err := s.ngAlert.Api.AlertRules.GetAlertGroupsWithFolderFullpath(ctx, signedInUser, nil)
+ if err != nil {
+ return nil, fmt.Errorf("fetching alert rule groups with folders: %w", err)
+ }
+
+ settingAlertRulesPaused := s.cfg.CloudMigration.AlertRulesState == setting.GMSAlertRulesPaused
+
+ alertRuleGroups := make([]alertRuleGroup, 0, len(alertRuleGroupsWithFolder))
+
+ for _, ruleGroup := range alertRuleGroupsWithFolder {
+ provisionedAlertRules := make([]alertRule, 0, len(ruleGroup.Rules))
+
+ for _, rule := range ruleGroup.Rules {
+ isPaused := rule.IsPaused
+ if settingAlertRulesPaused {
+ isPaused = true
+ }
+
+ provisionedAlertRules = append(provisionedAlertRules, alertRule{
+ ID: rule.ID,
+ UID: rule.UID,
+ OrgID: rule.OrgID,
+ FolderUID: rule.NamespaceUID,
+ RuleGroup: rule.RuleGroup,
+ Title: rule.Title,
+ For: model.Duration(rule.For),
+ Condition: rule.Condition,
+ Data: ngalertapi.ApiAlertQueriesFromAlertQueries(rule.Data),
+ Updated: rule.Updated,
+ NoDataState: rule.NoDataState.String(),
+ ExecErrState: rule.ExecErrState.String(),
+ Annotations: rule.Annotations,
+ Labels: rule.Labels,
+ IsPaused: isPaused,
+ NotificationSettings: ngalertapi.AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
+ Record: ngalertapi.ApiRecordFromModelRecord(rule.Record),
+ })
+ }
+
+ alertRuleGroups = append(alertRuleGroups, alertRuleGroup{
+ Title: ruleGroup.Title,
+ FolderUID: ruleGroup.FolderUID,
+ Interval: ruleGroup.Interval,
+ Rules: provisionedAlertRules,
+ })
+ }
+
+ return alertRuleGroups, nil
+}
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
index f8e99aed021..91d7ecc5cd7 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
@@ -121,7 +121,7 @@ func TestGetAlertRules(t *testing.T) {
user := &user.SignedInUser{OrgID: 1}
- alertRule := createAlertRule(t, ctx, s, user, false)
+ alertRule := createAlertRule(t, ctx, s, user, false, "")
alertRules, err := s.getAlertRules(ctx, user)
require.NoError(t, err)
@@ -138,10 +138,10 @@ func TestGetAlertRules(t *testing.T) {
user := &user.SignedInUser{OrgID: 1}
- alertRulePaused := createAlertRule(t, ctx, s, user, true)
+ alertRulePaused := createAlertRule(t, ctx, s, user, true, "")
require.True(t, alertRulePaused.IsPaused)
- alertRuleUnpaused := createAlertRule(t, ctx, s, user, false)
+ alertRuleUnpaused := createAlertRule(t, ctx, s, user, false, "")
require.False(t, alertRuleUnpaused.IsPaused)
alertRules, err := s.getAlertRules(ctx, user)
@@ -152,6 +152,83 @@ func TestGetAlertRules(t *testing.T) {
})
}
+func TestGetAlertRuleGroups(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ t.Cleanup(cancel)
+
+ t.Run("it returns the alert rule groups", func(t *testing.T) {
+ s := setUpServiceTest(t, false).(*Service)
+
+ user := &user.SignedInUser{OrgID: 1}
+
+ ruleGroupTitle := "ruleGroupTitle"
+
+ alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle)
+ alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle)
+ alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup")
+
+ createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2})
+
+ ruleGroups, err := s.getAlertRuleGroups(ctx, user)
+ require.NoError(t, err)
+ require.Len(t, ruleGroups, 2)
+
+ for _, ruleGroup := range ruleGroups {
+ alertRuleUIDs := make([]string, 0)
+ for _, alertRule := range ruleGroup.Rules {
+ alertRuleUIDs = append(alertRuleUIDs, alertRule.UID)
+ }
+
+ if ruleGroup.Title == ruleGroupTitle {
+ require.Len(t, ruleGroup.Rules, 2)
+ require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs)
+ } else {
+ require.Len(t, ruleGroup.Rules, 1)
+ require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs)
+ }
+ }
+ })
+
+ t.Run("with the alert rules state set to paused, it returns the alert rule groups with alert rules paused", func(t *testing.T) {
+ alertRulesState := func(c *setting.Cfg) {
+ c.CloudMigration.AlertRulesState = setting.GMSAlertRulesPaused
+ }
+
+ s := setUpServiceTest(t, false, alertRulesState).(*Service)
+
+ user := &user.SignedInUser{OrgID: 1}
+
+ ruleGroupTitle := "ruleGroupTitle"
+
+ alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle)
+ alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle)
+ alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup")
+
+ createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2})
+
+ ruleGroups, err := s.getAlertRuleGroups(ctx, user)
+ require.NoError(t, err)
+ require.Len(t, ruleGroups, 2)
+
+ for _, ruleGroup := range ruleGroups {
+ alertRuleUIDs := make([]string, 0)
+ for _, alertRule := range ruleGroup.Rules {
+ alertRuleUIDs = append(alertRuleUIDs, alertRule.UID)
+
+ require.True(t, alertRule.IsPaused)
+ }
+
+ if ruleGroup.Title == ruleGroupTitle {
+ require.Len(t, ruleGroup.Rules, 2)
+ require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs)
+ } else {
+ require.Len(t, ruleGroup.Rules, 1)
+ require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs)
+ }
+ }
+ })
+}
+
func createMuteTiming(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.MuteTimeInterval {
t.Helper()
@@ -267,12 +344,12 @@ func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Se
require.NoError(t, err)
}
-func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool) models.AlertRule {
+func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool, ruleGroup string) models.AlertRule {
t.Helper()
rule := models.AlertRule{
OrgID: user.GetOrgID(),
- Title: fmt.Sprintf("Alert Rule SLO (Paused: %v)", isPaused),
+ Title: fmt.Sprintf("Alert Rule SLO (Paused: %v) - %v", isPaused, ruleGroup),
NamespaceUID: "folderUID",
Condition: "A",
Data: []models.AlertQuery{
@@ -286,7 +363,7 @@ func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *
},
},
IsPaused: isPaused,
- RuleGroup: "ruleGroup",
+ RuleGroup: ruleGroup,
For: time.Minute,
IntervalSeconds: 60,
NoDataState: models.OK,
@@ -298,3 +375,19 @@ func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *
return createdRule
}
+
+func createAlertRuleGroup(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, title string, rules []models.AlertRule) models.AlertRuleGroup {
+ t.Helper()
+
+ group := models.AlertRuleGroup{
+ Title: title,
+ FolderUID: "folderUID",
+ Interval: 300,
+ Rules: rules,
+ }
+
+ err := service.ngAlert.Api.AlertRules.ReplaceRuleGroup(ctx, user, group, "")
+ require.NoError(t, err)
+
+ return group
+}
diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go
index 7d0b4b58f49..a46a3b46910 100644
--- a/pkg/services/cloudmigration/model.go
+++ b/pkg/services/cloudmigration/model.go
@@ -88,6 +88,7 @@ const (
FolderDataType MigrateDataType = "FOLDER"
LibraryElementDataType MigrateDataType = "LIBRARY_ELEMENT"
AlertRuleType MigrateDataType = "ALERT_RULE"
+ AlertRuleGroupType MigrateDataType = "ALERT_RULE_GROUP"
ContactPointType MigrateDataType = "CONTACT_POINT"
NotificationPolicyType MigrateDataType = "NOTIFICATION_POLICY"
NotificationTemplateType MigrateDataType = "NOTIFICATION_TEMPLATE"
diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json
index 7667a82756c..e2e8df6926b 100644
--- a/public/api-enterprise-spec.json
+++ b/public/api-enterprise-spec.json
@@ -5670,6 +5670,7 @@
"FOLDER",
"LIBRARY_ELEMENT",
"ALERT_RULE",
+ "ALERT_RULE_GROUP",
"CONTACT_POINT",
"NOTIFICATION_POLICY",
"NOTIFICATION_TEMPLATE",
diff --git a/public/api-merged.json b/public/api-merged.json
index e063c8c959c..3e2239131d4 100644
--- a/public/api-merged.json
+++ b/public/api-merged.json
@@ -17182,6 +17182,7 @@
"FOLDER",
"LIBRARY_ELEMENT",
"ALERT_RULE",
+ "ALERT_RULE_GROUP",
"CONTACT_POINT",
"NOTIFICATION_POLICY",
"NOTIFICATION_TEMPLATE",
diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
index e24b6b424f0..98e962b61e0 100644
--- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
+++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
@@ -194,6 +194,7 @@ export type MigrateDataResponseItemDto = {
| 'FOLDER'
| 'LIBRARY_ELEMENT'
| 'ALERT_RULE'
+ | 'ALERT_RULE_GROUP'
| 'CONTACT_POINT'
| 'NOTIFICATION_POLICY'
| 'NOTIFICATION_TEMPLATE'
diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
index c6b8f78f04b..3080a26fdd9 100644
--- a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
@@ -231,6 +231,8 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) {
return ;
case 'ALERT_RULE':
return ;
+ case 'ALERT_RULE_GROUP':
+ return ;
case 'PLUGIN':
if (pluginLogo) {
return
;
diff --git a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
index 399945cdaba..00d576db9c5 100644
--- a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
@@ -23,6 +23,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) {
return t('migrate-to-cloud.resource-type.notification_policy', 'Notification Policy');
case 'ALERT_RULE':
return t('migrate-to-cloud.resource-type.alert_rule', 'Alert Rule');
+ case 'ALERT_RULE_GROUP':
+ return t('migrate-to-cloud.resource-type.alert_rule_group', 'Alert Rule Group');
case 'PLUGIN':
return t('migrate-to-cloud.resource-type.plugin', 'Plugin');
default:
diff --git a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
index e21fd83e815..e98a8221bb6 100644
--- a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
@@ -62,6 +62,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) {
types.push(t('migrate-to-cloud.migrated-counts.notification_policies', 'notification policies'));
} else if (type === 'ALERT_RULE') {
types.push(t('migrate-to-cloud.migrated-counts.alert_rules', 'alert rules'));
+ } else if (type === 'ALERT_RULE_GROUP') {
+ types.push(t('migrate-to-cloud.migrated-counts.alert_rule_groups', 'alert rule groups'));
} else if (type === 'PLUGIN') {
types.push(t('migrate-to-cloud.migrated-counts.plugins', 'plugins'));
}
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index f1b5d7ba851..66492eac6b1 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -2117,6 +2117,7 @@
"title": "Let us help you migrate to this stack"
},
"migrated-counts": {
+ "alert_rule_groups": "alert rule groups",
"alert_rules": "alert rules",
"contact_points": "contact points",
"dashboards": "dashboards",
@@ -2221,6 +2222,7 @@
},
"resource-type": {
"alert_rule": "Alert Rule",
+ "alert_rule_group": "Alert Rule Group",
"contact_point": "Contact Point",
"dashboard": "Dashboard",
"datasource": "Data source",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index ad00f61eef6..193643a5111 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -2117,6 +2117,7 @@
"title": "Ŀęŧ ūş ĥęľp yőū mįģřäŧę ŧő ŧĥįş şŧäčĸ"
},
"migrated-counts": {
+ "alert_rule_groups": "äľęřŧ řūľę ģřőūpş",
"alert_rules": "äľęřŧ řūľęş",
"contact_points": "čőʼnŧäčŧ pőįʼnŧş",
"dashboards": "đäşĥþőäřđş",
@@ -2221,6 +2222,7 @@
},
"resource-type": {
"alert_rule": "Åľęřŧ Ŗūľę",
+ "alert_rule_group": "Åľęřŧ Ŗūľę Ğřőūp",
"contact_point": "Cőʼnŧäčŧ Pőįʼnŧ",
"dashboard": "Đäşĥþőäřđ",
"datasource": "Đäŧä şőūřčę",
diff --git a/public/openapi3.json b/public/openapi3.json
index f326616d62d..89612da9cf1 100644
--- a/public/openapi3.json
+++ b/public/openapi3.json
@@ -7250,6 +7250,7 @@
"FOLDER",
"LIBRARY_ELEMENT",
"ALERT_RULE",
+ "ALERT_RULE_GROUP",
"CONTACT_POINT",
"NOTIFICATION_POLICY",
"NOTIFICATION_TEMPLATE",