diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
index cbe6b060754..15438259240 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
@@ -783,7 +783,7 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Servi
kvStore := kvstore.ProvideService(sqlStore)
bus := bus.ProvideBus(tracer)
- fakeAccessControl := actest.FakeAccessControl{}
+ fakeAccessControl := actest.FakeAccessControl{ExpectedEvaluate: true}
fakeAccessControlService := actest.FakeService{}
alertMetrics := metrics.NewNGAlert(prometheus.NewRegistry())
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
index 952106e1608..fd977a85be7 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
@@ -34,6 +34,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{
cloudmigration.DashboardDataType,
cloudmigration.MuteTimingType,
cloudmigration.NotificationTemplateType,
+ cloudmigration.ContactPointType,
}
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
@@ -74,10 +75,17 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
return nil, err
}
+ // Alerts: Contact Points
+ contactPoints, err := s.getContactPoints(ctx, signedInUser)
+ if err != nil {
+ s.log.Error("Failed to get alert contact points", "err", err)
+ return nil, err
+ }
+
migrationDataSlice := make(
[]cloudmigration.MigrateDataRequestItem, 0,
len(dataSources)+len(dashs)+len(folders)+len(libraryElements)+
- len(muteTimings)+len(notificationTemplates),
+ len(muteTimings)+len(notificationTemplates)+len(contactPoints),
)
for _, ds := range dataSources {
@@ -142,6 +150,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
})
}
+ for _, contactPoint := range contactPoints {
+ migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
+ Type: cloudmigration.ContactPointType,
+ RefID: contactPoint.UID,
+ Name: contactPoint.Name,
+ Data: contactPoint,
+ })
+ }
+
// 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 94879e227f2..afc2ca5ec80 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go
@@ -6,7 +6,9 @@ import (
"github.com/prometheus/alertmanager/config"
+ "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/featuremgmt"
+ "github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/user"
)
@@ -66,3 +68,41 @@ func (s *Service) getNotificationTemplates(ctx context.Context, signedInUser *us
return notificationTemplates, nil
}
+
+type contactPoint struct {
+ Settings *simplejson.Json `json:"settings"`
+ UID string `json:"uid"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ DisableResolveMessage bool `json:"disableResolveMessage"`
+}
+
+func (s *Service) getContactPoints(ctx context.Context, signedInUser *user.SignedInUser) ([]contactPoint, error) {
+ if !s.features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrationsAlerts) {
+ return nil, nil
+ }
+
+ query := provisioning.ContactPointQuery{
+ OrgID: signedInUser.GetOrgID(),
+ Decrypt: true, // needed to recreate the settings in the target instance.
+ }
+
+ embeddedContactPoints, err := s.ngAlert.Api.ContactPointService.GetContactPoints(ctx, query, signedInUser)
+ if err != nil {
+ return nil, fmt.Errorf("fetching ngalert contact points: %w", err)
+ }
+
+ contactPoints := make([]contactPoint, 0, len(embeddedContactPoints))
+
+ for _, embeddedContactPoint := range embeddedContactPoints {
+ contactPoints = append(contactPoints, contactPoint{
+ UID: embeddedContactPoint.UID,
+ Name: embeddedContactPoint.Name,
+ Type: embeddedContactPoint.Type,
+ Settings: embeddedContactPoint.Settings,
+ DisableResolveMessage: embeddedContactPoint.DisableResolveMessage,
+ })
+ }
+
+ return contactPoints, nil
+}
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
index e1fed6720f9..058f7e5b257 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go
@@ -7,7 +7,10 @@ import (
"github.com/stretchr/testify/require"
+ "github.com/grafana/grafana/pkg/components/simplejson"
+ "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt"
+ ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/user"
)
@@ -72,6 +75,44 @@ func TestGetNotificationTemplates(t *testing.T) {
})
}
+func TestGetContactPoints(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)
+
+ contactPoints, err := s.getContactPoints(ctx, nil)
+ require.NoError(t, err)
+ require.Nil(t, contactPoints)
+ })
+
+ 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,
+ Permissions: map[int64]map[string][]string{
+ 1: {
+ accesscontrol.ActionAlertingNotificationsRead: nil,
+ accesscontrol.ActionAlertingReceiversReadSecrets: {ac.ScopeReceiversAll},
+ },
+ },
+ }
+
+ defaultEmailContactPointCount := 1
+
+ createdContactPoints := createContactPoints(t, ctx, s, user)
+
+ contactPoints, err := s.getContactPoints(ctx, user)
+ require.NoError(t, err)
+ require.NotNil(t, contactPoints)
+ require.Len(t, contactPoints, len(createdContactPoints)+defaultEmailContactPointCount)
+ })
+}
+
func createMuteTiming(t *testing.T, ctx context.Context, service *Service, orgID int64) definitions.MuteTimeInterval {
t.Helper()
@@ -99,6 +140,8 @@ func createMuteTiming(t *testing.T, ctx context.Context, service *Service, orgID
}
func createNotificationTemplate(t *testing.T, ctx context.Context, service *Service, orgID int64) definitions.NotificationTemplate {
+ t.Helper()
+
tmpl := definitions.NotificationTemplate{
Name: "MyTestNotificationTemplate",
Template: "This is a test template\n{{ .ExternalURL }}",
@@ -109,3 +152,56 @@ func createNotificationTemplate(t *testing.T, ctx context.Context, service *Serv
return createdTemplate
}
+
+func createContactPoints(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) []definitions.EmbeddedContactPoint {
+ t.Helper()
+
+ slackSettings, err := simplejson.NewJson([]byte(`{
+ "icon_emoji":"iconemoji",
+ "icon_url":"iconurl",
+ "recipient":"recipient",
+ "token":"slack-secret",
+ "username":"user"
+ }`))
+ require.NoError(t, err)
+
+ telegramSettings, err := simplejson.NewJson([]byte(`{
+ "bottoken":"telegram-secret",
+ "chatid":"chat-id",
+ "disable_notification":true,
+ "disable_web_page_preview":false,
+ "message_thread_id":"1234",
+ "parse_mode":"None",
+ "protect_content":true
+ }`))
+ require.NoError(t, err)
+
+ nameGroup := "group_1"
+
+ slackContactPoint := definitions.EmbeddedContactPoint{
+ Name: nameGroup,
+ Type: "slack",
+ Settings: slackSettings,
+ DisableResolveMessage: false,
+ Provenance: "",
+ }
+
+ createdSlack, err := service.ngAlert.Api.ContactPointService.CreateContactPoint(ctx, user.GetOrgID(), user, slackContactPoint, "")
+ require.NoError(t, err)
+
+ telegramContactPoint := definitions.EmbeddedContactPoint{
+ Name: nameGroup,
+ Type: "telegram",
+ Settings: telegramSettings,
+ DisableResolveMessage: false,
+ Provenance: "",
+ }
+
+ createdTelegram, err := service.ngAlert.Api.ContactPointService.CreateContactPoint(ctx, user.GetOrgID(), user, telegramContactPoint, "")
+ require.NoError(t, err)
+
+ return []definitions.EmbeddedContactPoint{
+ createdSlack,
+ createdTelegram,
+ }
+}
diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
index 3f5ea7984ed..62a807c3a4c 100644
--- a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
@@ -220,9 +220,11 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) {
case 'LIBRARY_ELEMENT':
return ;
case 'MUTE_TIMING':
- return ;
+ return ;
case 'NOTIFICATION_TEMPLATE':
- return ;
+ return ;
+ case 'CONTACT_POINT':
+ 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 afecc880de4..50ada9b4d55 100644
--- a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx
@@ -17,6 +17,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) {
return t('migrate-to-cloud.resource-type.mute_timing', 'Mute Timing');
case 'NOTIFICATION_TEMPLATE':
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');
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 e83c6c5b553..aeeec493993 100644
--- a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx
@@ -56,6 +56,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) {
types.push(t('migrate-to-cloud.migrated-counts.mute_timings', 'mute timings'));
} else if (type === 'NOTIFICATION_TEMPLATE') {
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'));
}
distinctItems += 1;
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 09aeebf2b0d..af80b2fbcf9 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -1410,6 +1410,7 @@
"title": "Let us help you migrate to this stack"
},
"migrated-counts": {
+ "contact_points": "contact points",
"dashboards": "dashboards",
"datasources": "data sources",
"folders": "folders",
@@ -1495,6 +1496,7 @@
"unknown-datasource-type": "Unknown data source"
},
"resource-type": {
+ "contact_point": "Contact Point",
"dashboard": "Dashboard",
"datasource": "Data source",
"folder": "Folder",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 281d8fd3cbf..4e3394f0b41 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -1410,6 +1410,7 @@
"title": "Ŀęŧ ūş ĥęľp yőū mįģřäŧę ŧő ŧĥįş şŧäčĸ"
},
"migrated-counts": {
+ "contact_points": "čőʼnŧäčŧ pőįʼnŧş",
"dashboards": "đäşĥþőäřđş",
"datasources": "đäŧä şőūřčęş",
"folders": "ƒőľđęřş",
@@ -1495,6 +1496,7 @@
"unknown-datasource-type": "Ůʼnĸʼnőŵʼn đäŧä şőūřčę"
},
"resource-type": {
+ "contact_point": "Cőʼnŧäčŧ Pőįʼnŧ",
"dashboard": "Đäşĥþőäřđ",
"datasource": "Đäŧä şőūřčę",
"folder": "Főľđęř",