mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
CloudMigrations: create snapshot for Alert Rules (#95404)
* CloudMigrations: create snapshot for Alert Rules * CloudMigrations: display Alert Rules in resource list summary
This commit is contained in:
parent
6f4a9eb07b
commit
b2de69d741
@ -651,17 +651,17 @@ func TestGetParentNames(t *testing.T) {
|
||||
user := &user.SignedInUser{OrgID: 1}
|
||||
libraryElementFolderUID := "folderUID-A"
|
||||
testcases := []struct {
|
||||
fakeFolders []*folder.Folder
|
||||
folders []folder.CreateFolderCommand
|
||||
dashboards []dashboards.Dashboard
|
||||
libraryElements []libraryElement
|
||||
expectedDashParentNames []string
|
||||
expectedFoldParentNames []string
|
||||
fakeFolders []*folder.Folder
|
||||
folders []folder.CreateFolderCommand
|
||||
dashboards []dashboards.Dashboard
|
||||
libraryElements []libraryElement
|
||||
expectedParentNames map[cloudmigration.MigrateDataType][]string
|
||||
}{
|
||||
{
|
||||
fakeFolders: []*folder.Folder{
|
||||
{UID: "folderUID-A", Title: "Folder A", OrgID: 1, ParentUID: ""},
|
||||
{UID: "folderUID-B", Title: "Folder B", OrgID: 1, ParentUID: "folderUID-A"},
|
||||
{UID: "folderUID-X", Title: "Folder X", OrgID: 1, ParentUID: ""},
|
||||
},
|
||||
folders: []folder.CreateFolderCommand{
|
||||
{UID: "folderUID-C", Title: "Folder A", OrgID: 1, ParentUID: "folderUID-A"},
|
||||
@ -675,8 +675,11 @@ func TestGetParentNames(t *testing.T) {
|
||||
{UID: "libraryElementUID-0", FolderUID: &libraryElementFolderUID},
|
||||
{UID: "libraryElementUID-1"},
|
||||
},
|
||||
expectedDashParentNames: []string{"", "Folder A", "Folder B"},
|
||||
expectedFoldParentNames: []string{"Folder A"},
|
||||
expectedParentNames: map[cloudmigration.MigrateDataType][]string{
|
||||
cloudmigration.DashboardDataType: []string{"", "Folder A", "Folder B"},
|
||||
cloudmigration.FolderDataType: []string{"Folder A"},
|
||||
cloudmigration.LibraryElementDataType: []string{"Folder A"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -686,13 +689,11 @@ func TestGetParentNames(t *testing.T) {
|
||||
dataUIDsToParentNamesByType, err := s.getParentNames(ctx, user, tc.dashboards, tc.folders, tc.libraryElements)
|
||||
require.NoError(t, err)
|
||||
|
||||
resDashParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[cloudmigration.DashboardDataType]))
|
||||
require.Len(t, resDashParentNames, len(tc.expectedDashParentNames))
|
||||
require.ElementsMatch(t, resDashParentNames, tc.expectedDashParentNames)
|
||||
|
||||
resFoldParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[cloudmigration.FolderDataType]))
|
||||
require.Len(t, resFoldParentNames, len(tc.expectedFoldParentNames))
|
||||
require.ElementsMatch(t, resFoldParentNames, tc.expectedFoldParentNames)
|
||||
for dataType, expectedParentNames := range tc.expectedParentNames {
|
||||
actualParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[dataType]))
|
||||
require.Len(t, actualParentNames, len(expectedParentNames))
|
||||
require.ElementsMatch(t, expectedParentNames, actualParentNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,6 +788,8 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Servi
|
||||
fakeAccessControlService := actest.FakeService{}
|
||||
alertMetrics := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
|
||||
cfg.UnifiedAlerting.DefaultRuleEvaluationInterval = time.Minute
|
||||
cfg.UnifiedAlerting.BaseInterval = time.Minute
|
||||
ruleStore, err := ngalertstore.ProvideDBStore(cfg, featureToggles, sqlStore, mockFolder, dashboardService, fakeAccessControl, bus)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -36,6 +36,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{
|
||||
cloudmigration.NotificationTemplateType,
|
||||
cloudmigration.ContactPointType,
|
||||
cloudmigration.NotificationPolicyType,
|
||||
cloudmigration.AlertRuleType,
|
||||
}
|
||||
|
||||
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
|
||||
@ -90,10 +91,17 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Alerts: Alert Rules
|
||||
alertRules, err := s.getAlertRules(ctx, signedInUser)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get alert rules", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
migrationDataSlice := make(
|
||||
[]cloudmigration.MigrateDataRequestItem, 0,
|
||||
len(dataSources)+len(dashs)+len(folders)+len(libraryElements)+
|
||||
len(muteTimings)+len(notificationTemplates)+len(contactPoints),
|
||||
len(muteTimings)+len(notificationTemplates)+len(contactPoints)+len(alertRules),
|
||||
)
|
||||
|
||||
for _, ds := range dataSources {
|
||||
@ -177,6 +185,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
|
||||
})
|
||||
}
|
||||
|
||||
for _, alertRule := range alertRules {
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.AlertRuleType,
|
||||
RefID: alertRule.UID,
|
||||
Name: alertRule.Title,
|
||||
Data: alertRule,
|
||||
})
|
||||
}
|
||||
|
||||
// Obtain the names of parent elements for Dashboard and Folders data types
|
||||
parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements)
|
||||
if err != nil {
|
||||
|
@ -3,11 +3,14 @@ package cloudmigrationimpl
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api"
|
||||
"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"
|
||||
@ -133,3 +136,60 @@ func (s *Service) getNotificationPolicies(ctx context.Context, signedInUser *use
|
||||
Routes: policyTree,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type alertRule struct {
|
||||
Updated time.Time `json:"updated,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Record *definitions.Record `json:"record"`
|
||||
NotificationSettings *definitions.AlertRuleNotificationSettings `json:"notification_settings"`
|
||||
FolderUID string `json:"folderUID"`
|
||||
RuleGroup string `json:"ruleGroup"`
|
||||
NoDataState string `json:"noDataState"`
|
||||
Condition string `json:"condition"`
|
||||
UID string `json:"uid"`
|
||||
Title string `json:"title"`
|
||||
ExecErrState string `json:"execErrState"`
|
||||
Data []definitions.AlertQuery `json:"data"`
|
||||
ID int64 `json:"id"`
|
||||
For model.Duration `json:"for"`
|
||||
OrgID int64 `json:"orgID"`
|
||||
IsPaused bool `json:"isPaused"`
|
||||
}
|
||||
|
||||
func (s *Service) getAlertRules(ctx context.Context, signedInUser *user.SignedInUser) ([]alertRule, error) {
|
||||
if !s.features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrationsAlerts) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
alertRules, _, err := s.ngAlert.Api.AlertRules.GetAlertRules(ctx, signedInUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching alert rules: %w", err)
|
||||
}
|
||||
|
||||
provisionedAlertRules := make([]alertRule, 0, len(alertRules))
|
||||
|
||||
for _, rule := range alertRules {
|
||||
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: rule.IsPaused,
|
||||
NotificationSettings: ngalertapi.AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
|
||||
Record: ngalertapi.ApiRecordFromModelRecord(rule.Record),
|
||||
})
|
||||
}
|
||||
|
||||
return provisionedAlertRules, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"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/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
@ -147,6 +149,34 @@ func TestGetNotificationPolicies(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAlertRules(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)
|
||||
|
||||
alertRules, err := s.getAlertRules(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, alertRules)
|
||||
})
|
||||
|
||||
t.Run("when the feature flag `onPremToCloudMigrationsAlerts` is enabled it returns the alert rules", func(t *testing.T) {
|
||||
s := setUpServiceTest(t, false).(*Service)
|
||||
s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations, featuremgmt.FlagOnPremToCloudMigrationsAlerts)
|
||||
|
||||
user := &user.SignedInUser{OrgID: 1}
|
||||
|
||||
alertRule := createAlertRule(t, ctx, s, user)
|
||||
|
||||
alertRules, err := s.getAlertRules(ctx, user)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, alertRules, 1)
|
||||
require.Equal(t, alertRule.UID, alertRules[0].UID)
|
||||
})
|
||||
}
|
||||
|
||||
func createMuteTiming(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.MuteTimeInterval {
|
||||
t.Helper()
|
||||
|
||||
@ -261,3 +291,34 @@ func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Se
|
||||
_, _, err := service.ngAlert.Api.Policies.UpdatePolicyTree(ctx, user.GetOrgID(), tree, "", "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) models.AlertRule {
|
||||
t.Helper()
|
||||
|
||||
rule := models.AlertRule{
|
||||
OrgID: user.GetOrgID(),
|
||||
Title: "Alert Rule SLO",
|
||||
NamespaceUID: "folderUID",
|
||||
Condition: "A",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
Model: []byte(`{"queryType": "a"}`),
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(60),
|
||||
To: models.Duration(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
RuleGroup: "ruleGroup",
|
||||
For: time.Minute,
|
||||
IntervalSeconds: 60,
|
||||
NoDataState: models.OK,
|
||||
ExecErrState: models.OkErrState,
|
||||
}
|
||||
|
||||
createdRule, err := service.ngAlert.Api.AlertRules.CreateAlertRule(ctx, user, rule, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
return createdRule
|
||||
}
|
||||
|
@ -227,6 +227,8 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) {
|
||||
return <Icon size="xl" name="bell" />;
|
||||
case 'NOTIFICATION_POLICY':
|
||||
return <Icon size="xl" name="bell" />;
|
||||
case 'ALERT_RULE':
|
||||
return <Icon size="xl" name="bell" />;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) {
|
||||
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');
|
||||
case 'ALERT_RULE':
|
||||
return t('migrate-to-cloud.resource-type.alert_rule', 'Alert Rule');
|
||||
default:
|
||||
return t('migrate-to-cloud.resource-type.unknown', 'Unknown');
|
||||
}
|
||||
|
@ -60,6 +60,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) {
|
||||
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'));
|
||||
} else if (type === 'ALERT_RULE') {
|
||||
types.push(t('migrate-to-cloud.migrated-counts.alert_rules', 'alert rules'));
|
||||
}
|
||||
|
||||
distinctItems += 1;
|
||||
|
@ -1552,6 +1552,7 @@
|
||||
"title": "Let us help you migrate to this stack"
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rules": "alert rules",
|
||||
"contact_points": "contact points",
|
||||
"dashboards": "dashboards",
|
||||
"datasources": "data sources",
|
||||
@ -1652,6 +1653,7 @@
|
||||
"unknown-datasource-type": "Unknown data source"
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "Alert Rule",
|
||||
"contact_point": "Contact Point",
|
||||
"dashboard": "Dashboard",
|
||||
"datasource": "Data source",
|
||||
|
@ -1552,6 +1552,7 @@
|
||||
"title": "Ŀęŧ ūş ĥęľp yőū mįģřäŧę ŧő ŧĥįş şŧäčĸ"
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rules": "äľęřŧ řūľęş",
|
||||
"contact_points": "čőʼnŧäčŧ pőįʼnŧş",
|
||||
"dashboards": "đäşĥþőäřđş",
|
||||
"datasources": "đäŧä şőūřčęş",
|
||||
@ -1652,6 +1653,7 @@
|
||||
"unknown-datasource-type": "Ůʼnĸʼnőŵʼn đäŧä şőūřčę"
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "Åľęřŧ Ŗūľę",
|
||||
"contact_point": "Cőʼnŧäčŧ Pőįʼnŧ",
|
||||
"dashboard": "Đäşĥþőäřđ",
|
||||
"datasource": "Đäŧä şőūřčę",
|
||||
|
Loading…
Reference in New Issue
Block a user