diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
index fd977a85be7..119c5856539 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
@@ -35,6 +35,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{
cloudmigration.MuteTimingType,
cloudmigration.NotificationTemplateType,
cloudmigration.ContactPointType,
+ cloudmigration.NotificationPolicyType,
}
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
@@ -82,6 +83,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
return nil, err
}
+ // Alerts: Notification Policies
+ notificationPolicies, err := s.getNotificationPolicies(ctx, signedInUser)
+ if err != nil {
+ s.log.Error("Failed to get alert notification policies", "err", err)
+ return nil, err
+ }
+
migrationDataSlice := make(
[]cloudmigration.MigrateDataRequestItem, 0,
len(dataSources)+len(dashs)+len(folders)+len(libraryElements)+
@@ -135,7 +143,7 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
for _, muteTiming := range muteTimings {
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
Type: cloudmigration.MuteTimingType,
- RefID: muteTiming.Name,
+ RefID: muteTiming.UID,
Name: muteTiming.Name,
Data: muteTiming,
})
@@ -144,7 +152,7 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
for _, notificationTemplate := range notificationTemplates {
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
Type: cloudmigration.NotificationTemplateType,
- RefID: notificationTemplate.Name,
+ RefID: notificationTemplate.UID,
Name: notificationTemplate.Name,
Data: notificationTemplate,
})
@@ -159,6 +167,14 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
})
}
+ // Notification Policy can only be managed by updating its entire tree, so we send the whole thing as one item.
+ migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
+ Type: cloudmigration.NotificationPolicyType,
+ RefID: notificationPolicies.Name, // no UID available
+ Name: notificationPolicies.Name,
+ Data: notificationPolicies.Routes,
+ })
+
// Obtain the names of parent elements for Dashboard and Folders data types
parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements)
if err != nil {
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
index afc2ca5ec80..cb9eb9f4330 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
@@ -8,11 +8,14 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/featuremgmt"
+ "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/user"
)
type muteTimeInterval struct {
+ UID string `json:"uid"`
+
// There is a lot of custom (de)serialization logic from Alertmanager,
// and this is the same type used by the underlying API, hence we can use the type as-is.
config.MuteTimeInterval `json:",inline"`
@@ -32,6 +35,7 @@ func (s *Service) getAlertMuteTimings(ctx context.Context, signedInUser *user.Si
for _, muteTiming := range muteTimings {
muteTimeIntervals = append(muteTimeIntervals, muteTimeInterval{
+ UID: muteTiming.UID,
MuteTimeInterval: config.MuteTimeInterval{
Name: muteTiming.Name,
TimeIntervals: muteTiming.TimeIntervals,
@@ -43,6 +47,7 @@ func (s *Service) getAlertMuteTimings(ctx context.Context, signedInUser *user.Si
}
type notificationTemplate struct {
+ UID string `json:"uid"`
Name string `json:"name"`
Template string `json:"template"`
}
@@ -61,6 +66,7 @@ func (s *Service) getNotificationTemplates(ctx context.Context, signedInUser *us
for _, template := range templates {
notificationTemplates = append(notificationTemplates, notificationTemplate{
+ UID: template.UID,
Name: template.Name,
Template: template.Template,
})
@@ -106,3 +112,24 @@ func (s *Service) getContactPoints(ctx context.Context, signedInUser *user.Signe
return contactPoints, nil
}
+
+type notificationPolicy struct {
+ Name string
+ Routes definitions.Route
+}
+
+func (s *Service) getNotificationPolicies(ctx context.Context, signedInUser *user.SignedInUser) (notificationPolicy, error) {
+ if !s.features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrationsAlerts) {
+ return notificationPolicy{}, nil
+ }
+
+ policyTree, _, err := s.ngAlert.Api.Policies.GetPolicyTree(ctx, signedInUser.GetOrgID())
+ if err != nil {
+ return notificationPolicy{}, fmt.Errorf("fetching ngalert notification policy tree: %w", err)
+ }
+
+ return notificationPolicy{
+ Name: "Notification Policy Tree",
+ Routes: policyTree,
+ }, nil
+}
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
index 058f7e5b257..55b08fb3cbc 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
@@ -5,8 +5,10 @@ import (
"encoding/json"
"testing"
+ "github.com/prometheus/alertmanager/pkg/labels"
"github.com/stretchr/testify/require"
+ "github.com/grafana/alerting/definition"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -32,10 +34,9 @@ func TestGetAlertMuteTimings(t *testing.T) {
s := setUpServiceTest(t, false).(*Service)
s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations, featuremgmt.FlagOnPremToCloudMigrationsAlerts)
- var orgID int64 = 1
- user := &user.SignedInUser{OrgID: orgID}
+ user := &user.SignedInUser{OrgID: 1}
- createdMuteTiming := createMuteTiming(t, ctx, s, orgID)
+ createdMuteTiming := createMuteTiming(t, ctx, s, user)
muteTimeIntervals, err := s.getAlertMuteTimings(ctx, user)
require.NoError(t, err)
@@ -62,10 +63,9 @@ func TestGetNotificationTemplates(t *testing.T) {
s := setUpServiceTest(t, false).(*Service)
s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations, featuremgmt.FlagOnPremToCloudMigrationsAlerts)
- var orgID int64 = 1
- user := &user.SignedInUser{OrgID: orgID}
+ user := &user.SignedInUser{OrgID: 1}
- createdTemplate := createNotificationTemplate(t, ctx, s, orgID)
+ createdTemplate := createNotificationTemplate(t, ctx, s, user)
notificationTemplates, err := s.getNotificationTemplates(ctx, user)
require.NoError(t, err)
@@ -113,7 +113,41 @@ func TestGetContactPoints(t *testing.T) {
})
}
-func createMuteTiming(t *testing.T, ctx context.Context, service *Service, orgID int64) definitions.MuteTimeInterval {
+func TestGetNotificationPolicies(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ t.Cleanup(cancel)
+
+ t.Run("when the feature flag `onPremToCloudMigrationsAlerts` is not enabled it returns nil", func(t *testing.T) {
+ s := setUpServiceTest(t, false).(*Service)
+ s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations)
+
+ notificationPolicies, err := s.getNotificationPolicies(ctx, nil)
+ require.NoError(t, err)
+ require.Empty(t, notificationPolicies)
+ })
+
+ t.Run("when the feature flag `onPremToCloudMigrationsAlerts` is enabled it returns the contact points", func(t *testing.T) {
+ s := setUpServiceTest(t, false).(*Service)
+ s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations, featuremgmt.FlagOnPremToCloudMigrationsAlerts)
+
+ user := &user.SignedInUser{OrgID: 1}
+
+ muteTiming := createMuteTiming(t, ctx, s, user)
+ require.NotEmpty(t, muteTiming.Name)
+
+ contactPoints := createContactPoints(t, ctx, s, user)
+ require.GreaterOrEqual(t, len(contactPoints), 1)
+
+ updateNotificationPolicyTree(t, ctx, s, user, contactPoints[0].Name, muteTiming.Name)
+
+ notificationPolicies, err := s.getNotificationPolicies(ctx, user)
+ require.NoError(t, err)
+ require.NotEmpty(t, notificationPolicies.Routes.Receiver)
+ require.NotNil(t, notificationPolicies.Routes.Routes)
+ })
+}
+
+func createMuteTiming(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.MuteTimeInterval {
t.Helper()
muteTiming := `{
@@ -133,13 +167,13 @@ func createMuteTiming(t *testing.T, ctx context.Context, service *Service, orgID
var mt definitions.MuteTimeInterval
require.NoError(t, json.Unmarshal([]byte(muteTiming), &mt))
- createdTiming, err := service.ngAlert.Api.MuteTimings.CreateMuteTiming(ctx, mt, orgID)
+ createdTiming, err := service.ngAlert.Api.MuteTimings.CreateMuteTiming(ctx, mt, user.GetOrgID())
require.NoError(t, err)
return createdTiming
}
-func createNotificationTemplate(t *testing.T, ctx context.Context, service *Service, orgID int64) definitions.NotificationTemplate {
+func createNotificationTemplate(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.NotificationTemplate {
t.Helper()
tmpl := definitions.NotificationTemplate{
@@ -147,7 +181,7 @@ func createNotificationTemplate(t *testing.T, ctx context.Context, service *Serv
Template: "This is a test template\n{{ .ExternalURL }}",
}
- createdTemplate, err := service.ngAlert.Api.Templates.CreateTemplate(ctx, orgID, tmpl)
+ createdTemplate, err := service.ngAlert.Api.Templates.CreateTemplate(ctx, user.GetOrgID(), tmpl)
require.NoError(t, err)
return createdTemplate
@@ -205,3 +239,25 @@ func createContactPoints(t *testing.T, ctx context.Context, service *Service, us
createdTelegram,
}
}
+
+func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, receiverGroup, muteTiming string) {
+ t.Helper()
+
+ child := definition.Route{
+ Continue: true,
+ MuteTimeIntervals: []string{muteTiming},
+ ObjectMatchers: definition.ObjectMatchers{
+ {Name: "label1", Type: labels.MatchEqual, Value: "value1"},
+ {Name: "label2", Type: labels.MatchNotEqual, Value: "value2"},
+ },
+ Receiver: receiverGroup,
+ }
+
+ tree := definition.Route{
+ Receiver: "grafana-default-email",
+ Routes: []*definition.Route{&child},
+ }
+
+ err := service.ngAlert.Api.Policies.UpdatePolicyTree(ctx, user.GetOrgID(), tree, "", "")
+ require.NoError(t, err)
+}
diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
index 62a807c3a4c..a9344da0f15 100644
--- a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
@@ -225,6 +225,8 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) {
return ;
case 'CONTACT_POINT':
return ;
+ case 'NOTIFICATION_POLICY':
+ return ;
default:
return undefined;
}
diff --git a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
index 50ada9b4d55..8cd71722c00 100644
--- a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
@@ -19,6 +19,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) {
return t('migrate-to-cloud.resource-type.notification_template', 'Notification Template');
case 'CONTACT_POINT':
return t('migrate-to-cloud.resource-type.contact_point', 'Contact Point');
+ case 'NOTIFICATION_POLICY':
+ return t('migrate-to-cloud.resource-type.notification_policy', 'Notification Policy');
default:
return t('migrate-to-cloud.resource-type.unknown', 'Unknown');
}
diff --git a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
index aeeec493993..79e95ddc5f4 100644
--- a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
@@ -58,6 +58,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) {
types.push(t('migrate-to-cloud.migrated-counts.notification_templates', 'notification templates'));
} else if (type === 'CONTACT_POINT') {
types.push(t('migrate-to-cloud.migrated-counts.contact_points', 'contact points'));
+ } else if (type === 'NOTIFICATION_POLICY') {
+ types.push(t('migrate-to-cloud.migrated-counts.notification_policies', 'notification policies'));
}
distinctItems += 1;
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index af80b2fbcf9..780b37f7ab6 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -1416,6 +1416,7 @@
"folders": "folders",
"library_elements": "library elements",
"mute_timings": "mute timings",
+ "notification_policies": "notification policies",
"notification_templates": "notification templates"
},
"migration-token": {
@@ -1502,6 +1503,7 @@
"folder": "Folder",
"library_element": "Library Element",
"mute_timing": "Mute Timing",
+ "notification_policy": "Notification Policy",
"notification_template": "Notification Template",
"unknown": "Unknown"
},
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 4e3394f0b41..4b10a1e4a89 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -1416,6 +1416,7 @@
"folders": "ƒőľđęřş",
"library_elements": "ľįþřäřy ęľęmęʼnŧş",
"mute_timings": "mūŧę ŧįmįʼnģş",
+ "notification_policies": "ʼnőŧįƒįčäŧįőʼn pőľįčįęş",
"notification_templates": "ʼnőŧįƒįčäŧįőʼn ŧęmpľäŧęş"
},
"migration-token": {
@@ -1502,6 +1503,7 @@
"folder": "Főľđęř",
"library_element": "Ŀįþřäřy Ēľęmęʼnŧ",
"mute_timing": "Mūŧę Ŧįmįʼnģ",
+ "notification_policy": "Ńőŧįƒįčäŧįőʼn Pőľįčy",
"notification_template": "Ńőŧįƒįčäŧįőʼn Ŧęmpľäŧę",
"unknown": "Ůʼnĸʼnőŵʼn"
},