mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
)
|
||||
|
||||
@@ -35,6 +36,7 @@ type backtestingEvaluator interface {
|
||||
|
||||
type stateManager interface {
|
||||
ProcessEvalResults(ctx context.Context, evaluatedAt time.Time, alertRule *models.AlertRule, results eval.Results, extraLabels data.Labels) []state.StateTransition
|
||||
schedule.RuleStateProvider
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
@@ -74,13 +76,16 @@ func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models
|
||||
}
|
||||
length := int(to.Sub(from).Seconds()) / int(rule.IntervalSeconds)
|
||||
|
||||
evaluator, err := backtestingEvaluatorFactory(ruleCtx, e.evalFactory, user, rule.GetEvalCondition())
|
||||
stateManager := e.createStateManager()
|
||||
|
||||
evaluator, err := backtestingEvaluatorFactory(ruleCtx, e.evalFactory, user, rule.GetEvalCondition(), &schedule.AlertingResultsFromRuleState{
|
||||
Manager: stateManager,
|
||||
Rule: rule,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Join(ErrInvalidInputData, err)
|
||||
}
|
||||
|
||||
stateManager := e.createStateManager()
|
||||
|
||||
logger.Info("Start testing alert rule", "from", from, "to", to, "interval", rule.IntervalSeconds, "evaluations", length)
|
||||
|
||||
start := time.Now()
|
||||
@@ -126,7 +131,7 @@ func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition) (backtestingEvaluator, error) {
|
||||
func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition, reader eval.AlertingResultsReader) (backtestingEvaluator, error) {
|
||||
for _, q := range condition.Data {
|
||||
if q.DatasourceUID == "__data__" || q.QueryType == "__data__" {
|
||||
if len(condition.Data) != 1 {
|
||||
@@ -152,9 +157,7 @@ func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFact
|
||||
}
|
||||
}
|
||||
|
||||
evaluator, err := evalFactory.Create(eval.EvaluationContext{Ctx: ctx,
|
||||
User: user,
|
||||
}, condition)
|
||||
evaluator, err := evalFactory.Create(eval.NewContextWithPreviousResults(ctx, user, reader), condition)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -145,7 +145,7 @@ func TestNewBacktestingEvaluator(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
e, err := newBacktestingEvaluator(context.Background(), evalFactory, nil, testCase.condition)
|
||||
e, err := newBacktestingEvaluator(context.Background(), evalFactory, nil, testCase.condition, nil)
|
||||
if testCase.error {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@@ -175,7 +175,7 @@ func TestEvaluatorTest(t *testing.T) {
|
||||
}
|
||||
manager := &fakeStateManager{}
|
||||
|
||||
backtestingEvaluatorFactory = func(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition) (backtestingEvaluator, error) {
|
||||
backtestingEvaluatorFactory = func(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition, r eval.AlertingResultsReader) (backtestingEvaluator, error) {
|
||||
return evaluator, nil
|
||||
}
|
||||
|
||||
@@ -386,6 +386,10 @@ func (f *fakeStateManager) ProcessEvalResults(_ context.Context, evaluatedAt tim
|
||||
return f.stateCallback(evaluatedAt)
|
||||
}
|
||||
|
||||
func (f *fakeStateManager) GetStatesForRuleUID(orgID int64, alertRuleUID string) []*state.State {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeBacktestingEvaluator struct {
|
||||
evalCallback func(now time.Time) (eval.Results, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user