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:
co-authored by
Sonia Aguilar
Sonia Aguilar
parent
a8dee54aa4
commit
3e2296acd3
@@ -26,6 +26,7 @@ RouteTreeSpec: {
|
||||
|
||||
group_by?: [...string]
|
||||
mute_time_intervals?: [...string]
|
||||
active_time_intervals?: [...string]
|
||||
routes?: [...#Route]
|
||||
group_wait?: string
|
||||
group_interval?: string
|
||||
|
||||
@@ -18,15 +18,16 @@ func NewRouteDefaults() *RouteDefaults {
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Route struct {
|
||||
Receiver *string `json:"receiver,omitempty"`
|
||||
Matchers []Matcher `json:"matchers,omitempty"`
|
||||
Continue bool `json:"continue"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
Receiver *string `json:"receiver,omitempty"`
|
||||
Matchers []Matcher `json:"matchers,omitempty"`
|
||||
Continue bool `json:"continue"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
||||
ActiveTimeIntervals []string `json:"active_time_intervals,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
}
|
||||
|
||||
// NewRoute creates a new Route object.
|
||||
|
||||
@@ -119,6 +119,20 @@ func schema_pkg_apis_routingtree_v0alpha1_Route(ref common.ReferenceCallback) co
|
||||
},
|
||||
},
|
||||
},
|
||||
"active_time_intervals": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"routes": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
|
||||
+2
@@ -1,9 +1,11 @@
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,ActiveTimeIntervals
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,GroupBy
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,Matchers
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,MuteTimeIntervals
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,Routes
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,RouteDefaults,GroupBy
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Spec,Routes
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,ActiveTimeIntervals
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,GroupBy
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,GroupInterval
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1,Route,GroupWait
|
||||
|
||||
@@ -77,7 +77,7 @@ require (
|
||||
github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage
|
||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb // @grafana/alerting-backend
|
||||
github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
|
||||
@@ -1565,8 +1565,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb h1:Nkqatz7R7K/2YaK+7pw8CAYgylKfksPVeT7gMARKjwI=
|
||||
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA=
|
||||
github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 h1:H2p3XKDHnTBGkMXLCgXiqb2dFnHbQ4zPDXOwKK4Ne3Y=
|
||||
github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA=
|
||||
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y=
|
||||
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us=
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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, ©D)
|
||||
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, ©D)
|
||||
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, ©D)
|
||||
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
|
||||
|
||||
@@ -145,7 +145,7 @@ const fillOutForm = async ({
|
||||
|
||||
const saveMuteTiming = async () => {
|
||||
const user = userEvent.setup();
|
||||
await user.click(await screen.findByText(/save mute timing/i));
|
||||
await user.click(await screen.findByText(/save time interval/i));
|
||||
};
|
||||
|
||||
setupMswServer();
|
||||
@@ -177,7 +177,7 @@ describe('Mute timings', () => {
|
||||
const capture = captureRequests();
|
||||
renderMuteTimings({ pathname: '/alerting/routes/new', search: `?alertmanager=${dataSources.am.name}` });
|
||||
|
||||
await screen.findByText(/add mute timing/i);
|
||||
await screen.findByText(/add time interval/i);
|
||||
|
||||
await fillOutForm({
|
||||
name: 'maintenance period',
|
||||
@@ -314,13 +314,13 @@ describe('Mute timings', () => {
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${'does not exist'}`,
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/No matching mute timing found/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/No matching time interval found/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('allows creation of new mute timings', async () => {
|
||||
renderMuteTimings('/alerting/routes/new');
|
||||
|
||||
await fillOutForm({ name: 'a new mute timing' });
|
||||
await fillOutForm({ name: 'a new time interval' });
|
||||
|
||||
await saveMuteTiming();
|
||||
await expectToHaveRedirectedToRoutesRoute();
|
||||
@@ -332,7 +332,7 @@ describe('Mute timings', () => {
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH + '_force_breakage'}`,
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/No matching mute timing found/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/No matching time interval found/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('loads edit form correctly and allows saving', async () => {
|
||||
@@ -351,6 +351,6 @@ describe('Mute timings', () => {
|
||||
search: `?muteName=${TIME_INTERVAL_NAME_FILE_PROVISIONED}`,
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/This mute timing cannot be edited through the UI/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/This time interval cannot be edited through the UI/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,13 +11,13 @@ import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alertin
|
||||
|
||||
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
|
||||
import { TimeIntervalsTable } from './components/mute-timings/MuteTimingsTable';
|
||||
import { useAlertmanager } from './state/AlertmanagerContext';
|
||||
import { withPageErrorBoundary } from './withPageErrorBoundary';
|
||||
|
||||
enum ActiveTab {
|
||||
NotificationPolicies = 'notification_policies',
|
||||
MuteTimings = 'mute_timings',
|
||||
TimeIntervals = 'time_intervals',
|
||||
}
|
||||
|
||||
const NotificationPoliciesTabs = () => {
|
||||
@@ -26,10 +26,10 @@ const NotificationPoliciesTabs = () => {
|
||||
// Alertmanager logic and data hooks
|
||||
const { selectedAlertmanager = '' } = useAlertmanager();
|
||||
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
|
||||
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
|
||||
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewTimeInterval);
|
||||
const availableTabs = [
|
||||
canSeePoliciesTab && ActiveTab.NotificationPolicies,
|
||||
canSeeTimingsTab && ActiveTab.MuteTimings,
|
||||
canSeeTimingsTab && ActiveTab.TimeIntervals,
|
||||
].filter((tab) => !!tab);
|
||||
const { data: muteTimings = [] } = useMuteTimings({
|
||||
alertmanager: selectedAlertmanager,
|
||||
@@ -41,7 +41,7 @@ const NotificationPoliciesTabs = () => {
|
||||
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
|
||||
|
||||
const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings;
|
||||
const muteTimingsTabActive = activeTab === ActiveTab.TimeIntervals;
|
||||
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
|
||||
|
||||
const numberOfMuteTimings = muteTimings.length;
|
||||
@@ -62,19 +62,19 @@ const NotificationPoliciesTabs = () => {
|
||||
)}
|
||||
{timingsSupported && canSeeTimingsTab && (
|
||||
<Tab
|
||||
label={t('alerting.notification-policies-tabs.label-mute-timings', 'Mute Timings')}
|
||||
label={t('alerting.notification-policies-tabs.label-time-intervals', 'Time intervals')}
|
||||
active={muteTimingsTabActive}
|
||||
counter={numberOfMuteTimings}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(ActiveTab.MuteTimings);
|
||||
setQueryParams({ tab: ActiveTab.MuteTimings });
|
||||
setActiveTab(ActiveTab.TimeIntervals);
|
||||
setQueryParams({ tab: ActiveTab.TimeIntervals });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
{policyTreeTabActive && <NotificationPoliciesList />}
|
||||
{muteTimingsTabActive && <MuteTimingsTable />}
|
||||
{muteTimingsTabActive && <TimeIntervalsTable />}
|
||||
</TabContent>
|
||||
</>
|
||||
);
|
||||
@@ -97,8 +97,8 @@ function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): Q
|
||||
tab = ActiveTab.NotificationPolicies;
|
||||
}
|
||||
|
||||
if (queryParams.tab === ActiveTab.MuteTimings) {
|
||||
tab = ActiveTab.MuteTimings;
|
||||
if (queryParams.tab === ActiveTab.TimeIntervals) {
|
||||
tab = ActiveTab.TimeIntervals;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Trans, t } from 'app/core/internationalization';
|
||||
export enum ProvisionedResource {
|
||||
ContactPoint = 'contact point',
|
||||
Template = 'template',
|
||||
MuteTiming = 'mute timing',
|
||||
MuteTiming = 'time interval',
|
||||
AlertRule = 'alert rule',
|
||||
RootNotificationPolicy = 'root notification policy',
|
||||
}
|
||||
|
||||
+8
-8
@@ -6,29 +6,29 @@ import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks'
|
||||
import { timeIntervalToString } from 'app/features/alerting/unified/utils/alertmanager';
|
||||
import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
const mapMuteTiming = ({ name, time_intervals }: MuteTimeInterval): SelectableValue<string> => ({
|
||||
const mapTimeInterval = ({ name, time_intervals }: MuteTimeInterval): SelectableValue<string> => ({
|
||||
value: name,
|
||||
label: name,
|
||||
description: time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '),
|
||||
});
|
||||
|
||||
/** Provides a MultiSelect with available mute timings for the given alertmanager */
|
||||
const MuteTimingsSelector = ({
|
||||
/** Provides a MultiSelect with available time intervals for the given alertmanager */
|
||||
const TimeIntervalSelector = ({
|
||||
alertmanager,
|
||||
selectProps,
|
||||
}: BaseAlertmanagerArgs & { selectProps: MultiSelectCommonProps<string> }) => {
|
||||
const { data } = useMuteTimings({ alertmanager, skip: selectProps.disabled });
|
||||
|
||||
const muteTimingOptions = data?.map((value) => mapMuteTiming(value)) || [];
|
||||
const timeIntervalOptions = data?.map((value) => mapTimeInterval(value)) || [];
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
aria-label={t('alerting.mute-timings-selector.aria-label-mute-timings', 'Mute timings')}
|
||||
options={muteTimingOptions}
|
||||
placeholder={t('alerting.mute-timings-selector.placeholder-select-mute-timings', 'Select mute timings...')}
|
||||
aria-label={t('alerting.time-intervals-selector.aria-label-time-intervals', 'Time intervals')}
|
||||
options={timeIntervalOptions}
|
||||
placeholder={t('alerting.time-intervals-selector.placeholder-select-time-intervals', 'Select time intervals...')}
|
||||
{...selectProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuteTimingsSelector;
|
||||
export default TimeIntervalSelector;
|
||||
|
||||
@@ -42,7 +42,7 @@ function EditMuteTimingPage() {
|
||||
return (
|
||||
<AlertmanagerPageWrapper
|
||||
navId="am-routes"
|
||||
pageNav={{ id: 'alert-policy-edit', text: 'Edit mute timing' }}
|
||||
pageNav={{ id: 'alert-policy-edit', text: 'Edit time interval' }}
|
||||
accessType="notification"
|
||||
>
|
||||
<EditTimingRoute />
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
|
||||
});
|
||||
const [showDeleteDrawer, setShowDeleteDrawer] = useState(false);
|
||||
const [ExportDrawer, showExportDrawer] = useExportMuteTimingsDrawer();
|
||||
const [exportSupported, exportAllowed] = useAlertmanagerAbility(AlertmanagerAction.ExportMuteTimings);
|
||||
const [exportSupported, exportAllowed] = useAlertmanagerAbility(AlertmanagerAction.ExportTimeIntervals);
|
||||
|
||||
const closeDeleteModal = () => setShowDeleteDrawer(false);
|
||||
|
||||
@@ -55,7 +55,7 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
|
||||
{!isGrafanaDataSource && isDisabled(muteTiming) && (
|
||||
<Badge text={t('alerting.mute-timing-actions-buttons.text-disabled', 'Disabled')} color="orange" />
|
||||
)}
|
||||
<Authorize actions={[AlertmanagerAction.UpdateMuteTiming]}>{viewOrEditButton}</Authorize>
|
||||
<Authorize actions={[AlertmanagerAction.UpdateTimeInterval]}>{viewOrEditButton}</Authorize>
|
||||
|
||||
{exportSupported && (
|
||||
<LinkButton
|
||||
@@ -71,7 +71,7 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
|
||||
)}
|
||||
|
||||
{!muteTiming.provisioned && (
|
||||
<Authorize actions={[AlertmanagerAction.DeleteMuteTiming]}>
|
||||
<Authorize actions={[AlertmanagerAction.DeleteTimeInterval]}>
|
||||
<LinkButton
|
||||
icon="trash-alt"
|
||||
variant="secondary"
|
||||
|
||||
@@ -70,7 +70,7 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
const formApi = useForm({ defaultValues, values: defaultValues });
|
||||
const updating = formApi.formState.isSubmitting;
|
||||
|
||||
const returnLink = makeAMLink('/alerting/routes/', selectedAlertmanager!, { tab: 'mute_timings' });
|
||||
const returnLink = makeAMLink('/alerting/routes/', selectedAlertmanager!, { tab: 'time_intervals' });
|
||||
|
||||
const onSubmit = async (values: MuteTimingFields) => {
|
||||
const interval = createMuteTiming(values);
|
||||
@@ -88,13 +88,18 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder text={t('alerting.mute-timing-form.text-loading-mute-timing', 'Loading mute timing')} />;
|
||||
return (
|
||||
<LoadingPlaceholder text={t('alerting.time-interval-form.text-loading-time-interval', 'Loading time interval')} />
|
||||
);
|
||||
}
|
||||
|
||||
if (showError) {
|
||||
return (
|
||||
<Alert
|
||||
title={t('alerting.mute-timing-form.title-no-matching-mute-timing-found', 'No matching mute timing found')}
|
||||
title={t(
|
||||
'alerting.time-interval-form.title-no-matching-time-interval-found',
|
||||
'No matching time interval found'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -109,8 +114,8 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
required
|
||||
label={t('alerting.mute-timing-form.label-name', 'Name')}
|
||||
description={t(
|
||||
'alerting.mute-timing-form.description-unique-timing',
|
||||
'A unique name for the mute timing'
|
||||
'alerting.time-interval-form.description-unique-time-interval',
|
||||
'A unique name for the time interval'
|
||||
)}
|
||||
invalid={!!formApi.formState.errors?.name}
|
||||
error={formApi.formState.errors.name?.message}
|
||||
@@ -135,9 +140,9 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
icon={updating ? 'spinner' : undefined}
|
||||
>
|
||||
{updating ? (
|
||||
<Trans i18nKey="alerting.mute-timings.saving">Saving mute timing</Trans>
|
||||
<Trans i18nKey="alerting.time-interval.saving">Saving time interval</Trans>
|
||||
) : (
|
||||
<Trans i18nKey="alerting.mute-timings.save">Save mute timing</Trans>
|
||||
<Trans i18nKey="alerting.time-interval.save">Save time interval</Trans>
|
||||
)}
|
||||
</Button>
|
||||
<LinkButton type="button" variant="secondary" fill="outline" href={returnLink} disabled={updating}>
|
||||
|
||||
+6
-6
@@ -31,10 +31,10 @@ export const MuteTimingTimeInterval = () => {
|
||||
<>
|
||||
<p>
|
||||
<Trans i18nKey="alerting.mute-timing-time-interval.description">
|
||||
A time interval is a definition for a moment in time. All fields are lists, and at least one list element
|
||||
must be satisfied to match the field. If a field is left blank, any moment of time will match the field. For
|
||||
an instant of time to match a complete time interval, all fields must match. A mute timing can contain
|
||||
multiple time intervals.
|
||||
A time interval item is a definition for a moment in time. All fields are lists, and at least one list
|
||||
element must be satisfied to match the field. If a field is left blank, any moment of time will match the
|
||||
field. For an instant of time to match a complete time interval, all fields must match. A time interval can
|
||||
contain multiple time interval items.
|
||||
</Trans>
|
||||
</p>
|
||||
<Stack direction="column" gap={2}>
|
||||
@@ -182,8 +182,8 @@ export const MuteTimingTimeInterval = () => {
|
||||
}}
|
||||
icon="plus"
|
||||
>
|
||||
<Trans i18nKey="alerting.mute-timing-time-interval.add-another-time-interval">
|
||||
Add another time interval
|
||||
<Trans i18nKey="alerting.mute-timing-time-interval.add-another-time-interval-item">
|
||||
Add another time interval item
|
||||
</Trans>
|
||||
</Button>
|
||||
</>
|
||||
|
||||
+6
-6
@@ -14,13 +14,13 @@ import { TIME_INTERVAL_UID_HAPPY_PATH } from '../../mocks/server/handlers/k8s/ti
|
||||
import { AlertmanagerProvider } from '../../state/AlertmanagerContext';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
|
||||
import { MuteTimingsTable } from './MuteTimingsTable';
|
||||
import { TimeIntervalsTable } from './MuteTimingsTable';
|
||||
import { defaultConfig } from './mocks';
|
||||
|
||||
const renderWithProvider = (alertManagerSource = GRAFANA_RULES_SOURCE_NAME) => {
|
||||
return render(
|
||||
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={alertManagerSource}>
|
||||
<MuteTimingsTable />
|
||||
<TimeIntervalsTable />
|
||||
</AlertmanagerProvider>
|
||||
);
|
||||
};
|
||||
@@ -92,14 +92,14 @@ describe('MuteTimingsTable', () => {
|
||||
expect(await screen.findByTestId('dynamic-table')).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findByText('Provisioned')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/no mute timings configured/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/you haven't created any mute timings yet/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/no time intervals configured/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/you haven't created any time in intervals yet/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows error when mute timings cannot load', async () => {
|
||||
setMuteTimingsListError();
|
||||
renderWithProvider();
|
||||
expect(await screen.findByText(/error loading mute timings/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/error loading time intervals/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('deletes interval', async () => {
|
||||
@@ -123,7 +123,7 @@ describe('MuteTimingsTable', () => {
|
||||
it('shows empty state when no mute timings are configured', async () => {
|
||||
setTimeIntervalsListEmpty();
|
||||
renderWithProvider();
|
||||
expect(await screen.findByText(/you haven't created any mute timings yet/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/you haven't created any time intervals yet/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type TableItem = {
|
||||
data: MuteTiming;
|
||||
};
|
||||
|
||||
export const MuteTimingsTable = () => {
|
||||
export const TimeIntervalsTable = () => {
|
||||
const { selectedAlertmanager: alertManagerSourceName = '', hasConfigurationAPI } = useAlertmanager();
|
||||
const hideActions = !hasConfigurationAPI;
|
||||
const styles = useStyles2(getStyles);
|
||||
@@ -47,26 +47,26 @@ export const MuteTimingsTable = () => {
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const [_, allowedToCreateMuteTiming] = useAlertmanagerAbility(AlertmanagerAction.CreateMuteTiming);
|
||||
const [_, allowedToCreateMuteTiming] = useAlertmanagerAbility(AlertmanagerAction.CreateTimeInterval);
|
||||
|
||||
const [exportMuteTimingsSupported, exportMuteTimingsAllowed] = useAlertmanagerAbility(
|
||||
AlertmanagerAction.ExportMuteTimings
|
||||
AlertmanagerAction.ExportTimeIntervals
|
||||
);
|
||||
const columns = useColumns(alertManagerSourceName, hideActions);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPlaceholder
|
||||
text={t('alerting.mute-timings-table.text-loading-mute-timings', 'Loading mute timings...')}
|
||||
text={t('alerting.time-intervals-table.text-loading-time-intervals', 'Loading time intervals...')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error" title={t('alerting.mute_timings.error-loading.title', 'Error loading mute timings')}>
|
||||
<Trans i18nKey="alerting.mute_timings.error-loading.description">
|
||||
Could not load mute timings. Please try again later.
|
||||
<Alert severity="error" title={t('alerting.time-intervals.error-loading.title', 'Error loading time intervals')}>
|
||||
<Trans i18nKey="alerting.time-intervals.error-loading.description">
|
||||
Could not load time intervals. Please try again later.
|
||||
</Trans>
|
||||
</Alert>
|
||||
);
|
||||
@@ -75,20 +75,20 @@ export const MuteTimingsTable = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Trans i18nKey="alerting.mute-timings.description">
|
||||
<Trans i18nKey="alerting.time-intervals.description">
|
||||
Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of
|
||||
time.
|
||||
</Trans>
|
||||
<Spacer />
|
||||
{!hideActions && items.length > 0 && (
|
||||
<Authorize actions={[AlertmanagerAction.CreateMuteTiming]}>
|
||||
<Authorize actions={[AlertmanagerAction.CreateTimeInterval]}>
|
||||
<LinkButton
|
||||
className={styles.muteTimingsButtons}
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
|
||||
>
|
||||
<Trans i18nKey="alerting.mute-timings.add-mute-timing">Add mute timing</Trans>
|
||||
<Trans i18nKey="alerting.time-interval.add-time-interval">Add time interval</Trans>
|
||||
</LinkButton>
|
||||
</Authorize>
|
||||
)}
|
||||
@@ -113,10 +113,10 @@ export const MuteTimingsTable = () => {
|
||||
{!hideActions ? (
|
||||
<EmptyAreaWithCTA
|
||||
text={t(
|
||||
'alerting.mute-timings-table.text-havent-created-timings',
|
||||
"You haven't created any mute timings yet"
|
||||
'alerting.time-intervals-table.text-havent-created-time-intervals',
|
||||
"You haven't created any time intervals yet"
|
||||
)}
|
||||
buttonLabel="Add mute timing"
|
||||
buttonLabel="Add time interval"
|
||||
buttonIcon="plus"
|
||||
buttonSize="lg"
|
||||
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
|
||||
@@ -124,7 +124,10 @@ export const MuteTimingsTable = () => {
|
||||
/>
|
||||
) : (
|
||||
<EmptyAreaWithCTA
|
||||
text={t('alerting.mute-timings-table.text-no-mute-timings-configured', 'No mute timings configured')}
|
||||
text={t(
|
||||
'alerting.time-intervals-table.text-no-time-intervals-configured',
|
||||
'No time intervals configured'
|
||||
)}
|
||||
buttonLabel={''}
|
||||
showButton={false}
|
||||
/>
|
||||
@@ -137,8 +140,8 @@ export const MuteTimingsTable = () => {
|
||||
|
||||
function useColumns(alertManagerSourceName: string, hideActions = false) {
|
||||
const [[_editSupported, allowedToEdit], [_deleteSupported, allowedToDelete]] = useAlertmanagerAbilities([
|
||||
AlertmanagerAction.UpdateMuteTiming,
|
||||
AlertmanagerAction.DeleteMuteTiming,
|
||||
AlertmanagerAction.UpdateTimeInterval,
|
||||
AlertmanagerAction.DeleteTimeInterval,
|
||||
]);
|
||||
const showActions = !hideActions && (allowedToEdit || allowedToDelete);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ function NewMuteTimingPage() {
|
||||
return (
|
||||
<AlertmanagerPageWrapper
|
||||
navId="am-routes"
|
||||
pageNav={{ id: 'alert-policy-new', text: 'Add mute timing' }}
|
||||
pageNav={{ id: 'alert-policy-new', text: 'Add time interval' }}
|
||||
accessType="notification"
|
||||
>
|
||||
<MuteTimingForm />
|
||||
|
||||
+25
-1
@@ -52,7 +52,7 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
|
||||
const styles = useStyles2(getStyles);
|
||||
const formStyles = useStyles2(getFormStyles);
|
||||
const { selectedAlertmanager } = useAlertmanager();
|
||||
const [, canSeeMuteTimings] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
|
||||
const [, canSeeMuteTimings] = useAlertmanagerAbility(AlertmanagerAction.ViewTimeInterval);
|
||||
const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(route?.group_by));
|
||||
const emptyMatcher = [{ name: '', operator: MatcherOperator.equal, value: '' }];
|
||||
|
||||
@@ -309,6 +309,30 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
|
||||
name="muteTimeIntervals"
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={t('alerting.am-routes-expanded-form.am-active-timing-select-label-active-timings', 'Active timings')}
|
||||
data-testid="am-active-timing-select"
|
||||
description={t(
|
||||
'alerting.am-routes-expanded-form.am-mute-timing-select-description-add-active-timing-to-policy',
|
||||
'Add active timing to policy'
|
||||
)}
|
||||
invalid={!!errors.activeTimeIntervals}
|
||||
>
|
||||
<Controller
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<MuteTimingsSelector
|
||||
alertmanager={selectedAlertmanager!}
|
||||
selectProps={{
|
||||
...field,
|
||||
disabled: !canSeeMuteTimings,
|
||||
onChange: (value) => onChange(mapMultiSelectValueToStrings(value)),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
name="activeTimeIntervals"
|
||||
/>
|
||||
</Field>
|
||||
{actionButtons}
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -736,7 +736,7 @@ const TimeIntervals: FC<{ timings: string[]; alertManagerSourceName: string }> =
|
||||
timings,
|
||||
alertManagerSourceName,
|
||||
}) => {
|
||||
const [, canSeeMuteTimings] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
|
||||
const [, canSeeMuteTimings] = useAlertmanagerAbility(AlertmanagerAction.ViewTimeInterval);
|
||||
/* TODO make a better mute timing overview, allow combining multiple in to one overview */
|
||||
/*
|
||||
<HoverCard
|
||||
|
||||
+2
@@ -14,6 +14,7 @@ import { NeedHelpInfo } from '../../NeedHelpInfo';
|
||||
|
||||
import { ContactPointDetails } from './contactPoint/ContactPointDetails';
|
||||
import { ContactPointSelector } from './contactPoint/ContactPointSelector';
|
||||
import { ActiveTimingFields } from './route-settings/ActiveTimingFields';
|
||||
import { MuteTimingFields } from './route-settings/MuteTimingFields';
|
||||
import { RoutingSettings } from './route-settings/RouteSettings';
|
||||
|
||||
@@ -114,6 +115,7 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
||||
/>
|
||||
</Stack>
|
||||
<MuteTimingFields alertmanager={alertManagerName} />
|
||||
<ActiveTimingFields alertmanager={alertManagerName} />
|
||||
<RoutingSettings alertManager={alertManagerName} />
|
||||
</Stack>
|
||||
</CollapsableSection>
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ export function SimplifiedRouting() {
|
||||
selectedContactPoint: selectedContactPoint?.selectedContactPoint ?? '',
|
||||
routeSettings: {
|
||||
muteTimeIntervals: selectedContactPoint?.muteTimeIntervals ?? [],
|
||||
activeTimeIntervals: selectedContactPoint?.activeTimeIntervals ?? [],
|
||||
overrideGrouping: selectedContactPoint?.overrideGrouping ?? false,
|
||||
groupBy: selectedContactPoint?.groupBy ?? [],
|
||||
overrideTimings: selectedContactPoint?.overrideTimings ?? false,
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Field, useStyles2 } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import MuteTimingsSelector from 'app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector';
|
||||
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
|
||||
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
|
||||
import { mapMultiSelectValueToStrings } from 'app/features/alerting/unified/utils/amroutes';
|
||||
|
||||
/** Provides a form field for use in simplified routing, for selecting appropriate mute timings */
|
||||
export function ActiveTimingFields({ alertmanager }: BaseAlertmanagerArgs) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useFormContext<RuleFormValues>();
|
||||
|
||||
return (
|
||||
<Field
|
||||
label={t('alerting.active-timing-fields.am-active-timing-select-label-active-timings', 'Active timings')}
|
||||
data-testid="am-active-timing-select"
|
||||
description={t(
|
||||
'alerting.mute-timing-fields.am-active-timing-select-description-active-timings',
|
||||
'Select a time interval to define when not to send notifications for this alert rule'
|
||||
)}
|
||||
className={styles.muteTimingField}
|
||||
invalid={!!errors.contactPoints?.[alertmanager]?.activeTimeIntervals}
|
||||
>
|
||||
<Controller
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<MuteTimingsSelector
|
||||
alertmanager={alertmanager}
|
||||
selectProps={{
|
||||
...field,
|
||||
onChange: (value) => onChange(mapMultiSelectValueToStrings(value)),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
name={`contactPoints.${alertmanager}.activeTimeIntervals`}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
muteTimingField: css({
|
||||
marginTop: theme.spacing(1),
|
||||
}),
|
||||
});
|
||||
+54
-54
@@ -96,10 +96,6 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"create-mute-timing": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"create-notification-policy": [
|
||||
false,
|
||||
false,
|
||||
@@ -112,6 +108,10 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"create-time-interval": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"decrypt-secrets": [
|
||||
false,
|
||||
false,
|
||||
@@ -120,10 +120,6 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"delete-mute-timing": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"delete-notification-policy": [
|
||||
false,
|
||||
false,
|
||||
@@ -132,6 +128,10 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"delete-time-interval": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"edit-contact-points": [
|
||||
false,
|
||||
false,
|
||||
@@ -144,11 +144,11 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"export-mute-timings": [
|
||||
"export-notification-policies": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"export-notification-policies": [
|
||||
"export-time-intervals": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
@@ -160,10 +160,6 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"update-mute-timing": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"update-notification-policy-tree": [
|
||||
false,
|
||||
false,
|
||||
@@ -172,6 +168,10 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"update-time-interval": [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"view-alert-groups": [
|
||||
true,
|
||||
false,
|
||||
@@ -188,10 +188,6 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"view-mute-timing": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"view-notification-policy-tree": [
|
||||
true,
|
||||
false,
|
||||
@@ -204,6 +200,10 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"view-time-interval": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -213,10 +213,6 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"create-mute-timing": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"create-notification-policy": [
|
||||
true,
|
||||
true,
|
||||
@@ -229,6 +225,10 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"create-time-interval": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"decrypt-secrets": [
|
||||
false,
|
||||
false,
|
||||
@@ -237,10 +237,6 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"delete-mute-timing": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"delete-notification-policy": [
|
||||
true,
|
||||
true,
|
||||
@@ -249,6 +245,10 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"delete-time-interval": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"edit-contact-points": [
|
||||
true,
|
||||
true,
|
||||
@@ -261,11 +261,11 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"export-mute-timings": [
|
||||
"export-notification-policies": [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"export-notification-policies": [
|
||||
"export-time-intervals": [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
@@ -277,10 +277,6 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"update-mute-timing": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"update-notification-policy-tree": [
|
||||
true,
|
||||
true,
|
||||
@@ -289,6 +285,10 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"update-time-interval": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-alert-groups": [
|
||||
true,
|
||||
true,
|
||||
@@ -305,10 +305,6 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-mute-timing": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-notification-policy-tree": [
|
||||
true,
|
||||
true,
|
||||
@@ -321,6 +317,10 @@ exports[`alertmanager abilities should report everything except exporting for Mi
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-time-interval": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -330,10 +330,6 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"create-mute-timing": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"create-notification-policy": [
|
||||
true,
|
||||
false,
|
||||
@@ -346,6 +342,10 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"create-time-interval": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"decrypt-secrets": [
|
||||
true,
|
||||
false,
|
||||
@@ -354,10 +354,6 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"delete-mute-timing": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"delete-notification-policy": [
|
||||
true,
|
||||
false,
|
||||
@@ -366,6 +362,10 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"delete-time-interval": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"edit-contact-points": [
|
||||
true,
|
||||
false,
|
||||
@@ -378,11 +378,11 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"export-mute-timings": [
|
||||
"export-notification-policies": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"export-notification-policies": [
|
||||
"export-time-intervals": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
@@ -394,10 +394,6 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"update-mute-timing": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"update-notification-policy-tree": [
|
||||
true,
|
||||
false,
|
||||
@@ -406,6 +402,10 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"update-time-interval": [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"view-alert-groups": [
|
||||
true,
|
||||
true,
|
||||
@@ -422,10 +422,6 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
false,
|
||||
],
|
||||
"view-mute-timing": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-notification-policy-tree": [
|
||||
true,
|
||||
true,
|
||||
@@ -438,5 +434,9 @@ exports[`alertmanager abilities should report everything is supported for builti
|
||||
true,
|
||||
true,
|
||||
],
|
||||
"view-time-interval": [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -66,12 +66,12 @@ export enum AlertmanagerAction {
|
||||
UpdateSilence = 'update-silence',
|
||||
PreviewSilencedInstances = 'preview-silenced-alerts',
|
||||
|
||||
// mute timings
|
||||
ViewMuteTiming = 'view-mute-timing',
|
||||
CreateMuteTiming = 'create-mute-timing',
|
||||
UpdateMuteTiming = 'update-mute-timing',
|
||||
DeleteMuteTiming = 'delete-mute-timing',
|
||||
ExportMuteTimings = 'export-mute-timings',
|
||||
// time intervals
|
||||
ViewTimeInterval = 'view-time-interval',
|
||||
CreateTimeInterval = 'create-time-interval',
|
||||
UpdateTimeInterval = 'update-time-interval',
|
||||
DeleteTimeInterval = 'delete-time-interval',
|
||||
ExportTimeIntervals = 'export-time-intervals',
|
||||
|
||||
// Alert groups
|
||||
ViewAlertGroups = 'view-alert-groups',
|
||||
@@ -421,28 +421,28 @@ export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> {
|
||||
[AlertmanagerAction.ViewSilence]: toAbility(AlwaysSupported, instancePermissions.read),
|
||||
[AlertmanagerAction.UpdateSilence]: toAbility(AlwaysSupported, instancePermissions.update),
|
||||
[AlertmanagerAction.PreviewSilencedInstances]: toAbility(AlwaysSupported, instancePermissions.read),
|
||||
// -- mute timings --
|
||||
[AlertmanagerAction.CreateMuteTiming]: toAbility(
|
||||
// -- time intervals --
|
||||
[AlertmanagerAction.CreateTimeInterval]: toAbility(
|
||||
hasConfigurationAPI,
|
||||
notificationsPermissions.create,
|
||||
...(isGrafanaFlavoredAlertmanager ? PERMISSIONS_TIME_INTERVALS_MODIFY : [])
|
||||
),
|
||||
[AlertmanagerAction.ViewMuteTiming]: toAbility(
|
||||
[AlertmanagerAction.ViewTimeInterval]: toAbility(
|
||||
AlwaysSupported,
|
||||
notificationsPermissions.read,
|
||||
...(isGrafanaFlavoredAlertmanager ? PERMISSIONS_TIME_INTERVALS_READ : [])
|
||||
),
|
||||
[AlertmanagerAction.UpdateMuteTiming]: toAbility(
|
||||
[AlertmanagerAction.UpdateTimeInterval]: toAbility(
|
||||
hasConfigurationAPI,
|
||||
notificationsPermissions.update,
|
||||
...(isGrafanaFlavoredAlertmanager ? PERMISSIONS_TIME_INTERVALS_MODIFY : [])
|
||||
),
|
||||
[AlertmanagerAction.DeleteMuteTiming]: toAbility(
|
||||
[AlertmanagerAction.DeleteTimeInterval]: toAbility(
|
||||
hasConfigurationAPI,
|
||||
notificationsPermissions.delete,
|
||||
...(isGrafanaFlavoredAlertmanager ? PERMISSIONS_TIME_INTERVALS_MODIFY : [])
|
||||
),
|
||||
[AlertmanagerAction.ExportMuteTimings]: toAbility(isGrafanaFlavoredAlertmanager, notificationsPermissions.read),
|
||||
[AlertmanagerAction.ExportTimeIntervals]: toAbility(isGrafanaFlavoredAlertmanager, notificationsPermissions.read),
|
||||
[AlertmanagerAction.ViewAlertGroups]: toAbility(AlwaysSupported, instancePermissions.read),
|
||||
};
|
||||
|
||||
|
||||
+2
@@ -59,6 +59,7 @@ exports[`routes Should add a new route with receiver E as a child of default rou
|
||||
"routes": [],
|
||||
},
|
||||
{
|
||||
"active_time_intervals": undefined,
|
||||
"continue": undefined,
|
||||
"group_by": undefined,
|
||||
"group_interval": undefined,
|
||||
@@ -126,6 +127,7 @@ exports[`routes Should update an existing route A with receiver B 1`] = `
|
||||
"receiver": "ROOT",
|
||||
"routes": [
|
||||
{
|
||||
"active_time_intervals": undefined,
|
||||
"continue": undefined,
|
||||
"group_by": undefined,
|
||||
"group_interval": undefined,
|
||||
|
||||
@@ -12,5 +12,6 @@ export interface FormAmRoute {
|
||||
groupIntervalValue: string;
|
||||
repeatIntervalValue: string;
|
||||
muteTimeIntervals: string[];
|
||||
activeTimeIntervals: string[];
|
||||
routes: FormAmRoute[];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ContactPoint {
|
||||
groupIntervalValue: string;
|
||||
repeatIntervalValue: string;
|
||||
muteTimeIntervals: string[];
|
||||
activeTimeIntervals: string[];
|
||||
}
|
||||
|
||||
// key: name of alert manager, value ContactPoint
|
||||
|
||||
@@ -8,6 +8,7 @@ exports[`addRouteToReferenceRoute should be able to add above 1`] = `
|
||||
"id": "route-2",
|
||||
},
|
||||
{
|
||||
"active_time_intervals": undefined,
|
||||
"continue": undefined,
|
||||
"group_by": undefined,
|
||||
"group_interval": undefined,
|
||||
@@ -39,6 +40,7 @@ exports[`addRouteToReferenceRoute should be able to add as child 1`] = `
|
||||
"id": "route-3",
|
||||
"routes": [
|
||||
{
|
||||
"active_time_intervals": undefined,
|
||||
"continue": undefined,
|
||||
"group_by": undefined,
|
||||
"group_interval": undefined,
|
||||
@@ -69,6 +71,7 @@ exports[`addRouteToReferenceRoute should be able to add below 1`] = `
|
||||
"id": "route-3",
|
||||
},
|
||||
{
|
||||
"active_time_intervals": undefined,
|
||||
"continue": undefined,
|
||||
"group_by": undefined,
|
||||
"group_interval": undefined,
|
||||
|
||||
@@ -60,6 +60,7 @@ export const emptyRoute: FormAmRoute = {
|
||||
groupIntervalValue: '',
|
||||
repeatIntervalValue: '',
|
||||
muteTimeIntervals: [],
|
||||
activeTimeIntervals: [],
|
||||
};
|
||||
|
||||
// add unique identifiers to each route in the route tree, that way we can figure out what route we've edited / deleted
|
||||
@@ -128,6 +129,7 @@ export const amRouteToFormAmRoute = (route: RouteWithID | undefined): FormAmRout
|
||||
repeatIntervalValue: route.repeat_interval ?? '',
|
||||
routes: formRoutes,
|
||||
muteTimeIntervals: route.mute_time_intervals ?? [],
|
||||
activeTimeIntervals: route.active_time_intervals ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -184,6 +186,7 @@ export const formAmRouteToAmRoute = (
|
||||
repeat_interval,
|
||||
routes: routes,
|
||||
mute_time_intervals: formAmRoute.muteTimeIntervals,
|
||||
active_time_intervals: formAmRoute.activeTimeIntervals,
|
||||
receiver: receiver,
|
||||
};
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ describe('getContactPointsFromDTO', () => {
|
||||
notification_settings: {
|
||||
receiver: 'receiver',
|
||||
mute_time_intervals: ['mute_timing'],
|
||||
active_time_intervals: ['active_timing'],
|
||||
group_by: ['group_by'],
|
||||
group_wait: 'group_wait',
|
||||
group_interval: 'group_interval',
|
||||
@@ -170,6 +171,7 @@ describe('getContactPointsFromDTO', () => {
|
||||
[GRAFANA_RULES_SOURCE_NAME]: {
|
||||
selectedContactPoint: 'receiver',
|
||||
muteTimeIntervals: ['mute_timing'],
|
||||
activeTimeIntervals: ['active_timing'],
|
||||
overrideGrouping: true,
|
||||
overrideTimings: true,
|
||||
groupBy: ['group_by'],
|
||||
@@ -188,6 +190,7 @@ describe('getNotificationSettingsForDTO', () => {
|
||||
grafana: {
|
||||
selectedContactPoint: 'receiver',
|
||||
muteTimeIntervals: ['mute_timing'],
|
||||
activeTimeIntervals: ['active_timing'],
|
||||
overrideGrouping: true,
|
||||
overrideTimings: true,
|
||||
groupBy: ['group_by'],
|
||||
@@ -214,6 +217,7 @@ describe('getNotificationSettingsForDTO', () => {
|
||||
grafana: {
|
||||
selectedContactPoint: 'receiver',
|
||||
muteTimeIntervals: ['mute_timing'],
|
||||
activeTimeIntervals: ['active_timing'],
|
||||
overrideGrouping: true,
|
||||
overrideTimings: true,
|
||||
groupBy: ['group_by'],
|
||||
@@ -227,6 +231,7 @@ describe('getNotificationSettingsForDTO', () => {
|
||||
expect(result).toEqual({
|
||||
receiver: 'receiver',
|
||||
mute_time_intervals: ['mute_timing'],
|
||||
active_time_intervals: ['active_timing'],
|
||||
group_by: ['group_by'],
|
||||
group_wait: 'group_wait',
|
||||
group_interval: 'group_interval',
|
||||
|
||||
@@ -112,6 +112,7 @@ export function getNotificationSettingsForDTO(
|
||||
return {
|
||||
receiver: contactPoints?.grafana?.selectedContactPoint,
|
||||
mute_time_intervals: contactPoints?.grafana?.muteTimeIntervals,
|
||||
active_time_intervals: contactPoints?.grafana?.activeTimeIntervals,
|
||||
group_by: contactPoints?.grafana?.overrideGrouping ? contactPoints?.grafana?.groupBy : undefined,
|
||||
group_wait:
|
||||
contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.groupWaitValue
|
||||
@@ -233,6 +234,7 @@ export function getContactPointsFromDTO(ga: GrafanaRuleDefinition): AlertManager
|
||||
? {
|
||||
selectedContactPoint: ga.notification_settings.receiver,
|
||||
muteTimeIntervals: ga.notification_settings.mute_time_intervals ?? [],
|
||||
activeTimeIntervals: ga.notification_settings.active_time_intervals ?? [],
|
||||
overrideGrouping:
|
||||
Array.isArray(ga.notification_settings.group_by) && ga.notification_settings.group_by.length > 0,
|
||||
overrideTimings: [
|
||||
|
||||
@@ -245,6 +245,7 @@ export interface GrafanaNotificationSettings {
|
||||
group_interval?: string;
|
||||
repeat_interval?: string;
|
||||
mute_time_intervals?: string[];
|
||||
active_time_intervals?: string[];
|
||||
}
|
||||
|
||||
export interface GrafanaEditorSettings {
|
||||
|
||||
@@ -320,6 +320,9 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"active-timing-fields": {
|
||||
"am-active-timing-select-label-active-timings": "Active timings"
|
||||
},
|
||||
"add-button": {
|
||||
"add-more": "Add more"
|
||||
},
|
||||
@@ -581,6 +584,8 @@
|
||||
},
|
||||
"am-routes-expanded-form": {
|
||||
"add-matcher": "Add matcher",
|
||||
"am-active-timing-select-label-active-timings": "Active timings",
|
||||
"am-mute-timing-select-description-add-active-timing-to-policy": "Add active timing to policy",
|
||||
"am-mute-timing-select-description-add-mute-timing-to-policy": "Add mute timing to policy",
|
||||
"am-mute-timing-select-label-mute-timings": "Mute timings",
|
||||
"aria-label-group-by": "Group by",
|
||||
@@ -1489,29 +1494,21 @@
|
||||
"aria-label": "More",
|
||||
"button-text": "More"
|
||||
},
|
||||
"mute_timings": {
|
||||
"error-loading": {
|
||||
"description": "Could not load mute timings. Please try again later.",
|
||||
"title": "Error loading mute timings"
|
||||
}
|
||||
},
|
||||
"mute-timing-actions-buttons": {
|
||||
"text-disabled": "Disabled",
|
||||
"title-delete-mute-timing": "Delete mute timing"
|
||||
},
|
||||
"mute-timing-fields": {
|
||||
"am-active-timing-select-description-active-timings": "Select a time interval to define when not to send notifications for this alert rule",
|
||||
"am-mute-timing-select-description-mute-timings": "Select a mute timing to define when not to send notifications for this alert rule",
|
||||
"am-mute-timing-select-label-mute-timings": "Mute timings"
|
||||
},
|
||||
"mute-timing-form": {
|
||||
"description-unique-timing": "A unique name for the mute timing",
|
||||
"label-name": "Name",
|
||||
"text-loading-mute-timing": "Loading mute timing",
|
||||
"title-no-matching-mute-timing-found": "No matching mute timing found"
|
||||
"label-name": "Name"
|
||||
},
|
||||
"mute-timing-time-interval": {
|
||||
"add-another-time-interval": "Add another time interval",
|
||||
"description": "A time interval is a definition for a moment in time. All fields are lists, and at least one list element must be satisfied to match the field. If a field is left blank, any moment of time will match the field. For an instant of time to match a complete time interval, all fields must match. A mute timing can contain multiple time intervals.",
|
||||
"add-another-time-interval-item": "Add another time interval item",
|
||||
"description": "A time interval item is a definition for a moment in time. All fields are lists, and at least one list element must be satisfied to match the field. If a field is left blank, any moment of time will match the field. For an instant of time to match a complete time interval, all fields must match. A time interval can contain multiple time interval items.",
|
||||
"description-dats-of-the-month": "The days of the month, 1:31, of a month. Negative values can be used to represent days which begin at the end of the month",
|
||||
"description-months": "The months of the year in either numerical or the full calendar month",
|
||||
"label-days-of-the-month": "Days of the month",
|
||||
@@ -1538,21 +1535,6 @@
|
||||
"title-remove": "Remove",
|
||||
"tooltip-remove-time-range": "Remove time range"
|
||||
},
|
||||
"mute-timings": {
|
||||
"add-mute-timing": "Add mute timing",
|
||||
"description": "Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of time.",
|
||||
"save": "Save mute timing",
|
||||
"saving": "Saving mute timing"
|
||||
},
|
||||
"mute-timings-selector": {
|
||||
"aria-label-mute-timings": "Mute timings",
|
||||
"placeholder-select-mute-timings": "Select mute timings..."
|
||||
},
|
||||
"mute-timings-table": {
|
||||
"text-havent-created-timings": "You haven't created any mute timings yet",
|
||||
"text-loading-mute-timings": "Loading mute timings...",
|
||||
"text-no-mute-timings-configured": "No mute timings configured"
|
||||
},
|
||||
"namespace": {
|
||||
"title-alert-rules": "Alert rules"
|
||||
},
|
||||
@@ -1593,8 +1575,8 @@
|
||||
"title-notification-policies-have-changed": "Notification policies have changed"
|
||||
},
|
||||
"notification-policies-tabs": {
|
||||
"label-mute-timings": "Mute Timings",
|
||||
"label-notification-policies": "Notification Policies"
|
||||
"label-notification-policies": "Notification Policies",
|
||||
"label-time-intervals": "Time intervals"
|
||||
},
|
||||
"notification-policy-matchers": {
|
||||
"default-policy": "Default policy",
|
||||
@@ -2430,6 +2412,32 @@
|
||||
"input": "Input",
|
||||
"stop-alerting-when": "Stop alerting (or pending state) when "
|
||||
},
|
||||
"time-interval": {
|
||||
"add-time-interval": "Add time interval",
|
||||
"save": "Save time interval",
|
||||
"saving": "Saving time interval"
|
||||
},
|
||||
"time-interval-form": {
|
||||
"description-unique-time-interval": "A unique name for the time interval",
|
||||
"text-loading-time-interval": "Loading time interval",
|
||||
"title-no-matching-time-interval-found": "No matching time interval found"
|
||||
},
|
||||
"time-intervals": {
|
||||
"description": "Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of time.",
|
||||
"error-loading": {
|
||||
"description": "Could not load time intervals. Please try again later.",
|
||||
"title": "Error loading time intervals"
|
||||
}
|
||||
},
|
||||
"time-intervals-selector": {
|
||||
"aria-label-time-intervals": "Time intervals",
|
||||
"placeholder-select-time-intervals": "Select time intervals..."
|
||||
},
|
||||
"time-intervals-table": {
|
||||
"text-havent-created-time-intervals": "You haven't created any time intervals yet",
|
||||
"text-loading-time-intervals": "Loading time intervals...",
|
||||
"text-no-time-intervals-configured": "No time intervals configured"
|
||||
},
|
||||
"timeseries-row": {
|
||||
"time-series-data": "Time series data",
|
||||
"timestamp": "Timestamp",
|
||||
|
||||
Reference in New Issue
Block a user