mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-28 22:30:43 +01:00
#### Description - New `GET /api/v3/rules` list API for alert rules: filter query DSL, `states` filter, sort, and offset pagination (design discussion: SigNoz/pulse-pod#324). - Based on #12806, which extracts the shared list filter SQL compiler; this PR adds only the rules key-policy resolver (`sqlrulestore/filterquery_resolver.go`) on top of it. - Rule state lives only in the rule manager's memory, so state filtering, total, sort and pagination run in code after the SQL fetch; total always equals what is pageable. - Sorting is deterministic on ties: equal rows break on name then id, always ascending, so pages never overlap or drop rows between requests. - Response rows carry only list-page fields, deliberately excluding `condition`, `annotations` and `notificationSettings`. The envelope also returns the org's distinct label pairs and the reserved filter keys for suggestions. - Also guards previously unlocked reads of the rules map (`ListRuleStates`, `GetRule`, `TriggeredAlerts`). **Filter keys and operators** | Key | Operators | Notes | |---|---|---| | `name`, `created_by`, `updated_by` | `=`, `!=`, `CONTAINS`, `LIKE`, `ILIKE`, `IN` and negations | string search | | `labels.<key>` | string operators plus `EXISTS`, `NOT EXISTS` | missing label evaluates as empty string; keys are case-sensitive | | `severity` | same as `labels.<key>` | alias for `labels.severity` | | `created_at`, `updated_at` | `=`, `!=`, `<`, `<=`, `>`, `>=`, `BETWEEN`, `NOT BETWEEN` | quoted RFC3339 values | | `alert_type` | `=`, `!=`, `IN`, `NOT IN` | enum: `METRIC_BASED_ALERT`, `TRACES_BASED_ALERT`, `LOGS_BASED_ALERT`, `EXCEPTIONS_BASED_ALERT` | | `rule_type` | `=`, `!=`, `IN`, `NOT IN` | enum: `threshold_rule`, `promql_rule`, `anomaly_rule` | - A bare word is free text: a case-insensitive substring match over name, description and labels. - `state` is not a DSL key. It is the repeated `states=` query param: `firing`, `pending`, `recovering`, `inactive`, `nodata`, `disabled`. - An unknown key or `REGEXP` returns a 400. #### Issues closed by this PR Closes SigNoz/pulse-pod#226 #### Additional Information - A missing label evaluates as the empty string for every value operator, one uniform rule instead of the querier's per-operator split ([`AddDefaultExistsFilter`](https://github.com/SigNoz/signoz/blob/e0da06f76d/pkg/types/querybuildertypes/querybuildertypesv5/builder_elements.go#L160)); presence is asked with `EXISTS` / `NOT EXISTS`. - Integration tests (`tests/integration/tests/alerts/06_list_rules_v3.py`) cover filters, states, sorting, pagination, totals and the error contract, run against both sqlite and postgres. - Found while testing: the stock `create_notification_channel` fixture teardown silently fails and leaks channels; follow-up fix needed. --------- Co-authored-by: Naman Verma <naman.verma@signoz.io>
63 lines
1.4 KiB
Go
63 lines
1.4 KiB
Go
package sqlstoretest
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/uptrace/bun/dialect/sqlitedialect"
|
|
)
|
|
|
|
func TestJSONExtractMapValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
column string
|
|
mapField string
|
|
key string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "PlainKey",
|
|
column: "data",
|
|
mapField: "labels",
|
|
key: "team",
|
|
expected: `json_extract("data", '$.labels."team"')`,
|
|
},
|
|
{
|
|
name: "DottedKey_OneMapEntry",
|
|
column: "data",
|
|
mapField: "labels",
|
|
key: "k8s.cluster",
|
|
expected: `json_extract("data", '$.labels."k8s.cluster"')`,
|
|
},
|
|
{
|
|
name: "BackslashInKey_Escaped",
|
|
column: "data",
|
|
mapField: "labels",
|
|
key: `a\b`,
|
|
expected: `json_extract("data", '$.labels."a\\b"')`,
|
|
},
|
|
{
|
|
name: "SingleQuoteInKey_Doubled",
|
|
column: "data",
|
|
mapField: "labels",
|
|
key: "o'brien",
|
|
expected: `json_extract("data", '$.labels."o''brien"')`,
|
|
},
|
|
{
|
|
name: "QualifiedColumn",
|
|
column: "rule.data",
|
|
mapField: "labels",
|
|
key: "severity",
|
|
expected: `json_extract("rule"."data", '$.labels."severity"')`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := newFormatter(sqlitedialect.New())
|
|
got := string(f.JSONExtractMapValue(tt.column, tt.mapField, tt.key))
|
|
assert.Equal(t, tt.expected, got)
|
|
})
|
|
}
|
|
}
|