mirror of https://github.com/grafana/grafana
Alerting: Usability adjustments to Loki representation of state history values (#62643)
* Extract label merge, add test file * Extract error/NoData to first class fields, remove a layer from values * Include dashUID and panelID as line-level fields * Drop unnecessary object receiver * Add tests for stream building * Drop NoData field from log linespull/61316/head
parent
5795553353
commit
9fa28c11c5
@ -0,0 +1,175 @@ |
||||
package historian |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"sort" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/state" |
||||
history_model "github.com/grafana/grafana/pkg/services/ngalert/state/historian/model" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestRemoteLokiBackend(t *testing.T) { |
||||
t.Run("statesToStreams", func(t *testing.T) { |
||||
t.Run("skips non-transitory states", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{State: eval.Normal}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
require.Empty(t, res) |
||||
}) |
||||
|
||||
t.Run("maps evaluation errors", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{State: eval.Error, Error: fmt.Errorf("oh no")}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
entry := requireSingleEntry(t, res) |
||||
require.Contains(t, entry.Error, "oh no") |
||||
}) |
||||
|
||||
t.Run("maps NoData results", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{State: eval.NoData}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
_ = requireSingleEntry(t, res) |
||||
}) |
||||
|
||||
t.Run("produces expected stream identifier", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{ |
||||
State: eval.Alerting, |
||||
Labels: data.Labels{"a": "b"}, |
||||
}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
require.Len(t, res, 1) |
||||
exp := map[string]string{ |
||||
"folderUID": rule.NamespaceUID, |
||||
"group": rule.Group, |
||||
"orgID": fmt.Sprint(rule.OrgID), |
||||
"ruleUID": rule.UID, |
||||
"a": "b", |
||||
} |
||||
require.Equal(t, exp, res[0].Stream) |
||||
}) |
||||
|
||||
t.Run("groups streams based on combined labels", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := []state.StateTransition{ |
||||
{ |
||||
PreviousState: eval.Normal, |
||||
State: &state.State{ |
||||
State: eval.Alerting, |
||||
Labels: data.Labels{"a": "b"}, |
||||
}, |
||||
}, |
||||
{ |
||||
PreviousState: eval.Normal, |
||||
State: &state.State{ |
||||
State: eval.Alerting, |
||||
Labels: data.Labels{"a": "b"}, |
||||
}, |
||||
}, |
||||
{ |
||||
PreviousState: eval.Normal, |
||||
State: &state.State{ |
||||
State: eval.Alerting, |
||||
Labels: data.Labels{"c": "d"}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
require.Len(t, res, 2) |
||||
sort.Slice(res, func(i, j int) bool { return len(res[i].Values) > len(res[j].Values) }) |
||||
require.Contains(t, res[0].Stream, "a") |
||||
require.Len(t, res[0].Values, 2) |
||||
require.Contains(t, res[1].Stream, "c") |
||||
require.Len(t, res[1].Values, 1) |
||||
}) |
||||
|
||||
t.Run("excludes private labels", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{ |
||||
State: eval.Alerting, |
||||
Labels: data.Labels{"__private__": "b"}, |
||||
}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
require.Len(t, res, 1) |
||||
require.NotContains(t, res[0].Stream, "__private__") |
||||
}) |
||||
|
||||
t.Run("serializes values when regular", func(t *testing.T) { |
||||
rule := createTestRule() |
||||
l := log.NewNopLogger() |
||||
states := singleFromNormal(&state.State{ |
||||
State: eval.Alerting, |
||||
Values: map[string]float64{"A": 2.0, "B": 5.5}, |
||||
}) |
||||
|
||||
res := statesToStreams(rule, states, nil, l) |
||||
|
||||
entry := requireSingleEntry(t, res) |
||||
require.NotNil(t, entry.Values) |
||||
require.NotNil(t, entry.Values.Get("A")) |
||||
require.NotNil(t, entry.Values.Get("B")) |
||||
require.InDelta(t, 2.0, entry.Values.Get("A").MustFloat64(), 1e-4) |
||||
require.InDelta(t, 5.5, entry.Values.Get("B").MustFloat64(), 1e-4) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func singleFromNormal(st *state.State) []state.StateTransition { |
||||
return []state.StateTransition{ |
||||
{ |
||||
PreviousState: eval.Normal, |
||||
State: st, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func createTestRule() history_model.RuleMeta { |
||||
return history_model.RuleMeta{ |
||||
OrgID: 1, |
||||
UID: "rule-uid", |
||||
Group: "my-group", |
||||
NamespaceUID: "my-folder", |
||||
DashboardUID: "dash-uid", |
||||
PanelID: 123, |
||||
} |
||||
} |
||||
|
||||
func requireSingleEntry(t *testing.T, res []stream) lokiEntry { |
||||
require.Len(t, res, 1) |
||||
require.Len(t, res[0].Values, 1) |
||||
return requireEntry(t, res[0].Values[0]) |
||||
} |
||||
|
||||
func requireEntry(t *testing.T, row row) lokiEntry { |
||||
t.Helper() |
||||
|
||||
var entry lokiEntry |
||||
err := json.Unmarshal([]byte(row.Val), &entry) |
||||
require.NoError(t, err) |
||||
return entry |
||||
} |
Loading…
Reference in new issue