Alerting: Add search.rule_group to search rules by rule group name (#113680)

Co-authored-by: Konrad Lalik <konradlalik@gmail.com>
This commit is contained in:
Alexander Akhmetov
2025-11-17 20:38:42 +01:00
committed by GitHub
co-authored by Konrad Lalik
parent 9d928a3ac6
commit da5af29218
11 changed files with 169 additions and 20 deletions
@@ -480,6 +480,7 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
receiverName := opts.Query.Get("receiver_name")
title := opts.Query.Get("search.rule_name")
searchRuleGroup := opts.Query.Get("search.rule_group")
var ruleType ngmodels.RuleTypeFilter
switch ngmodels.RuleType(opts.Query.Get("rule_type")) {
@@ -500,13 +501,14 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
byGroupQuery := ngmodels.ListAlertRulesExtendedQuery{
ListAlertRulesQuery: ngmodels.ListAlertRulesQuery{
OrgID: opts.OrgID,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
RuleGroups: ruleGroups,
ReceiverName: receiverName,
SearchTitle: title,
OrgID: opts.OrgID,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
RuleGroups: ruleGroups,
ReceiverName: receiverName,
SearchTitle: title,
SearchRuleGroup: searchRuleGroup,
},
RuleType: ruleType,
Limit: maxGroups,
@@ -638,15 +640,17 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
receiverName := opts.Query.Get("receiver_name")
title := opts.Query.Get("search.rule_name")
searchRuleGroup := opts.Query.Get("search.rule_group")
alertRuleQuery := ngmodels.ListAlertRulesQuery{
OrgID: opts.OrgID,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
RuleGroups: ruleGroups,
ReceiverName: receiverName,
SearchTitle: title,
OrgID: opts.OrgID,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
RuleGroups: ruleGroups,
ReceiverName: receiverName,
SearchTitle: title,
SearchRuleGroup: searchRuleGroup,
}
ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery)
if err != nil {
@@ -981,6 +981,9 @@ type ListAlertRulesQuery struct {
// SearchTitle allows searching for alert rules that contain
// the given string in their title (case insensitive)
SearchTitle string
// SearchRuleGroup allows searching for alert rules in groups that contain
// the given string in their name (case insensitive)
SearchRuleGroup string
HasPrometheusRuleDefinition *bool
}
+12
View File
@@ -865,6 +865,18 @@ func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.Lis
}
}
if query.SearchRuleGroup != "" {
normalizedInput := strings.ToLower(query.SearchRuleGroup)
words := strings.Fields(normalizedInput)
if len(words) > 0 {
pattern := "%" + strings.Join(words, "%") + "%"
// In MySQL rule_group field has case-sensitive collation by default,
// so we need to use LOWER to perform case-insensitive search.
q = q.And("LOWER(rule_group) LIKE ?", pattern)
}
}
if query.HasPrometheusRuleDefinition != nil {
q, err = st.filterWithPrometheusRuleDefinition(*query.HasPrometheusRuleDefinition, q)
if err != nil {
@@ -2141,6 +2141,75 @@ func TestIntegration_ListAlertRules(t *testing.T) {
})
}
})
t.Run("filter by SearchRuleGroup", func(t *testing.T) {
sqlStore := db.InitTestDB(t)
folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b)
rule1 := createRule(t, store, ruleGen.With(models.RuleMuts.WithGroupName("database-alerts")))
rule2 := createRule(t, store, ruleGen.With(models.RuleMuts.WithGroupName("application-alerts")))
rule3 := createRule(t, store, ruleGen.With(models.RuleMuts.WithGroupName("network-alerts")))
rule4 := createRule(t, store, ruleGen.With(models.RuleMuts.WithGroupName("critical-monitoring")))
tc := []struct {
name string
groupSearch string
expectedRules []*models.AlertRule
}{
{
name: "should find rules",
groupSearch: "alerts",
expectedRules: []*models.AlertRule{rule1, rule2, rule3},
},
{
name: "should find rule with partial match",
groupSearch: "mOnItOrInG",
expectedRules: []*models.AlertRule{rule4},
},
{
name: "should return no rules when no match",
groupSearch: "nonexistent",
expectedRules: []*models.AlertRule{},
},
{
name: "should return all rules when empty",
groupSearch: "",
expectedRules: []*models.AlertRule{rule1, rule2, rule3, rule4},
},
{
name: "should not find rules when word order is reversed",
groupSearch: "alerts database",
expectedRules: []*models.AlertRule{},
},
{
name: "should find multiple rules matching sequential words",
groupSearch: "database alert",
expectedRules: []*models.AlertRule{rule1},
},
{
name: "should handle extra whitespace between words",
groupSearch: " network alerts ",
expectedRules: []*models.AlertRule{rule3},
},
{
name: "should handle multiple words with partial matches",
groupSearch: "crit mon",
expectedRules: []*models.AlertRule{rule4},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
query := &models.ListAlertRulesQuery{
OrgID: orgID,
SearchRuleGroup: tt.groupSearch,
}
result, err := store.ListAlertRules(context.Background(), query)
require.NoError(t, err)
require.ElementsMatch(t, tt.expectedRules, result)
})
}
})
}
func TestIntegration_ListAlertRulesPaginated(t *testing.T) {