Alerting: Support for active time intervals in notification policies (#104252)

* add active_time_intervals to route model

* update k8s compat layer

* update notification policies service to validate active time intervals

* update integration tests

* update openapi

* add active time interval to model

* update route generator to include active time interval

* Update storage list and rename methods to handle active intervals

* update api model

* update provisioning and export models

* update ui to allow active timing config

* update i18n

* fix snapshots for ui tests

* run prettier

* Alerting: Active time intervals UI naming (#104402)

* update naming in UI

* update naming in the edit page title

* update translations

* update alerting module

---------

Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
Yuri Tseretyan
2025-05-07 19:19:33 -04:00
committed by GitHub
co-authored by Sonia Aguilar Sonia Aguilar
parent a8dee54aa4
commit 3e2296acd3
61 changed files with 631 additions and 338 deletions
@@ -51,13 +51,14 @@ func ConvertToK8sResource(orgID int64, r definitions.Route, version string, name
func convertRouteToK8sSubRoute(r *definitions.Route) model.Route {
result := model.Route{
GroupBy: r.GroupByStr,
MuteTimeIntervals: r.MuteTimeIntervals,
Continue: r.Continue,
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
Routes: make([]model.Route, 0, len(r.Routes)),
GroupBy: r.GroupByStr,
MuteTimeIntervals: r.MuteTimeIntervals,
ActiveTimeIntervals: r.ActiveTimeIntervals,
Continue: r.Continue,
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
Routes: make([]model.Route, 0, len(r.Routes)),
}
if r.Receiver != "" {
result.Receiver = util.Pointer(r.Receiver)
@@ -152,11 +153,12 @@ func convertToDomainModel(obj *model.RoutingTree) (definitions.Route, string, er
func convertK8sSubRouteToRoute(r model.Route, path string) (definitions.Route, []error) {
result := definitions.Route{
GroupByStr: r.GroupBy,
MuteTimeIntervals: r.MuteTimeIntervals,
Routes: make([]*definitions.Route, 0, len(r.Routes)),
Matchers: make(config.Matchers, 0, len(r.Matchers)),
Continue: r.Continue,
GroupByStr: r.GroupBy,
MuteTimeIntervals: r.MuteTimeIntervals,
ActiveTimeIntervals: r.ActiveTimeIntervals,
Routes: make([]*definitions.Route, 0, len(r.Routes)),
Matchers: make(config.Matchers, 0, len(r.Matchers)),
Continue: r.Continue,
}
if r.Receiver != nil {
result.Receiver = *r.Receiver
@@ -630,6 +630,13 @@ func escapeRouteExport(r *definitions.RouteExport) {
}
r.MuteTimeIntervals = &muteTimeIntervals
}
if r.ActiveTimeIntervals != nil {
intervals := make([]string, len(*r.ActiveTimeIntervals))
for i, timeInterval := range *r.ActiveTimeIntervals {
intervals[i] = addEscapeCharactersToString(timeInterval)
}
r.ActiveTimeIntervals = &intervals
}
for i := range r.Routes {
escapeRouteExport(r.Routes[i])
}
@@ -872,7 +872,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, rule1)
insertRule(t, sut, createTestAlertRule("rule2", 1))
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n"
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n"
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
require.Equal(t, 200, response.Status())
@@ -888,7 +888,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, createTestAlertRule("rule2", 1))
rc.Req.Header.Add("Accept", "application/json")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false},{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false},{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
require.Equal(t, 200, response.Status())
@@ -902,7 +902,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, createTestAlertRule("rule2", 1))
rc.Req.Header.Add("Accept", "application/yaml")
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n"
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n"
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
@@ -986,6 +986,7 @@ func TestProvisioningApi(t *testing.T) {
group_interval = "5s"
repeat_interval = "5m"
mute_timings = ["test-mute"]
active_timings = ["test-active"]
}
}
}
@@ -1120,7 +1121,7 @@ func TestProvisioningApi(t *testing.T) {
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule1", 1))
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
rc.Req.Header.Add("Accept", "application/json")
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
@@ -1135,7 +1136,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, createTestAlertRule("rule1", 1))
rc.Req.Header.Add("Accept", "application/yaml")
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n"
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n"
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
@@ -1245,7 +1246,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, rule3)
rc.Req.Header.Add("Accept", "application/json")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Email"}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Email"}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRulesExport(&rc)
require.Equal(t, 200, response.Status())
@@ -1265,7 +1266,7 @@ func TestProvisioningApi(t *testing.T) {
insertRule(t, sut, rule3)
rc.Req.Header.Add("Accept", "application/yaml")
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: groupa\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Email\n - orgId: 1\n name: groupb\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n - orgId: 1\n name: groupb\n folder: Folder Title2\n interval: 1m\n rules:\n - uid: rule3\n title: rule3\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n"
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: groupa\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Email\n - orgId: 1\n name: groupb\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n - orgId: 1\n name: groupb\n folder: Folder Title2\n interval: 1m\n rules:\n - uid: rule3\n title: rule3\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 60\n to: 0\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n notification_settings:\n receiver: Test-Receiver\n group_by:\n - alertname\n - grafana_folder\n - test\n group_wait: 1s\n group_interval: 5s\n repeat_interval: 5m\n mute_time_intervals:\n - test-mute\n active_time_intervals:\n - test-active\n"
response := sut.RouteGetAlertRulesExport(&rc)
require.Equal(t, 200, response.Status())
@@ -1281,7 +1282,7 @@ func TestProvisioningApi(t *testing.T) {
rc.Req.Header.Add("Accept", "application/json")
rc.Req.Form.Set("folderUid", "folder-uid")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRulesExport(&rc)
require.Equal(t, 200, response.Status())
@@ -1298,7 +1299,7 @@ func TestProvisioningApi(t *testing.T) {
rc.Req.Header.Add("Accept", "application/json")
rc.Req.Form.Set("folder_uid", "folder-uid")
rc.Req.Form.Add("folder_uid", "folder-uid2")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRulesExport(&rc)
require.Equal(t, 200, response.Status())
@@ -1316,7 +1317,7 @@ func TestProvisioningApi(t *testing.T) {
rc.Req.Form.Set("folderUid", "folder-uid")
rc.Req.Form.Set("group", "groupa")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRulesExport(&rc)
require.Equal(t, 200, response.Status())
@@ -1354,7 +1355,7 @@ func TestProvisioningApi(t *testing.T) {
rc.Req.Header.Add("Accept", "application/json")
rc.Req.Form.Set("ruleUid", "rule1")
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"]}}]}]}`
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":60,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false,"notification_settings":{"receiver":"Test-Receiver","group_by":["alertname","grafana_folder","test"],"group_wait":"1s","group_interval":"5s","repeat_interval":"5m","mute_time_intervals":["test-mute"],"active_time_intervals":["test-active"]}}]}]}`
response := sut.RouteGetAlertRulesExport(&rc)
@@ -1471,7 +1472,7 @@ func TestProvisioningApi(t *testing.T) {
rc := createTestRequestCtx()
rc.Req.Header.Add("Accept", "application/json")
expectedResponse := `{"apiVersion":1,"policies":[{"orgId":1,"receiver":"default-receiver","group_by":["g1","g2"],"routes":[{"receiver":"nested-receiver","group_by":["g3","g4"],"matchers":["a=\"b\""],"object_matchers":[["foo","=","bar"]],"mute_time_intervals":["interval"],"continue":true,"group_wait":"5m","group_interval":"5m","repeat_interval":"5m"}],"group_wait":"30s","group_interval":"5m","repeat_interval":"1h"}]}`
expectedResponse := `{"apiVersion":1,"policies":[{"orgId":1,"receiver":"default-receiver","group_by":["g1","g2"],"routes":[{"receiver":"nested-receiver","group_by":["g3","g4"],"matchers":["a=\"b\""],"object_matchers":[["foo","=","bar"]],"mute_time_intervals":["interval"],"active_time_intervals":["active"],"continue":true,"group_wait":"5m","group_interval":"5m","repeat_interval":"5m"}],"group_wait":"30s","group_interval":"5m","repeat_interval":"1h"}]}`
response := sut.RouteGetPolicyTreeExport(&rc)
@@ -1485,7 +1486,7 @@ func TestProvisioningApi(t *testing.T) {
rc := createTestRequestCtx()
rc.Req.Header.Add("Accept", "application/yaml")
expectedResponse := "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: default-receiver\n group_by:\n - g1\n - g2\n routes:\n - receiver: nested-receiver\n group_by:\n - g3\n - g4\n matchers:\n - a=\"b\"\n object_matchers:\n - - foo\n - =\n - bar\n mute_time_intervals:\n - interval\n continue: true\n group_wait: 5m\n group_interval: 5m\n repeat_interval: 5m\n group_wait: 30s\n group_interval: 5m\n repeat_interval: 1h\n"
expectedResponse := "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: default-receiver\n group_by:\n - g1\n - g2\n routes:\n - receiver: nested-receiver\n group_by:\n - g3\n - g4\n matchers:\n - a=\"b\"\n object_matchers:\n - - foo\n - =\n - bar\n mute_time_intervals:\n - interval\n active_time_intervals:\n - active\n continue: true\n group_wait: 5m\n group_interval: 5m\n repeat_interval: 5m\n group_wait: 30s\n group_interval: 5m\n repeat_interval: 1h\n"
response := sut.RouteGetPolicyTreeExport(&rc)
@@ -1499,7 +1500,7 @@ func TestProvisioningApi(t *testing.T) {
rc := createTestRequestCtx()
rc.Req.Form.Add("format", "hcl")
expectedResponse := "resource \"grafana_notification_policy\" \"notification_policy_1\" {\n contact_point = \"default-receiver\"\n group_by = [\"g1\", \"g2\"]\n\n policy {\n contact_point = \"nested-receiver\"\n group_by = [\"g3\", \"g4\"]\n\n matcher {\n label = \"foo\"\n match = \"=\"\n value = \"bar\"\n }\n\n mute_timings = [\"interval\"]\n continue = true\n group_wait = \"5m\"\n group_interval = \"5m\"\n repeat_interval = \"5m\"\n }\n\n group_wait = \"30s\"\n group_interval = \"5m\"\n repeat_interval = \"1h\"\n}\n"
expectedResponse := "resource \"grafana_notification_policy\" \"notification_policy_1\" {\n contact_point = \"default-receiver\"\n group_by = [\"g1\", \"g2\"]\n\n policy {\n contact_point = \"nested-receiver\"\n group_by = [\"g3\", \"g4\"]\n\n matcher {\n label = \"foo\"\n match = \"=\"\n value = \"bar\"\n }\n\n mute_timings = [\"interval\"]\n active_timings = [\"active\"]\n continue = true\n group_wait = \"5m\"\n group_interval = \"5m\"\n repeat_interval = \"5m\"\n }\n\n group_wait = \"30s\"\n group_interval = \"5m\"\n repeat_interval = \"1h\"\n}\n"
response := sut.RouteGetPolicyTreeExport(&rc)
@@ -2152,12 +2153,13 @@ func createFakeNotificationPolicyService() *fakeNotificationPolicyService {
Value: "b",
},
},
ObjectMatchers: definitions.ObjectMatchers{{Type: 0, Name: "foo", Value: "bar"}},
MuteTimeIntervals: []string{"interval"},
Continue: true,
GroupWait: &minutes,
GroupInterval: &minutes,
RepeatInterval: &minutes,
ObjectMatchers: definitions.ObjectMatchers{{Type: 0, Name: "foo", Value: "bar"}},
MuteTimeIntervals: []string{"interval"},
ActiveTimeIntervals: []string{"active"},
Continue: true,
GroupWait: &minutes,
GroupInterval: &minutes,
RepeatInterval: &minutes,
}},
},
prov: models.ProvenanceAPI,
@@ -2297,12 +2299,13 @@ func createTestAlertRule(title string, orgID int64) definitions.ProvisionedAlert
NoDataState: definitions.OK,
ExecErrState: definitions.OkErrState,
NotificationSettings: &definitions.AlertRuleNotificationSettings{
Receiver: "Test-Receiver",
GroupBy: []string{"alertname", "grafana_folder", "test"},
GroupWait: util.Pointer(model.Duration(1 * time.Second)),
GroupInterval: util.Pointer(model.Duration(5 * time.Second)),
RepeatInterval: util.Pointer(model.Duration(5 * time.Minute)),
MuteTimeIntervals: []string{"test-mute"},
Receiver: "Test-Receiver",
GroupBy: []string{"alertname", "grafana_folder", "test"},
GroupWait: util.Pointer(model.Duration(1 * time.Second)),
GroupInterval: util.Pointer(model.Duration(5 * time.Second)),
RepeatInterval: util.Pointer(model.Duration(5 * time.Minute)),
MuteTimeIntervals: []string{"test-mute"},
ActiveTimeIntervals: []string{"test-active"},
},
}
}
+22 -18
View File
@@ -367,6 +367,7 @@ func RouteExportFromRoute(route *definitions.Route) *definitions.RouteExport {
ObjectMatchers: route.ObjectMatchers,
ObjectMatchersSlice: matchers,
MuteTimeIntervals: NilIfEmpty(util.Pointer(route.MuteTimeIntervals)),
ActiveTimeIntervals: NilIfEmpty(util.Pointer(route.ActiveTimeIntervals)),
Continue: OmitDefault(util.Pointer(route.Continue)),
GroupWait: toStringIfNotNil(route.GroupWait),
GroupInterval: toStringIfNotNil(route.GroupInterval),
@@ -455,12 +456,13 @@ func AlertRuleNotificationSettingsFromNotificationSettings(ns []models.Notificat
}
m := ns[0]
return &definitions.AlertRuleNotificationSettings{
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: m.GroupWait,
GroupInterval: m.GroupInterval,
RepeatInterval: m.RepeatInterval,
MuteTimeIntervals: m.MuteTimeIntervals,
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: m.GroupWait,
GroupInterval: m.GroupInterval,
RepeatInterval: m.RepeatInterval,
MuteTimeIntervals: m.MuteTimeIntervals,
ActiveTimeIntervals: m.ActiveTimeIntervals,
}
}
@@ -480,12 +482,13 @@ func AlertRuleNotificationSettingsExportFromNotificationSettings(ns []models.Not
}
return &definitions.AlertRuleNotificationSettingsExport{
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: toStringIfNotNil(m.GroupWait),
GroupInterval: toStringIfNotNil(m.GroupInterval),
RepeatInterval: toStringIfNotNil(m.RepeatInterval),
MuteTimeIntervals: m.MuteTimeIntervals,
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: toStringIfNotNil(m.GroupWait),
GroupInterval: toStringIfNotNil(m.GroupInterval),
RepeatInterval: toStringIfNotNil(m.RepeatInterval),
MuteTimeIntervals: m.MuteTimeIntervals,
ActiveTimeIntervals: m.ActiveTimeIntervals,
}
}
@@ -496,12 +499,13 @@ func NotificationSettingsFromAlertRuleNotificationSettings(ns *definitions.Alert
}
return []models.NotificationSettings{
{
Receiver: ns.Receiver,
GroupBy: ns.GroupBy,
GroupWait: ns.GroupWait,
GroupInterval: ns.GroupInterval,
RepeatInterval: ns.RepeatInterval,
MuteTimeIntervals: ns.MuteTimeIntervals,
Receiver: ns.Receiver,
GroupBy: ns.GroupBy,
GroupWait: ns.GroupWait,
GroupInterval: ns.GroupInterval,
RepeatInterval: ns.RepeatInterval,
MuteTimeIntervals: ns.MuteTimeIntervals,
ActiveTimeIntervals: ns.ActiveTimeIntervals,
},
}
}
@@ -85,6 +85,7 @@ resource "grafana_rule_group" "rule_group_d3e8424bfbf66bc3" {
group_interval = "5s"
repeat_interval = "5m"
mute_timings = ["test-mute"]
active_timings = ["test-mute"]
}
}
rule {
@@ -125,6 +126,7 @@ resource "grafana_rule_group" "rule_group_d3e8424bfbf66bc3" {
group_interval = "5s"
repeat_interval = "5m"
mute_timings = ["test-mute"]
active_timings = ["test-mute"]
}
}
rule {
@@ -115,7 +115,8 @@
"group_wait":"1s",
"group_interval":"5s",
"repeat_interval":"5m",
"mute_time_intervals":["test-mute"]
"mute_time_intervals":["test-mute"],
"active_time_intervals":["test-mute"]
}
},
{
@@ -169,7 +170,8 @@
"group_wait":"1s",
"group_interval":"5s",
"repeat_interval":"5m",
"mute_time_intervals":["test-mute"]
"mute_time_intervals":["test-mute"],
"active_time_intervals":["test-mute"]
}
},
{
@@ -93,6 +93,8 @@ groups:
repeat_interval: 5m
mute_time_intervals:
- test-mute
active_time_intervals:
- test-mute
- uid: alert-with-uid
title: alert with uid
condition: B
@@ -139,6 +141,8 @@ groups:
repeat_interval: 5m
mute_time_intervals:
- test-mute
active_time_intervals:
- test-mute
- title: recording rule
data:
- refId: query
@@ -116,7 +116,8 @@
"group_wait":"1s",
"group_interval":"5s",
"repeat_interval":"5m",
"mute_time_intervals":["test-mute"]
"mute_time_intervals":["test-mute"],
"active_time_intervals":["test-mute"]
}
}
},
@@ -171,7 +172,8 @@
"group_wait":"1s",
"group_interval":"5s",
"repeat_interval":"5m",
"mute_time_intervals":["test-mute"]
"mute_time_intervals":["test-mute"],
"active_time_intervals":["test-mute"]
}
}
},
@@ -535,10 +535,16 @@ type AlertRuleNotificationSettings struct {
RepeatInterval *model.Duration `json:"repeat_interval,omitempty"`
// Override the times when notifications should be muted. These must match the name of a mute time interval defined
// in the alertmanager configuration mute_time_intervals section. When muted it will not send any notifications, but
// in the alertmanager configuration time_intervals section. When muted it will not send any notifications, but
// otherwise acts normally.
// example: ["maintenance"]
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
// Override the times when notifications should not be muted. These must match the name of a mute time interval defined
// in the alertmanager configuration time_intervals section. All notifications will be suppressed unless they are sent
// at the time that matches any interval.
// example: ["maintenance"]
ActiveTimeIntervals []string `json:"active_time_intervals,omitempty"`
}
// swagger:model
@@ -171,7 +171,7 @@ type ProvisionedAlertRule struct {
IsPaused bool `json:"isPaused"`
// example: {"receiver":"email","group_by":["alertname","grafana_folder","cluster"],"group_wait":"30s","group_interval":"1m","repeat_interval":"4d","mute_time_intervals":["Weekends","Holidays"]}
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings"`
//example: {"metric":"grafana_alerts_ratio", "from":"A"}
// example: {"metric":"grafana_alerts_ratio", "from":"A"}
Record *Record `json:"record"`
// example: 2
MissingSeriesEvalsToResolve *int `json:"missingSeriesEvalsToResolve,omitempty"`
@@ -306,12 +306,13 @@ type RelativeTimeRangeExport struct {
type AlertRuleNotificationSettingsExport struct {
// Field name mismatches with Terraform provider schema are noted where applicable.
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"contact_point"` // TF -> `contact_point`
GroupBy []string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"`
GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"`
GroupInterval *string `yaml:"group_interval,omitempty" json:"group_interval,omitempty" hcl:"group_interval,optional"`
RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"`
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"` // TF -> `mute_timings`
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"contact_point"` // TF -> `contact_point`
GroupBy []string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"`
GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"`
GroupInterval *string `yaml:"group_interval,omitempty" json:"group_interval,omitempty" hcl:"group_interval,optional"`
RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"`
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"` // TF -> `mute_timings`
ActiveTimeIntervals []string `yaml:"active_time_intervals,omitempty" json:"active_time_intervals,omitempty" hcl:"active_timings"` // TF -> `active_timings`
}
// Record is the provisioned export of models.Record.
@@ -81,6 +81,7 @@ type RouteExport struct {
ObjectMatchers ObjectMatchers `yaml:"object_matchers,omitempty" json:"object_matchers,omitempty"`
ObjectMatchersSlice []*MatcherExport `yaml:"-" json:"-" hcl:"matcher,block"`
MuteTimeIntervals *[]string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"`
ActiveTimeIntervals *[]string `yaml:"active_time_intervals,omitempty" json:"active_time_intervals,omitempty" hcl:"active_timings"`
Continue *bool `yaml:"continue,omitempty" json:"continue,omitempty" hcl:"continue,optional"` // Added omitempty to yaml for a cleaner export.
Routes []*RouteExport `yaml:"routes,omitempty" json:"routes,omitempty" hcl:"policy,block"`
@@ -7,6 +7,8 @@ import (
"strings"
"time"
prommodels "github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@@ -14,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
prommodels "github.com/prometheus/common/model"
)
type RuleLimits struct {
@@ -392,12 +393,13 @@ func ValidateRuleGroup(
func validateNotificationSettings(n *apimodels.AlertRuleNotificationSettings) ([]ngmodels.NotificationSettings, error) {
s := ngmodels.NotificationSettings{
Receiver: n.Receiver,
GroupBy: n.GroupBy,
GroupWait: n.GroupWait,
GroupInterval: n.GroupInterval,
RepeatInterval: n.RepeatInterval,
MuteTimeIntervals: n.MuteTimeIntervals,
Receiver: n.Receiver,
GroupBy: n.GroupBy,
GroupWait: n.GroupWait,
GroupInterval: n.GroupInterval,
RepeatInterval: n.RepeatInterval,
MuteTimeIntervals: n.MuteTimeIntervals,
ActiveTimeIntervals: n.ActiveTimeIntervals,
}
if err := s.Validate(); err != nil {
@@ -850,6 +850,22 @@ func TestDiff(t *testing.T) {
},
},
},
{
name: "should detect changes in ActiveTimeIntervals",
notificationSettings: CopyNotificationSettings(baseSettings, NSMuts.WithActiveTimeIntervals(baseSettings.ActiveTimeIntervals[0]+"-modified", baseSettings.ActiveTimeIntervals[1]+"-modified")),
diffs: []cmputil.Diff{
{
Path: "NotificationSettings[0].ActiveTimeIntervals[0]",
Left: reflect.ValueOf(baseSettings.ActiveTimeIntervals[0]),
Right: reflect.ValueOf(baseSettings.ActiveTimeIntervals[0] + "-modified"),
},
{
Path: "NotificationSettings[0].ActiveTimeIntervals[1]",
Left: reflect.ValueOf(baseSettings.ActiveTimeIntervals[1]),
Right: reflect.ValueOf(baseSettings.ActiveTimeIntervals[1] + "-modified"),
},
},
},
}
for _, tt := range testCases {
+13 -6
View File
@@ -28,11 +28,12 @@ type ListNotificationSettingsQuery struct {
type NotificationSettings struct {
Receiver string `json:"receiver"`
GroupBy []string `json:"group_by,omitempty"`
GroupWait *model.Duration `json:"group_wait,omitempty"`
GroupInterval *model.Duration `json:"group_interval,omitempty"`
RepeatInterval *model.Duration `json:"repeat_interval,omitempty"`
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
GroupBy []string `json:"group_by,omitempty"`
GroupWait *model.Duration `json:"group_wait,omitempty"`
GroupInterval *model.Duration `json:"group_interval,omitempty"`
RepeatInterval *model.Duration `json:"repeat_interval,omitempty"`
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
ActiveTimeIntervals []string `json:"active_time_intervals,omitempty"`
}
func (s *NotificationSettings) GetUID() string {
@@ -136,6 +137,9 @@ func (s *NotificationSettings) Equals(other *NotificationSettings) bool {
if !slices.Equal(s.MuteTimeIntervals, other.MuteTimeIntervals) {
return false
}
if !slices.Equal(s.ActiveTimeIntervals, other.ActiveTimeIntervals) {
return false
}
sGr := s.GroupBy
oGr := other.GroupBy
return slices.Equal(sGr, oGr)
@@ -143,7 +147,7 @@ func (s *NotificationSettings) Equals(other *NotificationSettings) bool {
// IsAllDefault checks if the NotificationSettings object has all default values for optional fields (all except Receiver) .
func (s *NotificationSettings) IsAllDefault() bool {
return len(s.GroupBy) == 0 && s.GroupWait == nil && s.GroupInterval == nil && s.RepeatInterval == nil && len(s.MuteTimeIntervals) == 0
return len(s.GroupBy) == 0 && s.GroupWait == nil && s.GroupInterval == nil && s.RepeatInterval == nil && len(s.MuteTimeIntervals) == 0 && len(s.ActiveTimeIntervals) == 0
}
// NewDefaultNotificationSettings creates a new default NotificationSettings with the specified receiver.
@@ -186,5 +190,8 @@ func (s *NotificationSettings) Fingerprint() data.Fingerprint {
for _, interval := range s.MuteTimeIntervals {
writeString(interval)
}
for _, interval := range s.ActiveTimeIntervals {
writeString(interval)
}
return data.Fingerprint(h.Sum64())
}
@@ -154,6 +154,23 @@ func TestNotificationSettingsLabels(t *testing.T) {
AutogeneratedRouteSettingsHashLabel: "47164c92f2986a35",
},
},
{
name: "custom notification settings with active time interval",
notificationSettings: NotificationSettings{
Receiver: "receiver name",
GroupBy: []string{"label1", "label2"},
GroupWait: util.Pointer(model.Duration(1 * time.Minute)),
GroupInterval: util.Pointer(model.Duration(2 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)),
MuteTimeIntervals: []string{"maintenance1", "maintenance2"},
ActiveTimeIntervals: []string{"active1", "active2"},
},
labels: data.Labels{
AutogeneratedRouteLabel: "true",
AutogeneratedRouteReceiverNameLabel: "receiver name",
AutogeneratedRouteSettingsHashLabel: "a173df6210e43af0",
},
},
}
for _, tt := range testCases {
+17 -6
View File
@@ -925,6 +925,10 @@ func CopyNotificationSettings(ns NotificationSettings, mutators ...Mutator[Notif
c.MuteTimeIntervals = make([]string, len(ns.MuteTimeIntervals))
copy(c.MuteTimeIntervals, ns.MuteTimeIntervals)
}
if ns.ActiveTimeIntervals != nil {
c.ActiveTimeIntervals = make([]string, len(ns.ActiveTimeIntervals))
copy(c.ActiveTimeIntervals, ns.ActiveTimeIntervals)
}
for _, mutator := range mutators {
mutator(&c)
}
@@ -935,12 +939,13 @@ func CopyNotificationSettings(ns NotificationSettings, mutators ...Mutator[Notif
func NotificationSettingsGen(mutators ...Mutator[NotificationSettings]) func() NotificationSettings {
return func() NotificationSettings {
c := NotificationSettings{
Receiver: util.GenerateShortUID(),
GroupBy: []string{model.AlertNameLabel, FolderTitleLabel, util.GenerateShortUID()},
GroupWait: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
GroupInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
RepeatInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
MuteTimeIntervals: []string{util.GenerateShortUID(), util.GenerateShortUID()},
Receiver: util.GenerateShortUID(),
GroupBy: []string{model.AlertNameLabel, FolderTitleLabel, util.GenerateShortUID()},
GroupWait: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
GroupInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
RepeatInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
MuteTimeIntervals: []string{util.GenerateShortUID(), util.GenerateShortUID()},
ActiveTimeIntervals: []string{util.GenerateShortUID(), util.GenerateShortUID()},
}
for _, mutator := range mutators {
mutator(&c)
@@ -1002,6 +1007,12 @@ func (n NotificationSettingsMutators) WithMuteTimeIntervals(muteTimeIntervals ..
}
}
func (n NotificationSettingsMutators) WithActiveTimeIntervals(activeTimeIntervals ...string) Mutator[NotificationSettings] {
return func(ns *NotificationSettings) {
ns.ActiveTimeIntervals = activeTimeIntervals
}
}
// Silences
// CopySilenceWith creates a deep copy of Silence and then applies mutators to it.
@@ -146,13 +146,14 @@ func generateRouteFromSettings(defaultReceiver string, settings map[data.Fingerp
ObjectMatchers: definitions.ObjectMatchers{settingMatcher},
Continue: false, // Only a single setting-specific route should match.
GroupByStr: normalized,
GroupBy: groupBy,
GroupByAll: groupByAll,
MuteTimeIntervals: s.MuteTimeIntervals,
GroupWait: s.GroupWait,
GroupInterval: s.GroupInterval,
RepeatInterval: s.RepeatInterval,
GroupByStr: normalized,
GroupBy: groupBy,
GroupByAll: groupByAll,
MuteTimeIntervals: s.MuteTimeIntervals,
ActiveTimeIntervals: s.ActiveTimeIntervals,
GroupWait: s.GroupWait,
GroupInterval: s.GroupInterval,
RepeatInterval: s.RepeatInterval,
})
}
@@ -108,20 +108,22 @@ func TestAddAutogenConfig(t *testing.T) {
},
{
name: "settings with custom options, add option-specific routes",
existingConfig: configGen([]string{"receiver1", "receiver2", "receiver3", "receiver4", "receiver5"}, []string{"maintenance"}),
existingConfig: configGen([]string{"receiver1", "receiver2", "receiver3", "receiver4", "receiver5"}, []string{"maintenance", "active"}),
storeSettings: []models.NotificationSettings{
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver1"), models.NSMuts.WithGroupInterval(util.Pointer(1*time.Minute))),
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver2"), models.NSMuts.WithGroupWait(util.Pointer(2*time.Minute))),
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver3"), models.NSMuts.WithRepeatInterval(util.Pointer(3*time.Minute))),
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver4"), models.NSMuts.WithGroupBy(model.AlertNameLabel, models.FolderTitleLabel, "custom")),
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver5"), models.NSMuts.WithMuteTimeIntervals("maintenance")),
models.CopyNotificationSettings(models.NewDefaultNotificationSettings("receiver5"), models.NSMuts.WithActiveTimeIntervals("active")),
{
Receiver: "receiver1",
GroupBy: []string{model.AlertNameLabel, models.FolderTitleLabel, "custom"},
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)),
MuteTimeIntervals: []string{"maintenance"},
Receiver: "receiver1",
GroupBy: []string{model.AlertNameLabel, models.FolderTitleLabel, "custom"},
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)),
MuteTimeIntervals: []string{"maintenance"},
ActiveTimeIntervals: []string{"active"},
},
},
expRoute: withChildRoutes(rootRoute(), &definitions.Route{
@@ -132,19 +134,24 @@ func TestAddAutogenConfig(t *testing.T) {
Receiver: "receiver5",
ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "030d6474aec0b553"),
MuteTimeIntervals: []string{"maintenance"},
}, &definitions.Route{
Receiver: "receiver5",
ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "cd6cd2089632453c"),
ActiveTimeIntervals: []string{"active"},
}),
withChildRoutes(basicContactRoute("receiver1"), &definitions.Route{
Receiver: "receiver1",
ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "4f095749ddf3eeeb"),
GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel, "custom"},
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)),
MuteTimeIntervals: []string{"maintenance"},
}, &definitions.Route{
Receiver: "receiver1",
ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "dde34b8127e68f31"),
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
}, &definitions.Route{
Receiver: "receiver1",
ObjectMatchers: matcher(models.AutogeneratedRouteSettingsHashLabel, "f134b8faf7db083c"),
GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel, "custom"},
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Minute)),
MuteTimeIntervals: []string{"maintenance"},
ActiveTimeIntervals: []string{"active"},
}),
withChildRoutes(basicContactRoute("receiver2"), &definitions.Route{
Receiver: "receiver2",
@@ -91,6 +91,11 @@ func (n staticValidator) Validate(settings models.NotificationSettings) error {
errs = append(errs, ErrorTimeIntervalDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: interval}})
}
}
for _, interval := range settings.ActiveTimeIntervals {
if _, ok := n.availableTimeIntervals[interval]; !ok {
errs = append(errs, ErrorTimeIntervalDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: interval}})
}
}
return errors.Join(errs...)
}
@@ -104,7 +104,7 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
for _, mt := range revision.Config.AlertmanagerConfig.TimeIntervals {
timeIntervals[mt.Name] = struct{}{}
}
err = tree.ValidateMuteTimes(timeIntervals)
err = tree.ValidateTimeIntervals(timeIntervals)
if err != nil {
return definitions.Route{}, "", MakeErrRouteInvalidFormat(err)
}
@@ -92,6 +92,21 @@ func TestUpdatePolicyTree(t *testing.T) {
require.ErrorIs(t, err, ErrRouteInvalidFormat)
})
t.Run("ErrValidation if referenced active time interval does not exist", func(t *testing.T) {
sut, store, _ := createNotificationPolicyServiceSut()
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
return &rev, nil
}
newRoute := definitions.Route{
Receiver: rev.Config.AlertmanagerConfig.Receivers[0].Name,
ActiveTimeIntervals: []string{
"not-existing",
},
}
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
require.ErrorIs(t, err, ErrRouteInvalidFormat)
})
t.Run("ErrValidation if root route has no receiver", func(t *testing.T) {
rev := getDefaultConfigRevision()
sut, store, _ := createNotificationPolicyServiceSut()
+7 -2
View File
@@ -673,7 +673,7 @@ func (st DBstore) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertR
}
if query.TimeIntervalName != "" {
if !slices.ContainsFunc(converted.NotificationSettings, func(settings ngmodels.NotificationSettings) bool {
return slices.Contains(settings.MuteTimeIntervals, query.TimeIntervalName)
return slices.Contains(settings.MuteTimeIntervals, query.TimeIntervalName) || slices.Contains(settings.ActiveTimeIntervals, query.TimeIntervalName)
}) {
continue
}
@@ -985,7 +985,7 @@ func (st DBstore) ListNotificationSettings(ctx context.Context, q ngmodels.ListN
if q.ReceiverName != "" && q.ReceiverName != setting.Receiver { // currently, there can be only one setting. If in future there are more, we will return all settings of a rule that has a setting with receiver
continue
}
if q.TimeIntervalName != "" && !slices.Contains(setting.MuteTimeIntervals, q.TimeIntervalName) {
if q.TimeIntervalName != "" && !slices.Contains(setting.MuteTimeIntervals, q.TimeIntervalName) && !slices.Contains(setting.ActiveTimeIntervals, q.TimeIntervalName) {
continue
}
ns = append(ns, setting)
@@ -1157,6 +1157,11 @@ func (st DBstore) RenameTimeIntervalInNotificationSettings(
r.NotificationSettings[idx].MuteTimeIntervals[mtIdx] = newTimeInterval
}
}
for mtIdx := range r.NotificationSettings[idx].ActiveTimeIntervals {
if r.NotificationSettings[idx].ActiveTimeIntervals[mtIdx] == oldTimeInterval {
r.NotificationSettings[idx].ActiveTimeIntervals[mtIdx] = newTimeInterval
}
}
}
updates = append(updates, ngmodels.UpdateRule{
+11 -4
View File
@@ -907,7 +907,9 @@ func TestIntegrationAlertRulesNotificationSettings(t *testing.T) {
gen = gen.With(gen.WithOrgID(1), gen.WithIntervalMatching(store.Cfg.BaseInterval))
rules := gen.GenerateManyRef(3)
receiveRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName)))).GenerateManyRef(3)
timeIntervalRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithMuteTimeIntervals(timeIntervalName)))).GenerateManyRef(3)
mutetimeIntervalRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithMuteTimeIntervals(timeIntervalName)))).GenerateManyRef(3)
activeTimeIntervalRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithActiveTimeIntervals(timeIntervalName)))).GenerateManyRef(3)
timeIntervalRules := append(mutetimeIntervalRules, activeTimeIntervalRules...)
noise := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(timeIntervalName), models.NSMuts.WithMuteTimeIntervals(receiverName)))).GenerateManyRef(3)
deref := make([]models.AlertRule, 0, len(rules)+len(receiveRules)+len(timeIntervalRules)+len(noise))
@@ -959,16 +961,21 @@ func TestIntegrationAlertRulesNotificationSettings(t *testing.T) {
deref[i], deref[j] = deref[j], deref[i]
})
for _, rule := range deref {
if len(rule.NotificationSettings) == 0 || rule.NotificationSettings[0].Receiver == "" || len(rule.NotificationSettings[0].MuteTimeIntervals) == 0 {
if len(rule.NotificationSettings) == 0 || rule.NotificationSettings[0].Receiver == "" || len(rule.NotificationSettings[0].MuteTimeIntervals) == 0 || len(rule.NotificationSettings[0].ActiveTimeIntervals) == 0 {
continue
}
if len(expected) > 0 {
if rule.NotificationSettings[0].Receiver == receiver && slices.Contains(rule.NotificationSettings[0].MuteTimeIntervals, intervalName) {
if rule.NotificationSettings[0].Receiver == receiver && (slices.Contains(rule.NotificationSettings[0].MuteTimeIntervals, intervalName) || slices.Contains(rule.NotificationSettings[0].ActiveTimeIntervals, intervalName)) {
expected = append(expected, rule.GetKey())
}
} else {
receiver = rule.NotificationSettings[0].Receiver
intervalName = rule.NotificationSettings[0].MuteTimeIntervals[0]
if len(rule.NotificationSettings[0].MuteTimeIntervals) > 0 {
intervalName = rule.NotificationSettings[0].MuteTimeIntervals[0]
}
if len(rule.NotificationSettings[0].ActiveTimeIntervals) > 0 {
intervalName = rule.NotificationSettings[0].ActiveTimeIntervals[0]
}
expected = append(expected, rule.GetKey())
}
}
@@ -203,12 +203,13 @@ func (queryV1 *QueryV1) mapToModel() (models.AlertQuery, error) {
}
type NotificationSettingsV1 struct {
Receiver values.StringValue `json:"receiver" yaml:"receiver"`
GroupBy []values.StringValue `json:"group_by,omitempty" yaml:"group_by"`
GroupWait values.StringValue `json:"group_wait,omitempty" yaml:"group_wait"`
GroupInterval values.StringValue `json:"group_interval,omitempty" yaml:"group_interval"`
RepeatInterval values.StringValue `json:"repeat_interval,omitempty" yaml:"repeat_interval"`
MuteTimeIntervals []values.StringValue `json:"mute_time_intervals,omitempty" yaml:"mute_time_intervals"`
Receiver values.StringValue `json:"receiver" yaml:"receiver"`
GroupBy []values.StringValue `json:"group_by,omitempty" yaml:"group_by"`
GroupWait values.StringValue `json:"group_wait,omitempty" yaml:"group_wait"`
GroupInterval values.StringValue `json:"group_interval,omitempty" yaml:"group_interval"`
RepeatInterval values.StringValue `json:"repeat_interval,omitempty" yaml:"repeat_interval"`
MuteTimeIntervals []values.StringValue `json:"mute_time_intervals,omitempty" yaml:"mute_time_intervals"`
ActiveTimeIntervals []values.StringValue `json:"active_time_intervals,omitempty" yaml:"active_time_intervals"`
}
func (nsV1 *NotificationSettingsV1) mapToModel() (models.NotificationSettings, error) {
@@ -259,14 +260,25 @@ func (nsV1 *NotificationSettingsV1) mapToModel() (models.NotificationSettings, e
mute = append(mute, value.Value())
}
}
var active []string
if len(nsV1.ActiveTimeIntervals) > 0 {
active = make([]string, 0, len(nsV1.ActiveTimeIntervals))
for _, value := range nsV1.ActiveTimeIntervals {
if value.Value() == "" {
continue
}
active = append(active, value.Value())
}
}
return models.NotificationSettings{
Receiver: nsV1.Receiver.Value(),
GroupBy: groupBy,
GroupWait: gw,
GroupInterval: gi,
RepeatInterval: ri,
MuteTimeIntervals: mute,
Receiver: nsV1.Receiver.Value(),
GroupBy: groupBy,
GroupWait: gw,
GroupInterval: gi,
RepeatInterval: ri,
MuteTimeIntervals: mute,
ActiveTimeIntervals: active,
}, nil
}
@@ -212,20 +212,22 @@ func TestNotificationsSettingsV1MapToModel(t *testing.T) {
{
name: "Valid Input",
input: NotificationSettingsV1{
Receiver: stringToStringValue("test-receiver"),
GroupBy: []values.StringValue{stringToStringValue("test-group_by")},
GroupWait: stringToStringValue("1s"),
GroupInterval: stringToStringValue("2s"),
RepeatInterval: stringToStringValue("3s"),
MuteTimeIntervals: []values.StringValue{stringToStringValue("test-mute")},
Receiver: stringToStringValue("test-receiver"),
GroupBy: []values.StringValue{stringToStringValue("test-group_by")},
GroupWait: stringToStringValue("1s"),
GroupInterval: stringToStringValue("2s"),
RepeatInterval: stringToStringValue("3s"),
MuteTimeIntervals: []values.StringValue{stringToStringValue("test-mute")},
ActiveTimeIntervals: []values.StringValue{stringToStringValue("test-active")},
},
expected: models.NotificationSettings{
Receiver: "test-receiver",
GroupBy: []string{"test-group_by"},
GroupWait: util.Pointer(model.Duration(1 * time.Second)),
GroupInterval: util.Pointer(model.Duration(2 * time.Second)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Second)),
MuteTimeIntervals: []string{"test-mute"},
Receiver: "test-receiver",
GroupBy: []string{"test-group_by"},
GroupWait: util.Pointer(model.Duration(1 * time.Second)),
GroupInterval: util.Pointer(model.Duration(2 * time.Second)),
RepeatInterval: util.Pointer(model.Duration(3 * time.Second)),
MuteTimeIntervals: []string{"test-mute"},
ActiveTimeIntervals: []string{"test-active"},
},
},
{
+22 -9
View File
@@ -4415,16 +4415,27 @@ func TestIntegrationRuleNotificationSettings(t *testing.T) {
t.Log(body)
})
t.Run("create should fail if mute timing does not exist", func(t *testing.T) {
var copyD testData
err = json.Unmarshal(testDataRaw, &copyD)
group := copyD.RuleGroup
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
ns.MuteTimeIntervals = []string{"random-time-interval"}
t.Run("create should fail if time interval does not exist", func(t *testing.T) {
t.Run("mute time interval", func(t *testing.T) {
var copyD testData
err = json.Unmarshal(testDataRaw, &copyD)
group := copyD.RuleGroup
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
ns.MuteTimeIntervals = []string{"random-time-interval"}
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group, false)
require.Equalf(t, http.StatusBadRequest, status, body)
t.Log(body)
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group, false)
require.Equalf(t, http.StatusBadRequest, status, body)
})
t.Run("active time interval", func(t *testing.T) {
var copyD testData
err = json.Unmarshal(testDataRaw, &copyD)
group := copyD.RuleGroup
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
ns.ActiveTimeIntervals = []string{"random-time-interval"}
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group, false)
require.Equalf(t, http.StatusBadRequest, status, body)
})
})
t.Run("create should not fail if group_by is missing required labels but they should still be used", func(t *testing.T) {
@@ -4483,6 +4494,7 @@ func TestIntegrationRuleNotificationSettings(t *testing.T) {
assert.Nil(c, autogenRoute.GroupInterval)
assert.Nil(c, autogenRoute.RepeatInterval)
assert.Empty(c, autogenRoute.MuteTimeIntervals)
assert.Empty(c, autogenRoute.ActiveTimeIntervals)
assert.Empty(c, autogenRoute.GroupBy)
if !canContinue {
return
@@ -4511,6 +4523,7 @@ func TestIntegrationRuleNotificationSettings(t *testing.T) {
assert.Nil(c, receiverRoute.GroupInterval)
assert.Nil(c, receiverRoute.RepeatInterval)
assert.Empty(c, receiverRoute.MuteTimeIntervals)
assert.Empty(c, receiverRoute.ActiveTimeIntervals)
var groupBy []string
for _, name := range receiverRoute.GroupBy {
groupBy = append(groupBy, string(name))
@@ -538,13 +538,14 @@ func TestIntegrationDataConsistency(t *testing.T) {
ensureMatcher(t, labels.MatchRegexp, "o", "1"),
ensureMatcher(t, labels.MatchNotRegexp, "p", "1"),
},
Receiver: receiver,
GroupByStr: []string{"test-789"},
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
GroupInterval: util.Pointer(model.Duration(5 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(30 * time.Hour)),
MuteTimeIntervals: []string{timeInterval},
Continue: true,
Receiver: receiver,
GroupByStr: []string{"test-789"},
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
GroupInterval: util.Pointer(model.Duration(5 * time.Minute)),
RepeatInterval: util.Pointer(model.Duration(30 * time.Hour)),
MuteTimeIntervals: []string{timeInterval},
ActiveTimeIntervals: []string{timeInterval},
Continue: true,
},
},
}
@@ -562,13 +563,14 @@ func TestIntegrationDataConsistency(t *testing.T) {
}, tree.Spec.Defaults)
assert.Len(t, tree.Spec.Routes, 1)
assert.Equal(t, v0alpha1.Route{
Continue: true,
Receiver: util.Pointer(receiver),
GroupBy: []string{"test-789"},
GroupWait: util.Pointer("2m"),
GroupInterval: util.Pointer("5m"),
RepeatInterval: util.Pointer("1d6h"),
MuteTimeIntervals: []string{timeInterval},
Continue: true,
Receiver: util.Pointer(receiver),
GroupBy: []string{"test-789"},
GroupWait: util.Pointer("2m"),
GroupInterval: util.Pointer("5m"),
RepeatInterval: util.Pointer("1d6h"),
MuteTimeIntervals: []string{timeInterval},
ActiveTimeIntervals: []string{timeInterval},
Matchers: []v0alpha1.Matcher{
{
Label: "m",
@@ -3599,6 +3599,13 @@
"continue"
],
"properties": {
"active_time_intervals": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
},
"continue": {
"type": "boolean",
"default": false