grafana/pkg/services/ngalert/eval/testing.go
Yuri Tseretyan f6a46744a6
Alerting: Support hysteresis command expression (#75189)
Backend: 

* Update the Grafana Alerting engine to provide feedback to HysteresisCommand. The feedback information is stored in state.Manager as a fingerprint of each state. The fingerprint is persisted to the database. Only fingerprints that belong to Pending and Alerting states are considered as "loaded" and provided back to the command.
   - add ResultFingerprint to state.State. It's different from other fingerprints we store in the state because it is calculated from the result labels.
  -  add rule_fingerprint column to alert_instance
   - update alerting evaluator to accept AlertingResultsReader via context, and update scheduler to provide it.
   - add AlertingResultsFromRuleState that implements the new interface in eval package
   - update getExprRequest to patch the hysteresis command.

* Only one "Recovery Threshold" query is allowed to be used in the alert rule and it must be the Condition.


Frontend:

* Add hysteresis option to Threshold in UI. It's called "Recovery Threshold"
* Add test for getUnloadEvaluatorTypeFromCondition
* Hide hysteresis in panel expressions

* Refactor isInvalid and add test for it
* Remove unnecesary React.memo
* Add tests for updateEvaluatorConditions

---------

Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
2024-01-04 11:47:13 -05:00

90 lines
1.7 KiB
Go

package eval
import (
"fmt"
"math/rand"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
type ResultMutator func(r *Result)
func RandomState() State {
return []State{
Normal,
Alerting,
NoData,
Error,
}[rand.Intn(4)]
}
func GenerateResults(count int, generator func() Result) Results {
var result = make(Results, 0, count)
for i := 0; i < count; i++ {
result = append(result, generator())
}
return result
}
func ResultGen(mutators ...ResultMutator) func() Result {
return func() Result {
state := RandomState()
var err error
if state == Error {
err = fmt.Errorf("result_error")
}
result := Result{
Instance: models.GenerateAlertLabels(rand.Intn(5)+1, "result_"),
State: state,
Error: err,
EvaluatedAt: time.Time{},
EvaluationDuration: time.Duration(rand.Int63n(6)) * time.Second,
EvaluationString: "",
Values: nil,
}
for _, mutator := range mutators {
mutator(&result)
}
return result
}
}
func WithEvaluatedAt(time time.Time) ResultMutator {
return func(r *Result) {
r.EvaluatedAt = time
}
}
func WithState(state State) ResultMutator {
return func(r *Result) {
r.State = state
if state == Error {
r.Error = fmt.Errorf("with_state_error")
}
}
}
func WithError(err error) ResultMutator {
return func(r *Result) {
r.State = Error
r.Error = err
}
}
func WithLabels(labels data.Labels) ResultMutator {
return func(r *Result) {
r.Instance = labels
}
}
type FakeLoadedMetricsReader struct {
fingerprints map[data.Fingerprint]struct{}
}
func (f FakeLoadedMetricsReader) Read() map[data.Fingerprint]struct{} {
return f.fingerprints
}