mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Don't record annotations for mapped NoData transitions, when NoData is mapped to OK (#77164)
* Exclude mapped nodata transitions when nodata mapped to OK * Fix processEvalResults test * Don't check NoDataState when filtering transition * Add comment to explain purpose of separate function --------- Co-authored-by: William Wernert <william.wernert@grafana.com>
This commit is contained in:
parent
3fc7aa97d6
commit
65ecde6eed
@ -173,7 +173,7 @@ func (h *AnnotationBackend) Query(ctx context.Context, query ngmodels.HistoryQue
|
|||||||
func buildAnnotations(rule history_model.RuleMeta, states []state.StateTransition, logger log.Logger) []annotations.Item {
|
func buildAnnotations(rule history_model.RuleMeta, states []state.StateTransition, logger log.Logger) []annotations.Item {
|
||||||
items := make([]annotations.Item, 0, len(states))
|
items := make([]annotations.Item, 0, len(states))
|
||||||
for _, state := range states {
|
for _, state := range states {
|
||||||
if !shouldRecord(state) {
|
if !shouldRecordAnnotation(state) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.Debug("Alert state changed creating annotation", "newState", state.Formatted(), "oldState", state.PreviousFormatted())
|
logger.Debug("Alert state changed creating annotation", "newState", state.Formatted(), "oldState", state.PreviousFormatted())
|
||||||
|
@ -34,6 +34,24 @@ func shouldRecord(transition state.StateTransition) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldRecordAnnotation returns true if an annotation should be created for a given state transition.
|
||||||
|
// This is stricter than shouldRecord to avoid cluttering panels with state transitions.
|
||||||
|
func shouldRecordAnnotation(t state.StateTransition) bool {
|
||||||
|
if !shouldRecord(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not record transitions between Normal and Normal (NoData)
|
||||||
|
if t.State.State == eval.Normal && t.PreviousState == eval.Normal {
|
||||||
|
if (t.State.StateReason == "" && t.PreviousStateReason == models.StateReasonNoData) ||
|
||||||
|
(t.State.StateReason == models.StateReasonNoData && t.PreviousStateReason == "") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func removePrivateLabels(labels data.Labels) data.Labels {
|
func removePrivateLabels(labels data.Labels) data.Labels {
|
||||||
result := make(data.Labels)
|
result := make(data.Labels)
|
||||||
for k, v := range labels {
|
for k, v := range labels {
|
||||||
|
@ -97,6 +97,58 @@ func TestShouldRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldRecordAnnotation(t *testing.T) {
|
||||||
|
transition := func(from eval.State, fromReason string, to eval.State, toReason string) state.StateTransition {
|
||||||
|
return state.StateTransition{
|
||||||
|
PreviousState: from,
|
||||||
|
PreviousStateReason: fromReason,
|
||||||
|
State: &state.State{State: to, StateReason: toReason},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("transitions between Normal and Normal(NoData) not recorded", func(t *testing.T) {
|
||||||
|
forward := transition(eval.Normal, "", eval.Normal, models.StateReasonNoData)
|
||||||
|
backward := transition(eval.Normal, models.StateReasonNoData, eval.Normal, "")
|
||||||
|
|
||||||
|
require.False(t, shouldRecordAnnotation(forward), "Normal -> Normal(NoData) should be false")
|
||||||
|
require.False(t, shouldRecordAnnotation(backward), "Normal(NoData) -> Normal should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("other Normal transitions involving NoData still recorded", func(t *testing.T) {
|
||||||
|
pauseForward := transition(eval.Normal, models.StateReasonNoData, eval.Normal, models.StateReasonPaused)
|
||||||
|
pauseBackward := transition(eval.Normal, models.StateReasonPaused, eval.Normal, models.StateReasonNoData)
|
||||||
|
errorForward := transition(eval.Normal, models.StateReasonNoData, eval.Normal, models.StateReasonError)
|
||||||
|
errorBackward := transition(eval.Normal, models.StateReasonError, eval.Normal, models.StateReasonNoData)
|
||||||
|
missingSeriesBackward := transition(eval.Normal, models.StateReasonMissingSeries, eval.Normal, models.StateReasonNoData)
|
||||||
|
|
||||||
|
require.True(t, shouldRecordAnnotation(pauseForward), "Normal(NoData) -> Normal(Paused) should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(pauseBackward), "Normal(Paused) -> Normal(NoData) should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(errorForward), "Normal(NoData) -> Normal(Error) should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(errorBackward), "Normal(Error) -> Normal(NoData) should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(missingSeriesBackward), "Normal(MissingSeries) -> Normal(NoData) should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("respects filters in shouldRecord()", func(t *testing.T) {
|
||||||
|
missingSeries := transition(eval.Normal, "", eval.Normal, models.StateReasonMissingSeries)
|
||||||
|
unpause := transition(eval.Normal, models.StateReasonPaused, eval.Normal, "")
|
||||||
|
afterUpdate := transition(eval.Normal, models.StateReasonUpdated, eval.Normal, "")
|
||||||
|
|
||||||
|
require.False(t, shouldRecordAnnotation(missingSeries), "Normal -> Normal(MissingSeries) should be false")
|
||||||
|
require.False(t, shouldRecordAnnotation(unpause), "Normal(Paused) -> Normal should be false")
|
||||||
|
require.False(t, shouldRecordAnnotation(afterUpdate), "Normal(Updated) -> Normal should be false")
|
||||||
|
|
||||||
|
// Smoke test a few basic ones, exhaustive tests for shouldRecord() already exist elsewhere.
|
||||||
|
basicPending := transition(eval.Normal, "", eval.Pending, "")
|
||||||
|
basicAlerting := transition(eval.Pending, "", eval.Alerting, "")
|
||||||
|
basicResolve := transition(eval.Alerting, "", eval.Normal, "")
|
||||||
|
basicError := transition(eval.Normal, "", eval.Error, "")
|
||||||
|
require.True(t, shouldRecordAnnotation(basicPending), "Normal -> Pending should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(basicAlerting), "Pending -> Alerting should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(basicResolve), "Alerting -> Normal should be true")
|
||||||
|
require.True(t, shouldRecordAnnotation(basicError), "Normal -> Error should be true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemovePrivateLabels(t *testing.T) {
|
func TestRemovePrivateLabels(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
|
@ -852,7 +852,7 @@ func TestProcessEvalResults(t *testing.T) {
|
|||||||
newResult(eval.WithState(eval.NoData), eval.WithLabels(labels1)), // TODO fix it because NoData does not have same labels
|
newResult(eval.WithState(eval.NoData), eval.WithLabels(labels1)), // TODO fix it because NoData does not have same labels
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedAnnotations: 1,
|
expectedAnnotations: 0,
|
||||||
expectedStates: []*state.State{
|
expectedStates: []*state.State{
|
||||||
{
|
{
|
||||||
Labels: labels["system + rule + labels1"],
|
Labels: labels["system + rule + labels1"],
|
||||||
|
Loading…
Reference in New Issue
Block a user