mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
CloudMigrations: create snapshot for Mute Timings (#94668)
* CloudMigrations: Create snapshot for Mute Timings * CloudMigrations: add mute timings icon and copies to frontend
This commit is contained in:
parent
5fb685dcc6
commit
081ec57443
@ -34,6 +34,7 @@ import (
|
|||||||
libraryelements "github.com/grafana/grafana/pkg/services/libraryelements/model"
|
libraryelements "github.com/grafana/grafana/pkg/services/libraryelements/model"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
@ -773,7 +774,11 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Servi
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
featureToggles := featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations, featuremgmt.FlagDashboardRestore)
|
featureToggles := featuremgmt.WithFeatures(
|
||||||
|
featuremgmt.FlagOnPremToCloudMigrations,
|
||||||
|
featuremgmt.FlagOnPremToCloudMigrationsAlerts,
|
||||||
|
featuremgmt.FlagDashboardRestore, // needed for skipping creating soft-deleted dashboards in the snapshot.
|
||||||
|
)
|
||||||
|
|
||||||
kvStore := kvstore.ProvideService(sqlStore)
|
kvStore := kvstore.ProvideService(sqlStore)
|
||||||
|
|
||||||
@ -793,12 +798,37 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Servi
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var validConfig = `{
|
||||||
|
"template_files": {
|
||||||
|
"a": "template"
|
||||||
|
},
|
||||||
|
"alertmanager_config": {
|
||||||
|
"route": {
|
||||||
|
"receiver": "grafana-default-email"
|
||||||
|
},
|
||||||
|
"receivers": [{
|
||||||
|
"name": "grafana-default-email",
|
||||||
|
"grafana_managed_receiver_configs": [{
|
||||||
|
"uid": "",
|
||||||
|
"name": "email receiver",
|
||||||
|
"type": "email",
|
||||||
|
"settings": {
|
||||||
|
"addresses": "<example@email.com>"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
require.NoError(t, ng.Api.AlertingStore.SaveAlertmanagerConfiguration(context.Background(), &models.SaveAlertmanagerConfigurationCmd{
|
||||||
|
AlertmanagerConfiguration: validConfig,
|
||||||
|
OrgID: 1,
|
||||||
|
LastApplied: time.Now().Unix(),
|
||||||
|
}))
|
||||||
|
|
||||||
s, err := ProvideService(
|
s, err := ProvideService(
|
||||||
cfg,
|
cfg,
|
||||||
httpclient.NewProvider(),
|
httpclient.NewProvider(),
|
||||||
featuremgmt.WithFeatures(
|
featureToggles,
|
||||||
featuremgmt.FlagOnPremToCloudMigrations,
|
|
||||||
featuremgmt.FlagDashboardRestore),
|
|
||||||
sqlStore,
|
sqlStore,
|
||||||
dsService,
|
dsService,
|
||||||
secretskv.NewFakeSQLSecretsKVStore(t, sqlStore),
|
secretskv.NewFakeSQLSecretsKVStore(t, sqlStore),
|
||||||
|
@ -32,6 +32,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{
|
|||||||
cloudmigration.FolderDataType,
|
cloudmigration.FolderDataType,
|
||||||
cloudmigration.LibraryElementDataType,
|
cloudmigration.LibraryElementDataType,
|
||||||
cloudmigration.DashboardDataType,
|
cloudmigration.DashboardDataType,
|
||||||
|
cloudmigration.MuteTimingType,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
|
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
|
||||||
@ -58,9 +59,16 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alerts: Mute Timings
|
||||||
|
muteTimings, err := s.getAlertMuteTimings(ctx, signedInUser)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to get alert mute timings", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
migrationDataSlice := make(
|
migrationDataSlice := make(
|
||||||
[]cloudmigration.MigrateDataRequestItem, 0,
|
[]cloudmigration.MigrateDataRequestItem, 0,
|
||||||
len(dataSources)+len(dashs)+len(folders)+len(libraryElements),
|
len(dataSources)+len(dashs)+len(folders)+len(libraryElements)+len(muteTimings),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, ds := range dataSources {
|
for _, ds := range dataSources {
|
||||||
@ -107,6 +115,15 @@ 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,
|
||||||
|
Name: muteTiming.Name,
|
||||||
|
Data: muteTiming,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain the names of parent elements for Dashboard and Folders data types
|
// Obtain the names of parent elements for Dashboard and Folders data types
|
||||||
parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements)
|
parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package cloudmigrationimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/alertmanager/config"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type muteTimeInterval struct {
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getAlertMuteTimings(ctx context.Context, signedInUser *user.SignedInUser) ([]muteTimeInterval, error) {
|
||||||
|
if !s.features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrationsAlerts) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
muteTimings, err := s.ngAlert.Api.MuteTimings.GetMuteTimings(ctx, signedInUser.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetching ngalert mute timings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
muteTimeIntervals := make([]muteTimeInterval, 0, len(muteTimings))
|
||||||
|
|
||||||
|
for _, muteTiming := range muteTimings {
|
||||||
|
muteTimeIntervals = append(muteTimeIntervals, muteTimeInterval{
|
||||||
|
MuteTimeInterval: config.MuteTimeInterval{
|
||||||
|
Name: muteTiming.Name,
|
||||||
|
TimeIntervals: muteTiming.TimeIntervals,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return muteTimeIntervals, nil
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package cloudmigrationimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAlertMuteTimings(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)
|
||||||
|
|
||||||
|
muteTimeIntervals, err := s.getAlertMuteTimings(ctx, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, muteTimeIntervals)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the feature flag `onPremToCloudMigrationsAlerts` is enabled it returns the mute timings", func(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}
|
||||||
|
|
||||||
|
createdMuteTiming := createMuteTiming(t, ctx, s, orgID)
|
||||||
|
|
||||||
|
muteTimeIntervals, err := s.getAlertMuteTimings(ctx, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, muteTimeIntervals)
|
||||||
|
require.Len(t, muteTimeIntervals, 1)
|
||||||
|
require.Equal(t, createdMuteTiming.Name, muteTimeIntervals[0].Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMuteTiming(t *testing.T, ctx context.Context, service *Service, orgID int64) definitions.MuteTimeInterval {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
muteTiming := `{
|
||||||
|
"name": "My Unique MuteTiming 1",
|
||||||
|
"time_intervals": [
|
||||||
|
{
|
||||||
|
"times": [{"start_time": "12:12","end_time": "23:23"}],
|
||||||
|
"weekdays": ["monday","wednesday","friday","sunday"],
|
||||||
|
"days_of_month": ["10:20","25:-1"],
|
||||||
|
"months": ["1:6","10:12"],
|
||||||
|
"years": ["2022:2054"],
|
||||||
|
"location": "Africa/Douala"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
var mt definitions.MuteTimeInterval
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(muteTiming), &mt))
|
||||||
|
|
||||||
|
createdTiming, err := service.ngAlert.Api.MuteTimings.CreateMuteTiming(ctx, mt, orgID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return createdTiming
|
||||||
|
}
|
@ -219,6 +219,8 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) {
|
|||||||
return <Icon size="xl" name="database" />;
|
return <Icon size="xl" name="database" />;
|
||||||
case 'LIBRARY_ELEMENT':
|
case 'LIBRARY_ELEMENT':
|
||||||
return <Icon size="xl" name="library-panel" />;
|
return <Icon size="xl" name="library-panel" />;
|
||||||
|
case 'MUTE_TIMING':
|
||||||
|
return <Icon size="xl" name="bell-slash" />;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) {
|
|||||||
return t('migrate-to-cloud.resource-type.folder', 'Folder');
|
return t('migrate-to-cloud.resource-type.folder', 'Folder');
|
||||||
case 'LIBRARY_ELEMENT':
|
case 'LIBRARY_ELEMENT':
|
||||||
return t('migrate-to-cloud.resource-type.library_element', 'Library Element');
|
return t('migrate-to-cloud.resource-type.library_element', 'Library Element');
|
||||||
|
case 'MUTE_TIMING':
|
||||||
|
return t('migrate-to-cloud.resource-type.mute_timing', 'Mute Timing');
|
||||||
default:
|
default:
|
||||||
return t('migrate-to-cloud.resource-type.unknown', 'Unknown');
|
return t('migrate-to-cloud.resource-type.unknown', 'Unknown');
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) {
|
|||||||
types.push(t('migrate-to-cloud.migrated-counts.folders', 'folders'));
|
types.push(t('migrate-to-cloud.migrated-counts.folders', 'folders'));
|
||||||
} else if (type === 'LIBRARY_ELEMENT') {
|
} else if (type === 'LIBRARY_ELEMENT') {
|
||||||
types.push(t('migrate-to-cloud.migrated-counts.library_elements', 'library elements'));
|
types.push(t('migrate-to-cloud.migrated-counts.library_elements', 'library elements'));
|
||||||
|
} else if (type === 'MUTE_TIMING') {
|
||||||
|
types.push(t('migrate-to-cloud.migrated-counts.mute_timings', 'mute timings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
distinctItems += 1;
|
distinctItems += 1;
|
||||||
|
@ -1408,7 +1408,8 @@
|
|||||||
"dashboards": "dashboards",
|
"dashboards": "dashboards",
|
||||||
"datasources": "data sources",
|
"datasources": "data sources",
|
||||||
"folders": "folders",
|
"folders": "folders",
|
||||||
"library_elements": "library elements"
|
"library_elements": "library elements",
|
||||||
|
"mute_timings": "mute timings"
|
||||||
},
|
},
|
||||||
"migration-token": {
|
"migration-token": {
|
||||||
"delete-button": "Delete token",
|
"delete-button": "Delete token",
|
||||||
@ -1492,6 +1493,7 @@
|
|||||||
"datasource": "Data source",
|
"datasource": "Data source",
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
"library_element": "Library Element",
|
"library_element": "Library Element",
|
||||||
|
"mute_timing": "Mute Timing",
|
||||||
"unknown": "Unknown"
|
"unknown": "Unknown"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
|
@ -1408,7 +1408,8 @@
|
|||||||
"dashboards": "đäşĥþőäřđş",
|
"dashboards": "đäşĥþőäřđş",
|
||||||
"datasources": "đäŧä şőūřčęş",
|
"datasources": "đäŧä şőūřčęş",
|
||||||
"folders": "ƒőľđęřş",
|
"folders": "ƒőľđęřş",
|
||||||
"library_elements": "ľįþřäřy ęľęmęʼnŧş"
|
"library_elements": "ľįþřäřy ęľęmęʼnŧş",
|
||||||
|
"mute_timings": "mūŧę ŧįmįʼnģş"
|
||||||
},
|
},
|
||||||
"migration-token": {
|
"migration-token": {
|
||||||
"delete-button": "Đęľęŧę ŧőĸęʼn",
|
"delete-button": "Đęľęŧę ŧőĸęʼn",
|
||||||
@ -1492,6 +1493,7 @@
|
|||||||
"datasource": "Đäŧä şőūřčę",
|
"datasource": "Đäŧä şőūřčę",
|
||||||
"folder": "Főľđęř",
|
"folder": "Főľđęř",
|
||||||
"library_element": "Ŀįþřäřy Ēľęmęʼnŧ",
|
"library_element": "Ŀįþřäřy Ēľęmęʼnŧ",
|
||||||
|
"mute_timing": "Mūŧę Ŧįmįʼnģ",
|
||||||
"unknown": "Ůʼnĸʼnőŵʼn"
|
"unknown": "Ůʼnĸʼnőŵʼn"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
|
Loading…
Reference in New Issue
Block a user