Alerting: Fix rule state history with annotations backend (#101174)

* add alertUID to annotations API query parameter
* update state history UI to fetch rule by UID

---------

Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
Yuri Tseretyan
2025-02-21 23:08:40 +02:00
committed by GitHub
parent c33e908baf
commit bbeae46105
14 changed files with 97 additions and 24 deletions
@@ -91,20 +91,22 @@ func (r *LokiHistorianStore) Get(ctx context.Context, query annotations.ItemQuer
return make([]*annotations.ItemDTO, 0), nil
}
rule := &ngmodels.AlertRule{}
if query.AlertID != 0 {
var err error
rule, err = r.ruleStore.GetRuleByID(ctx, ngmodels.GetAlertRuleByIDQuery{OrgID: query.OrgID, ID: query.AlertID})
var ruleUID string
if query.AlertUID != "" {
ruleUID = query.AlertUID
} else if query.AlertID != 0 {
rule, err := r.ruleStore.GetRuleByID(ctx, ngmodels.GetAlertRuleByIDQuery{OrgID: query.OrgID, ID: query.AlertID})
if err != nil {
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
return make([]*annotations.ItemDTO, 0), ErrLokiStoreNotFound.Errorf("rule with ID %d does not exist", query.AlertID)
}
return make([]*annotations.ItemDTO, 0), ErrLokiStoreInternal.Errorf("failed to query rule: %w", err)
}
ruleUID = rule.UID
}
// No folders in the filter because it filter by Dashboard UID, and the request is already authorized.
logQL, err := historian.BuildLogQuery(buildHistoryQuery(&query, accessResources.Dashboards, rule.UID), nil, r.client.MaxQuerySize())
logQL, err := historian.BuildLogQuery(buildHistoryQuery(&query, accessResources.Dashboards, ruleUID), nil, r.client.MaxQuerySize())
if err != nil {
grafanaErr := errutil.Error{}
if errors.As(err, &grafanaErr) {
@@ -108,7 +108,34 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
require.Len(t, res, numTransitions)
})
t.Run("should return ErrLokiStoreNotFound if rule is not found", func(t *testing.T) {
t.Run("can query history by alert uid", func(t *testing.T) {
rule := dashboardRules[dashboard1.UID][0]
fakeLokiClient.rangeQueryRes = []historian.Stream{
historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger()),
}
query := annotations.ItemQuery{
OrgID: 1,
AlertUID: rule.UID,
From: start.UnixMilli(),
To: start.Add(time.Second * time.Duration(numTransitions+1)).UnixMilli(),
}
res, err := store.Get(
context.Background(),
query,
&annotation_ac.AccessResources{
Dashboards: map[string]int64{
dashboard1.UID: dashboard1.ID,
},
CanAccessDashAnnotations: true,
},
)
require.NoError(t, err)
require.Len(t, res, numTransitions)
})
t.Run("should return ErrLokiStoreNotFound if rule is not found by ID", func(t *testing.T) {
var rules = slices.Concat(maps.Values(dashboardRules)...)
id := rand.Int63n(1000) // in Postgres ID is integer, so limit range
// make sure id is not known
@@ -137,6 +164,27 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
require.ErrorIs(t, err, ErrLokiStoreNotFound)
})
t.Run("should return empty response if rule is not found by UID", func(t *testing.T) {
query := annotations.ItemQuery{
OrgID: 1,
AlertUID: "not-found-uid",
From: start.UnixMilli(),
To: start.Add(time.Second * time.Duration(numTransitions+1)).UnixMilli(),
}
res, err := store.Get(
context.Background(),
query,
&annotation_ac.AccessResources{
Dashboards: map[string]int64{
dashboard1.UID: dashboard1.ID,
},
CanAccessDashAnnotations: true,
},
)
require.NoError(t, err)
require.Empty(t, res)
})
t.Run("can query history by dashboard id", func(t *testing.T) {
fakeLokiClient.rangeQueryRes = []historian.Stream{
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),