Alerting: Fix receiver_name and has_prometheus_definition filters with compact=true (#115582)
This commit is contained in:
@@ -2169,6 +2169,57 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("compact mode with receiver_name filter returns only matching rules", func(t *testing.T) {
|
||||
fakeStore, _, api := setupAPI(t)
|
||||
|
||||
ruleA := gen.With(
|
||||
gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
|
||||
NamespaceUID: "folder-1",
|
||||
RuleGroup: "group-1",
|
||||
OrgID: orgID,
|
||||
}),
|
||||
gen.WithNotificationSettings(
|
||||
ngmodels.NotificationSettings{
|
||||
Receiver: "receiver-a",
|
||||
GroupBy: []string{"alertname"},
|
||||
},
|
||||
),
|
||||
).GenerateRef()
|
||||
fakeStore.PutRule(context.Background(), ruleA)
|
||||
|
||||
ruleB := gen.With(
|
||||
gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
|
||||
NamespaceUID: "folder-2",
|
||||
RuleGroup: "group-2",
|
||||
OrgID: orgID,
|
||||
}),
|
||||
gen.WithNotificationSettings(
|
||||
ngmodels.NotificationSettings{
|
||||
Receiver: "receiver-b",
|
||||
GroupBy: []string{"alertname"},
|
||||
},
|
||||
),
|
||||
).GenerateRef()
|
||||
fakeStore.PutRule(context.Background(), ruleB)
|
||||
r, err := http.NewRequest("GET", "/api/v1/rules?compact=true&receiver_name=receiver-a", nil)
|
||||
require.NoError(t, err)
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, resp.Status())
|
||||
var res apimodels.RuleResponse
|
||||
require.NoError(t, json.Unmarshal(resp.Body(), &res))
|
||||
|
||||
require.Len(t, res.Data.RuleGroups, 1)
|
||||
require.Equal(t, "group-1", res.Data.RuleGroups[0].Name)
|
||||
require.Empty(t, res.Data.RuleGroups[0].Rules[0].Query, "Query should be empty in compact mode")
|
||||
})
|
||||
|
||||
t.Run("provenance as expected", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, api, provStore := setupAPIFull(t)
|
||||
// Rule without provenance
|
||||
|
||||
@@ -642,6 +642,23 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
opts := AlertRuleConvertOptions{}
|
||||
if query.Compact {
|
||||
opts.ExcludeAlertQueries = true
|
||||
opts.ExcludeNotificationSettings = true
|
||||
opts.ExcludeMetadata = true
|
||||
|
||||
if query.ReceiverName != "" || query.TimeIntervalName != "" {
|
||||
// Need NotificationSettings for these filters
|
||||
opts.ExcludeNotificationSettings = false
|
||||
}
|
||||
|
||||
if query.HasPrometheusRuleDefinition != nil {
|
||||
// Need Metadata for this filter
|
||||
opts.ExcludeMetadata = false
|
||||
}
|
||||
}
|
||||
|
||||
// Process rules and implement per-group pagination
|
||||
var groupsFetched int64
|
||||
var rulesFetched int64
|
||||
@@ -653,12 +670,7 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis
|
||||
continue
|
||||
}
|
||||
|
||||
var converted ngmodels.AlertRule
|
||||
if query.Compact {
|
||||
converted, err = alertRuleToModelsAlertRuleCompact(*rule, st.Logger)
|
||||
} else {
|
||||
converted, err = alertRuleToModelsAlertRule(*rule, st.Logger)
|
||||
}
|
||||
converted, err := convertAlertRuleToModel(*rule, st.Logger, opts)
|
||||
|
||||
if err != nil {
|
||||
st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "ListAlertRulesByGroup", "error", err)
|
||||
|
||||
@@ -15,22 +15,23 @@ type compactQuery struct {
|
||||
DatasourceUID string `json:"datasourceUid"`
|
||||
}
|
||||
|
||||
func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) {
|
||||
return convertAlertRuleToModel(ar, l, false)
|
||||
// AlertRuleConvertOptions controls which fields to parse during conversion from alertRule to models.AlertRule.
|
||||
// By default all fields are included. Set Exclude* to true to skip parsing expensive fields.
|
||||
type AlertRuleConvertOptions struct {
|
||||
ExcludeAlertQueries bool // Only parse datasource UIDs from queries
|
||||
ExcludeNotificationSettings bool
|
||||
ExcludeMetadata bool
|
||||
}
|
||||
|
||||
// alertRuleToModelsAlertRuleCompact transforms an alertRule to a models.AlertRule
|
||||
// ignoring alert queries (except for data source UIDs), notification settings, and metadata.
|
||||
func alertRuleToModelsAlertRuleCompact(ar alertRule, l log.Logger) (models.AlertRule, error) {
|
||||
return convertAlertRuleToModel(ar, l, true)
|
||||
func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) {
|
||||
return convertAlertRuleToModel(ar, l, AlertRuleConvertOptions{})
|
||||
}
|
||||
|
||||
// convertAlertRuleToModel creates a models.AlertRule from an alertRule.
|
||||
// When 'compact' is set to 'true', it skips parsing the alert queries (except for the data source UID), notification
|
||||
// settings, and metadata, thus reducing the number of JSON serializations needed.
|
||||
func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.AlertRule, error) {
|
||||
// opts.Exclude* fields control which expensive fields to skip parsing, reducing JSON serializations.
|
||||
func convertAlertRuleToModel(ar alertRule, l log.Logger, opts AlertRuleConvertOptions) (models.AlertRule, error) {
|
||||
var data []models.AlertQuery
|
||||
if compact {
|
||||
if opts.ExcludeAlertQueries {
|
||||
var cqs []compactQuery
|
||||
if err := json.Unmarshal([]byte(ar.Data), &cqs); err != nil {
|
||||
return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err)
|
||||
@@ -118,7 +119,7 @@ func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.A
|
||||
}
|
||||
}
|
||||
|
||||
if !compact && ar.NotificationSettings != "" {
|
||||
if !opts.ExcludeNotificationSettings && ar.NotificationSettings != "" {
|
||||
ns, err := parseNotificationSettings(ar.NotificationSettings)
|
||||
if err != nil {
|
||||
return models.AlertRule{}, fmt.Errorf("failed to parse notification settings: %w", err)
|
||||
@@ -126,7 +127,7 @@ func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.A
|
||||
result.NotificationSettings = ns
|
||||
}
|
||||
|
||||
if !compact && ar.Metadata != "" {
|
||||
if !opts.ExcludeMetadata && ar.Metadata != "" {
|
||||
err = json.Unmarshal([]byte(ar.Metadata), &result.Metadata)
|
||||
if err != nil {
|
||||
return models.AlertRule{}, fmt.Errorf("failed to metadata: %w", err)
|
||||
|
||||
@@ -84,7 +84,11 @@ func TestAlertRuleToModelsAlertRuleCompact(t *testing.T) {
|
||||
Metadata: `{"editor_settings":{"simplified_query_and_expressions_section":true}}`,
|
||||
}
|
||||
|
||||
compactResult, err := alertRuleToModelsAlertRuleCompact(rule, &logtest.Fake{})
|
||||
compactResult, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{
|
||||
ExcludeAlertQueries: true,
|
||||
ExcludeNotificationSettings: true,
|
||||
ExcludeMetadata: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have datasource UIDs.
|
||||
@@ -142,6 +146,82 @@ func TestAlertRuleToModelsAlertRuleCompact(t *testing.T) {
|
||||
// Should have metadata (metadata is parsed from JSON to struct).
|
||||
require.NotEqual(t, ngmodels.AlertRuleMetadata{}, fullResult.Metadata)
|
||||
})
|
||||
|
||||
t.Run("compact mode with notification settings included for filtering", func(t *testing.T) {
|
||||
rule := alertRule{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
UID: "test-uid",
|
||||
Title: "Test Rule",
|
||||
Condition: "A",
|
||||
Data: `[{"datasourceUid":"ds1","refId":"A","queryType":"test","model":{"expr":"up"}}]`,
|
||||
IntervalSeconds: 60,
|
||||
Version: 1,
|
||||
NamespaceUID: "ns-uid",
|
||||
RuleGroup: "test-group",
|
||||
NoDataState: "NoData",
|
||||
ExecErrState: "Error",
|
||||
NotificationSettings: `[{"receiver":"test-receiver"}]`,
|
||||
Metadata: `{"editor_settings":{"simplified_query_and_expressions_section":true}}`,
|
||||
}
|
||||
|
||||
result, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{
|
||||
ExcludeAlertQueries: true,
|
||||
ExcludeNotificationSettings: false,
|
||||
ExcludeMetadata: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have compact query data (only datasource UIDs).
|
||||
require.Len(t, result.Data, 1)
|
||||
require.Equal(t, "ds1", result.Data[0].DatasourceUID)
|
||||
require.Empty(t, result.Data[0].RefID)
|
||||
|
||||
// Should have notification settings for filtering.
|
||||
require.Len(t, result.NotificationSettings, 1)
|
||||
require.Equal(t, "test-receiver", result.NotificationSettings[0].Receiver)
|
||||
|
||||
// Should not have metadata.
|
||||
require.Equal(t, ngmodels.AlertRuleMetadata{}, result.Metadata)
|
||||
})
|
||||
|
||||
t.Run("compact mode with metadata included for filtering", func(t *testing.T) {
|
||||
rule := alertRule{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
UID: "test-uid",
|
||||
Title: "Test Rule",
|
||||
Condition: "A",
|
||||
Data: `[{"datasourceUid":"ds1","refId":"A","queryType":"test","model":{"expr":"up"}}]`,
|
||||
IntervalSeconds: 60,
|
||||
Version: 1,
|
||||
NamespaceUID: "ns-uid",
|
||||
RuleGroup: "test-group",
|
||||
NoDataState: "NoData",
|
||||
ExecErrState: "Error",
|
||||
NotificationSettings: `[{"receiver":"test-receiver"}]`,
|
||||
Metadata: `{"prometheus_style_rule":{"original_rule_definition":"alert: TestAlert\n expr: rate(metric[5m]) > 1"}}`,
|
||||
}
|
||||
|
||||
result, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{
|
||||
ExcludeAlertQueries: true,
|
||||
ExcludeNotificationSettings: true,
|
||||
ExcludeMetadata: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have compact query data (only datasource UIDs).
|
||||
require.Len(t, result.Data, 1)
|
||||
require.Equal(t, "ds1", result.Data[0].DatasourceUID)
|
||||
require.Empty(t, result.Data[0].RefID)
|
||||
|
||||
// Should not have notification settings.
|
||||
require.Empty(t, result.NotificationSettings)
|
||||
|
||||
// Should have metadata for filtering.
|
||||
require.NotEqual(t, ngmodels.AlertRuleMetadata{}, result.Metadata)
|
||||
require.True(t, result.HasPrometheusRuleDefinition())
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertRuleVersionToAlertRule(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user