diff --git a/conf/defaults.ini b/conf/defaults.ini index 1061677bee4..55378a7f6ee 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1515,6 +1515,16 @@ max_age = # Configures max number of alert annotations that Grafana stores. Default value is 0, which keeps all alert annotations. max_annotations_to_keep = +[unified_alerting.prometheus_conversion] +# Configuration options for converting Prometheus alerting and recording rules to Grafana rules. +# These settings affect rules created via the Prometheus conversion API. + +# Offset the rule evaluation time for imported rules by a specified duration in the past. +# This offset is applied and saved to the rule query during the conversion process from Prometheus to Grafana format. +# The setting only affects rules imported after the configuration change is made and does not modify existing rules. +# Accepts duration formats like: 30s, 1m, 1h. +rule_query_offset = 1m + [recording_rules] # Enable recording rules. You must provide write credentials below. enabled = false diff --git a/conf/sample.ini b/conf/sample.ini index 4cecf657e42..ea2cb9a00df 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1496,6 +1496,16 @@ max_age = # Configures max number of alert annotations that Grafana stores. Default value is 0, which keeps all alert annotations. max_annotations_to_keep = +[unified_alerting.prometheus_conversion] +# Configuration options for converting Prometheus alerting and recording rules to Grafana rules. +# These settings affect rules created via the Prometheus conversion API. + +# Offset the rule evaluation time for imported rules by a specified duration in the past. +# This offset is applied and saved to the rule query during the conversion process from Prometheus to Grafana format. +# The setting only affects rules imported after the configuration change is made and does not modify existing rules. +# Accepts duration formats like: 30s, 1m, 1h. +rule_query_offset = 1m + #################################### Recording Rules ##################### [recording_rules] # Enable recording rules. You must provide write credentials below. diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index d9f28509622..5cbe398b459 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -463,6 +463,7 @@ func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup( IsPaused: pauseAlertRules, }, KeepOriginalRuleDefinition: util.Pointer(keepOriginalRuleDefinition), + EvaluationOffset: &srv.cfg.PrometheusConversion.RuleQueryOffset, }, ) if err != nil { diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index dc4017f477e..7e0149ac14a 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -179,6 +179,32 @@ func TestPrometheusRulesToGrafana(t *testing.T) { }, expectError: false, }, + { + name: "when global query offset is set, it should be used", + orgID: 1, + namespace: "some-namespace-uid", + promGroup: PrometheusRuleGroup{ + Name: "test-group-1", + Interval: prommodel.Duration(10 * time.Second), + Rules: []PrometheusRule{ + { + Alert: "alert-1", + Expr: "cpu_usage > 80", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "CPU usage is critical", + }, + }, + }, + }, + config: Config{ + EvaluationOffset: util.Pointer(5 * time.Minute), + }, + expectError: false, + }, } for _, tc := range testCases { @@ -244,8 +270,13 @@ func TestPrometheusRulesToGrafana(t *testing.T) { require.Equal(t, expectedLabels, grafanaRule.Labels, tc.name) require.Equal(t, promRule.Annotations, grafanaRule.Annotations, tc.name) - require.Equal(t, models.Duration(0*time.Minute), grafanaRule.Data[0].RelativeTimeRange.To) - require.Equal(t, models.Duration(10*time.Minute), grafanaRule.Data[0].RelativeTimeRange.From) + + evalOffset := time.Duration(0) + if tc.config.EvaluationOffset != nil { + evalOffset = *tc.config.EvaluationOffset + } + require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To) + require.Equal(t, models.Duration(evalOffset+10*time.Minute), grafanaRule.Data[0].RelativeTimeRange.From) originalRuleDefinition, err := yaml.Marshal(promRule) require.NoError(t, err) diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index b14d22b97bc..84461d3c82f 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -112,6 +112,7 @@ type UnifiedAlertingSettings struct { StateHistory UnifiedAlertingStateHistorySettings RemoteAlertmanager RemoteAlertmanagerSettings RecordingRules RecordingRuleSettings + PrometheusConversion UnifiedAlertingPrometheusConversionSettings // MaxStateSaveConcurrency controls the number of goroutines (per rule) that can save alert state in parallel. MaxStateSaveConcurrency int @@ -165,6 +166,12 @@ type UnifiedAlertingReservedLabelSettings struct { DisabledLabels map[string]struct{} } +// UnifiedAlertingPrometheusConversionSettings contains configuration for converting Prometheus rules to Grafana format +type UnifiedAlertingPrometheusConversionSettings struct { + // RuleQueryOffset defines a time offset to apply to rule queries during conversion from Prometheus to Grafana format + RuleQueryOffset time.Duration +} + type UnifiedAlertingStateHistorySettings struct { Enabled bool Backend string @@ -437,6 +444,11 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { } uaCfg.StateHistory = uaCfgStateHistory + prometheusConversion := iniFile.Section("unified_alerting.prometheus_conversion") + uaCfg.PrometheusConversion = UnifiedAlertingPrometheusConversionSettings{ + RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute), + } + rr := iniFile.Section("recording_rules") uaCfgRecordingRules := RecordingRuleSettings{ Enabled: rr.Key("enabled").MustBool(false),