You can now get alert rules for a dashboard or a panel using /api/v1/rules endpoints. (#39476)
Get alert rules for a dashboard and panel in /api/v1/rules
This commit is contained in:
@@ -65,7 +65,7 @@ func TestAlertRulePermissions(t *testing.T) {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
assert.Equal(t, resp.StatusCode, 200)
|
||||
|
||||
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||
expectedGetNamespaceResponseBody := `
|
||||
@@ -187,7 +187,7 @@ func TestAlertRulePermissions(t *testing.T) {
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
assert.Equal(t, resp.StatusCode, 200)
|
||||
|
||||
body, _ = rulesNamespaceWithoutVariableValues(t, b)
|
||||
expectedGetNamespaceResponseBody = `
|
||||
@@ -427,3 +427,349 @@ func TestAlertRuleConflictingTitle(t *testing.T) {
|
||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRulerRulesFilterByDashboard(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
// override bus to get the GetSignedInUserQuery handler
|
||||
store.Bus = bus.GetBus()
|
||||
|
||||
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
||||
dashboardUID, err := createFolder(t, store, 0, "default")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
createUser(t, store, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
})
|
||||
|
||||
interval, err := model.ParseDuration("10s")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now, let's create some rules
|
||||
{
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
Name: "anotherrulegroup",
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: interval,
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
"__dashboardUid__": dashboardUID,
|
||||
"__panelId__": "1",
|
||||
},
|
||||
},
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiring",
|
||||
Condition: "A",
|
||||
Data: []ngmodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{
|
||||
From: ngmodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: ngmodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiringButSilenced",
|
||||
Condition: "A",
|
||||
Data: []ngmodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{
|
||||
From: ngmodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: ngmodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(&rules)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Post(u, "application/json", &buf)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
||||
}
|
||||
|
||||
expectedAllJSON := fmt.Sprintf(`
|
||||
{
|
||||
"default": [{
|
||||
"name": "anotherrulegroup",
|
||||
"interval": "1m",
|
||||
"rules": [{
|
||||
"expr": "",
|
||||
"for": "10s",
|
||||
"annotations": {
|
||||
"__dashboardUid__": "%s",
|
||||
"__panelId__": "1"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 1,
|
||||
"orgId": 1,
|
||||
"title": "AlwaysFiring",
|
||||
"condition": "A",
|
||||
"data": [{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "-100",
|
||||
"model": {
|
||||
"expression": "2 + 3 \u003e 1",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}],
|
||||
"updated": "2021-02-21T01:10:30Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "uid",
|
||||
"namespace_uid": "nsuid",
|
||||
"namespace_id": 1,
|
||||
"rule_group": "anotherrulegroup",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}, {
|
||||
"expr": "",
|
||||
"grafana_alert": {
|
||||
"id": 2,
|
||||
"orgId": 1,
|
||||
"title": "AlwaysFiringButSilenced",
|
||||
"condition": "A",
|
||||
"data": [{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "-100",
|
||||
"model": {
|
||||
"expression": "2 + 3 \u003e 1",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}],
|
||||
"updated": "2021-02-21T01:10:30Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "uid",
|
||||
"namespace_uid": "nsuid",
|
||||
"namespace_id": 1,
|
||||
"rule_group": "anotherrulegroup",
|
||||
"no_data_state": "Alerting",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}`, dashboardUID)
|
||||
expectedFilteredByJSON := fmt.Sprintf(`
|
||||
{
|
||||
"default": [{
|
||||
"name": "anotherrulegroup",
|
||||
"interval": "1m",
|
||||
"rules": [{
|
||||
"expr": "",
|
||||
"for": "10s",
|
||||
"annotations": {
|
||||
"__dashboardUid__": "%s",
|
||||
"__panelId__": "1"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 1,
|
||||
"orgId": 1,
|
||||
"title": "AlwaysFiring",
|
||||
"condition": "A",
|
||||
"data": [{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "-100",
|
||||
"model": {
|
||||
"expression": "2 + 3 \u003e 1",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}],
|
||||
"updated": "2021-02-21T01:10:30Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "uid",
|
||||
"namespace_uid": "nsuid",
|
||||
"namespace_id": 1,
|
||||
"rule_group": "anotherrulegroup",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}`, dashboardUID)
|
||||
expectedNoneJSON := `{}`
|
||||
|
||||
// Now, let's see how this looks like.
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||
require.JSONEq(t, expectedAllJSON, body)
|
||||
}
|
||||
|
||||
// Now, let's check we get the same rule when filtering by dashboard_uid
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, dashboardUID)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||
require.JSONEq(t, expectedFilteredByJSON, body)
|
||||
}
|
||||
|
||||
// Now, let's check we get no rules when filtering by an unknown dashboard_uid
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, "abc")
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
require.JSONEq(t, expectedNoneJSON, string(b))
|
||||
}
|
||||
|
||||
// Now, let's check we get the same rule when filtering by dashboard_uid and panel_id
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=1", grafanaListedAddr, dashboardUID)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||
require.JSONEq(t, expectedFilteredByJSON, body)
|
||||
}
|
||||
|
||||
// Now, let's check we get no rules when filtering by dashboard_uid and unknown panel_id
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=2", grafanaListedAddr, dashboardUID)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
require.JSONEq(t, expectedNoneJSON, string(b))
|
||||
}
|
||||
|
||||
// Now, let's check an invalid panel_id returns a 400 Bad Request response
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=invalid", grafanaListedAddr, dashboardUID)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{"message":"invalid panel_id: strconv.ParseInt: parsing \"invalid\": invalid syntax"}`, string(b))
|
||||
}
|
||||
|
||||
// Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response
|
||||
{
|
||||
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?panel_id=1", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(promRulesURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{"message":"panel_id must be set with dashboard_uid"}`, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user