mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Display query from grafana-managed alert rules on /api/v1/rules (#45969)
* Aleting: Extract query from alerting rule model for api/v1/rules * more changes and fixtures * appease the linter
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -16,7 +25,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
fakeAlertInstanceManager := NewFakeAlertInstanceManager(t)
|
||||
orgID := int64(1)
|
||||
|
||||
server := PrometheusSrv{
|
||||
api := PrometheusSrv{
|
||||
log: log.NewNopLogger(),
|
||||
manager: fakeAlertInstanceManager,
|
||||
store: fakeStore,
|
||||
@@ -25,7 +34,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
c := &models.ReqContext{SignedInUser: &models.SignedInUser{OrgId: orgID}}
|
||||
|
||||
t.Run("with no alerts", func(t *testing.T) {
|
||||
r := server.RouteGetAlertStatuses(c)
|
||||
r := api.RouteGetAlertStatuses(c)
|
||||
require.Equal(t, http.StatusOK, r.Status())
|
||||
require.JSONEq(t, `
|
||||
{
|
||||
@@ -38,8 +47,8 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with two alerts", func(t *testing.T) {
|
||||
fakeAlertInstanceManager.GenerateAlertInstances(1, 2)
|
||||
r := server.RouteGetAlertStatuses(c)
|
||||
fakeAlertInstanceManager.GenerateAlertInstances(1, util.GenerateShortUID(), 2)
|
||||
r := api.RouteGetAlertStatuses(c)
|
||||
require.Equal(t, http.StatusOK, r.Status())
|
||||
require.JSONEq(t, `
|
||||
{
|
||||
@@ -78,3 +87,224 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
}`, string(r.Body()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) }
|
||||
orgID := int64(1)
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/v1/rules", nil)
|
||||
require.NoError(t, err)
|
||||
c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
|
||||
|
||||
t.Run("with no rules", func(t *testing.T) {
|
||||
_, _, api := setupAPI(t)
|
||||
r := api.RouteGetRuleStatuses(c)
|
||||
|
||||
require.JSONEq(t, `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"groups": []
|
||||
}
|
||||
}
|
||||
`, string(r.Body()))
|
||||
})
|
||||
|
||||
t.Run("with a rule that only has one query", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
|
||||
|
||||
r := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, r.Status())
|
||||
require.JSONEq(t, `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"groups": [{
|
||||
"name": "rule-group",
|
||||
"file": "namespaceUID",
|
||||
"rules": [{
|
||||
"state": "inactive",
|
||||
"name": "AlwaysFiring",
|
||||
"query": "vector(1)",
|
||||
"alerts": [{
|
||||
"labels": {
|
||||
"job": "prometheus"
|
||||
},
|
||||
"annotations": {
|
||||
"severity": "critical"
|
||||
},
|
||||
"state": "Normal",
|
||||
"activeAt": "0001-01-01T00:00:00Z",
|
||||
"value": ""
|
||||
}],
|
||||
"labels": null,
|
||||
"health": "ok",
|
||||
"lastError": "",
|
||||
"type": "alerting",
|
||||
"lastEvaluation": "2022-03-10T14:01:00Z",
|
||||
"duration": 180,
|
||||
"evaluationTime": 60
|
||||
}],
|
||||
"interval": 60,
|
||||
"lastEvaluation": "2022-03-10T14:01:00Z",
|
||||
"evaluationTime": 0
|
||||
}]
|
||||
}
|
||||
}
|
||||
`, string(r.Body()))
|
||||
})
|
||||
|
||||
t.Run("with a rule that has multiple queries", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery())
|
||||
|
||||
r := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, r.Status())
|
||||
require.JSONEq(t, `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"groups": [{
|
||||
"name": "rule-group",
|
||||
"file": "namespaceUID",
|
||||
"rules": [{
|
||||
"state": "inactive",
|
||||
"name": "AlwaysFiring",
|
||||
"query": "vector(1) | vector(1)",
|
||||
"alerts": [{
|
||||
"labels": {
|
||||
"job": "prometheus"
|
||||
},
|
||||
"annotations": {
|
||||
"severity": "critical"
|
||||
},
|
||||
"state": "Normal",
|
||||
"activeAt": "0001-01-01T00:00:00Z",
|
||||
"value": ""
|
||||
}],
|
||||
"labels": null,
|
||||
"health": "ok",
|
||||
"lastError": "",
|
||||
"type": "alerting",
|
||||
"lastEvaluation": "2022-03-10T14:01:00Z",
|
||||
"duration": 180,
|
||||
"evaluationTime": 60
|
||||
}],
|
||||
"interval": 60,
|
||||
"lastEvaluation": "2022-03-10T14:01:00Z",
|
||||
"evaluationTime": 0
|
||||
}]
|
||||
}
|
||||
}
|
||||
`, string(r.Body()))
|
||||
})
|
||||
}
|
||||
|
||||
func setupAPI(t *testing.T) (*store.FakeRuleStore, *fakeAlertInstanceManager, PrometheusSrv) {
|
||||
fakeStore := store.NewFakeRuleStore(t)
|
||||
fakeAIM := NewFakeAlertInstanceManager(t)
|
||||
api := PrometheusSrv{
|
||||
log: log.NewNopLogger(),
|
||||
manager: fakeAIM,
|
||||
store: fakeStore,
|
||||
}
|
||||
|
||||
return fakeStore, fakeAIM, api
|
||||
}
|
||||
|
||||
func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *store.FakeRuleStore, query func(r *ngmodels.AlertRule)) {
|
||||
t.Helper()
|
||||
|
||||
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), asFixture(), query))
|
||||
|
||||
fakeAIM.GenerateAlertInstances(orgID, rules[0].UID, 1, func(s *state.State) *state.State {
|
||||
s.Labels = data.Labels{"job": "prometheus"}
|
||||
s.Annotations = data.Labels{"severity": "critical"}
|
||||
return s
|
||||
})
|
||||
|
||||
for _, r := range rules {
|
||||
fakeStore.PutRule(context.Background(), r)
|
||||
}
|
||||
}
|
||||
|
||||
// asFixture removes variable values of the alert rule.
|
||||
// we're not too interested in variability of the rule in this scenario.
|
||||
func asFixture() func(r *ngmodels.AlertRule) {
|
||||
return func(r *ngmodels.AlertRule) {
|
||||
r.Title = "AlwaysFiring"
|
||||
r.NamespaceUID = "namespaceUID"
|
||||
r.RuleGroup = "rule-group"
|
||||
r.UID = "RuleUID"
|
||||
r.Labels = nil
|
||||
r.Annotations = nil
|
||||
r.IntervalSeconds = 60
|
||||
r.For = 180 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) {
|
||||
return func(r *ngmodels.AlertRule) {
|
||||
queries := []ngmodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "AUID",
|
||||
Model: json.RawMessage(fmt.Sprintf(prometheusQueryModel, "A")),
|
||||
},
|
||||
{
|
||||
RefID: "B",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(fmt.Sprintf(classicConditionsModel, "A", "B")),
|
||||
},
|
||||
}
|
||||
r.Data = queries
|
||||
}
|
||||
}
|
||||
|
||||
func withExpressionsMultiQuery() func(r *ngmodels.AlertRule) {
|
||||
return func(r *ngmodels.AlertRule) {
|
||||
queries := []ngmodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "AUID",
|
||||
Model: json.RawMessage(fmt.Sprintf(prometheusQueryModel, "A")),
|
||||
},
|
||||
{
|
||||
RefID: "B",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "BUID",
|
||||
Model: json.RawMessage(fmt.Sprintf(prometheusQueryModel, "B")),
|
||||
},
|
||||
{
|
||||
RefID: "C",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(fmt.Sprintf(reduceLastExpressionModel, "A", "C")),
|
||||
},
|
||||
{
|
||||
RefID: "D",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(fmt.Sprintf(reduceLastExpressionModel, "B", "D")),
|
||||
},
|
||||
{
|
||||
RefID: "E",
|
||||
QueryType: "",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(fmt.Sprintf(mathExpressionModel, "A", "B", "E")),
|
||||
},
|
||||
}
|
||||
r.Data = queries
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user