mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 04:04:00 -06:00
Alerting: Add filters for RouteGetRuleStatuses (#88295)
* Placeholder commit with rule_uid change * Add new filters to grafana rule state API * Revert type change * Split rule_group and rule_name params * remove debug line * Change how query params are parsed * Comment
This commit is contained in:
parent
783aa1c0b4
commit
b66cd7ef79
@ -235,6 +235,9 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
|
||||
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
|
||||
}
|
||||
|
||||
// TODO: Refactor this function to reduce the cylomatic complexity
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager, store ListAlertRulesStore, opts RuleGroupStatusesOptions) apimodels.RuleResponse {
|
||||
ruleResponse := apimodels.RuleResponse{
|
||||
DiscoveryBase: apimodels.DiscoveryBase{
|
||||
@ -292,16 +295,26 @@ func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager
|
||||
return ruleResponse
|
||||
}
|
||||
|
||||
namespaceUIDs := make([]string, len(opts.Namespaces))
|
||||
for k := range opts.Namespaces {
|
||||
namespaceUIDs = append(namespaceUIDs, k)
|
||||
namespaceUIDs := make([]string, 0, len(opts.Namespaces))
|
||||
|
||||
folderUID := opts.Query.Get("folder_uid")
|
||||
_, exists := opts.Namespaces[folderUID]
|
||||
if folderUID != "" && exists {
|
||||
namespaceUIDs = append(namespaceUIDs, folderUID)
|
||||
} else {
|
||||
for k := range opts.Namespaces {
|
||||
namespaceUIDs = append(namespaceUIDs, k)
|
||||
}
|
||||
}
|
||||
|
||||
ruleGroups := opts.Query["rule_group"]
|
||||
|
||||
alertRuleQuery := ngmodels.ListAlertRulesQuery{
|
||||
OrgID: opts.OrgID,
|
||||
NamespaceUIDs: namespaceUIDs,
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
RuleGroups: ruleGroups,
|
||||
}
|
||||
ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery)
|
||||
if err != nil {
|
||||
@ -311,10 +324,23 @@ func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager
|
||||
return ruleResponse
|
||||
}
|
||||
|
||||
ruleNames := opts.Query["rule_name"]
|
||||
ruleNamesSet := make(map[string]struct{}, len(ruleNames))
|
||||
for _, rn := range ruleNames {
|
||||
ruleNamesSet[rn] = struct{}{}
|
||||
}
|
||||
|
||||
// Group rules together by Namespace and Rule Group. Rules are also grouped by Org ID,
|
||||
// but in this API all rules belong to the same organization.
|
||||
// but in this API all rules belong to the same organization. Also filter by rule name if
|
||||
// it was provided as a query param.
|
||||
groupedRules := make(map[ngmodels.AlertRuleGroupKey][]*ngmodels.AlertRule)
|
||||
for _, rule := range ruleList {
|
||||
if len(ruleNamesSet) > 0 {
|
||||
if _, exists := ruleNamesSet[rule.Title]; !exists {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
groupKey := rule.GetGroupKey()
|
||||
ruleGroup := groupedRules[groupKey]
|
||||
ruleGroup = append(ruleGroup, rule)
|
||||
@ -343,6 +369,7 @@ func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ruleGroup, totals := toRuleGroup(log, manager, groupKey, folder, rules, limitAlertsPerRule, withStatesFast, matchers, labelOptions)
|
||||
ruleGroup.Totals = totals
|
||||
for k, v := range totals {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -527,6 +528,138 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("test folder, group and rule name query params", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
fakeAIM := NewFakeAlertInstanceManager(t)
|
||||
|
||||
rulesInGroup1 := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
|
||||
RuleGroup: "rule-group-1",
|
||||
NamespaceUID: "folder-1",
|
||||
OrgID: orgID,
|
||||
})).GenerateManyRef(1)
|
||||
|
||||
rulesInGroup2 := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
|
||||
RuleGroup: "rule-group-2",
|
||||
NamespaceUID: "folder-2",
|
||||
OrgID: orgID,
|
||||
})).GenerateManyRef(2)
|
||||
|
||||
rulesInGroup3 := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
|
||||
RuleGroup: "rule-group-3",
|
||||
NamespaceUID: "folder-1",
|
||||
OrgID: orgID,
|
||||
})).GenerateManyRef(3)
|
||||
|
||||
ruleStore.PutRule(context.Background(), rulesInGroup1...)
|
||||
ruleStore.PutRule(context.Background(), rulesInGroup2...)
|
||||
ruleStore.PutRule(context.Background(), rulesInGroup3...)
|
||||
|
||||
api := PrometheusSrv{
|
||||
log: log.NewNopLogger(),
|
||||
manager: fakeAIM,
|
||||
store: ruleStore,
|
||||
authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
|
||||
}
|
||||
|
||||
permissions := createPermissionsForRules(slices.Concat(rulesInGroup1, rulesInGroup2, rulesInGroup3), orgID)
|
||||
user := &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
Permissions: permissions,
|
||||
}
|
||||
c := &contextmodel.ReqContext{
|
||||
SignedInUser: user,
|
||||
}
|
||||
t.Run("should only return rule groups under given folder_uid", func(t *testing.T) {
|
||||
r, err := http.NewRequest("GET", "/api/v1/rules?folder_uid=folder-1", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.Context = &web.Context{Req: r}
|
||||
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, resp.Status())
|
||||
result := &apimodels.RuleResponse{}
|
||||
require.NoError(t, json.Unmarshal(resp.Body(), result))
|
||||
|
||||
require.Len(t, result.Data.RuleGroups, 2)
|
||||
require.Equal(t, "rule-group-1", result.Data.RuleGroups[0].Name)
|
||||
require.Equal(t, "rule-group-3", result.Data.RuleGroups[1].Name)
|
||||
})
|
||||
|
||||
t.Run("should only return rule groups under given rule_group list", func(t *testing.T) {
|
||||
r, err := http.NewRequest("GET", "/api/v1/rules?rule_group=rule-group-1&rule_group=rule-group-2", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.Context = &web.Context{Req: r}
|
||||
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, resp.Status())
|
||||
result := &apimodels.RuleResponse{}
|
||||
require.NoError(t, json.Unmarshal(resp.Body(), result))
|
||||
|
||||
require.Len(t, result.Data.RuleGroups, 2)
|
||||
require.True(t, true, slices.ContainsFunc(result.Data.RuleGroups, func(rg apimodels.RuleGroup) bool {
|
||||
return rg.Name == "rule-group-1"
|
||||
}))
|
||||
require.True(t, true, slices.ContainsFunc(result.Data.RuleGroups, func(rg apimodels.RuleGroup) bool {
|
||||
return rg.Name == "rule-group-2"
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("should only return rule under given rule_name list", func(t *testing.T) {
|
||||
expectedRuleInGroup2 := rulesInGroup2[0]
|
||||
expectedRuleInGroup3 := rulesInGroup3[0]
|
||||
|
||||
r, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/rules?rule_name=%s&rule_name=%s", expectedRuleInGroup2.Title, expectedRuleInGroup3.Title), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.Context = &web.Context{Req: r}
|
||||
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, resp.Status())
|
||||
result := &apimodels.RuleResponse{}
|
||||
require.NoError(t, json.Unmarshal(resp.Body(), result))
|
||||
|
||||
require.Len(t, result.Data.RuleGroups, 2)
|
||||
require.True(t, true, slices.ContainsFunc(result.Data.RuleGroups, func(rg apimodels.RuleGroup) bool {
|
||||
return rg.Name == "rule-group-2"
|
||||
}))
|
||||
require.True(t, true, slices.ContainsFunc(result.Data.RuleGroups, func(rg apimodels.RuleGroup) bool {
|
||||
return rg.Name == "rule-group-3"
|
||||
}))
|
||||
require.Len(t, result.Data.RuleGroups[0].Rules, 1)
|
||||
require.Len(t, result.Data.RuleGroups[1].Rules, 1)
|
||||
|
||||
if result.Data.RuleGroups[0].Name == "rule-group-2" {
|
||||
require.Equal(t, expectedRuleInGroup2.Title, result.Data.RuleGroups[0].Rules[0].Name)
|
||||
require.Equal(t, expectedRuleInGroup3.Title, result.Data.RuleGroups[1].Rules[0].Name)
|
||||
} else {
|
||||
require.Equal(t, expectedRuleInGroup2.Title, result.Data.RuleGroups[1].Rules[0].Name)
|
||||
require.Equal(t, expectedRuleInGroup3.Title, result.Data.RuleGroups[0].Rules[0].Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should only return rule with given folder_uid, rule_group and rule_name", func(t *testing.T) {
|
||||
expectedRule := rulesInGroup3[2]
|
||||
r, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/rules?folder_uid=folder-1&rule_group=rule-group-3&rule_name=%s", expectedRule.Title), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.Context = &web.Context{Req: r}
|
||||
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, resp.Status())
|
||||
result := &apimodels.RuleResponse{}
|
||||
require.NoError(t, json.Unmarshal(resp.Body(), result))
|
||||
|
||||
require.Len(t, result.Data.RuleGroups, 1)
|
||||
folder, err := api.store.GetNamespaceByUID(context.Background(), "folder-1", orgID, user)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, folder.Fullpath, result.Data.RuleGroups[0].File)
|
||||
require.Equal(t, "rule-group-3", result.Data.RuleGroups[0].Name)
|
||||
require.Len(t, result.Data.RuleGroups[0].Rules, 1)
|
||||
require.Equal(t, expectedRule.Title, result.Data.RuleGroups[0].Rules[0].Name)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
||||
t.Run("should return only rules if the user can query all data sources", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
|
Loading…
Reference in New Issue
Block a user