From 126169f3cab39cedcea53951120b1fd0d90518d4 Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Fri, 16 Aug 2024 23:02:14 +0800 Subject: [PATCH] Alerting: Add LogicOr operation option (#89258) --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../alert-rule-evaluation/_index.md | 13 ++++++ .../alert-rules/queries-conditions.md | 2 +- pkg/expr/classic/classic.go | 19 ++++++--- pkg/expr/classic/classic_test.go | 40 +++++++++++++++++++ pkg/expr/query.panel.schema.json | 3 +- pkg/expr/query.request.schema.json | 3 +- pkg/expr/query.types.json | 5 ++- .../app/features/alerting/state/alertDef.ts | 1 + .../expressions/components/Condition.tsx | 2 +- 9 files changed, 77 insertions(+), 11 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md index 5de512dd012..ea83debb9dc 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md @@ -53,6 +53,19 @@ The pending period specifies how long the condition must be met before firing, e You can also set the pending period to zero to skip it and have the alert fire immediately once the condition is met. +## Condition operator + +There are several condition operators available. + +- **and**: Two conditions before and after must be true for the overall condition to be true. +- **or**: If one of conditions before and after are true, the overall condition is true. +- **logic-or**: If the condition before logic-or is true, the overall condition is immediately true, without evaluating subsequent conditions. + +Here are some examples of operators. + +- `TRUE and TRUE or FALSE and FALSE` evaluate to `FALSE`, because last two conditions return `FALSE`. +- `TRUE and TRUE logic-or FALSE and FALSE` evaluate to `TRUE`, because the preceding condition returns `TRUE`. + ## Evaluation example Keep in mind: diff --git a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md index 55efac7be87..9e6ece241e1 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md +++ b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md @@ -136,7 +136,7 @@ These functions are available for **Reduce** and **Classic condition** expressio An alert condition is the query or expression that determines whether the alert fires or not depending on the value it yields. There can be only one condition which determines the triggering of the alert. -After you have defined your queries and/or expressions, choose one of them as the alert rule condition. By default, the last expression added is used as the alert condition. +After you have defined your queries and expressions, choose one of them as the alert rule condition. By default, the last expression added is used as the alert condition. When the queried data satisfies the defined condition, Grafana triggers the associated alert, which can be configured to send notifications through various channels like email, Slack, or PagerDuty. diff --git a/pkg/expr/classic/classic.go b/pkg/expr/classic/classic.go index edc31d47b37..0f42ac45d6f 100644 --- a/pkg/expr/classic/classic.go +++ b/pkg/expr/classic/classic.go @@ -79,6 +79,11 @@ func (cmd *ConditionsCmd) Execute(ctx context.Context, t time.Time, vars mathexp // matches contains the list of matches for all conditions matches := make([]EvalMatch, 0) for i, cond := range cmd.Conditions { + // Avoid operate subsequent conditions for LogicOr when it is already firing, see #87483 + if isFiring && cond.Operator == ConditionOperatorLogicOr { + break + } + isCondFiring, isCondNoData, condMatches, err := cmd.executeCond(ctx, t, cond, vars) if err != nil { return mathexp.Results{}, err @@ -221,7 +226,7 @@ func (cmd *ConditionsCmd) Type() string { } func compareWithOperator(b1, b2 bool, operator ConditionOperatorType) bool { - if operator == "or" { + if operator == ConditionOperatorOr || operator == ConditionOperatorLogicOr { return b1 || b2 } else { return b1 && b2 @@ -271,8 +276,9 @@ type ConditionEvalJSON struct { type ConditionOperatorType string const ( - ConditionOperatorAnd ConditionOperatorType = "and" - ConditionOperatorOr ConditionOperatorType = "or" + ConditionOperatorAnd ConditionOperatorType = "and" + ConditionOperatorOr ConditionOperatorType = "or" + ConditionOperatorLogicOr ConditionOperatorType = "logic-or" ) type ConditionOperatorJSON struct { @@ -297,8 +303,11 @@ func NewConditionCmd(refID string, ccj []ConditionJSON) (*ConditionsCmd, error) for i, cj := range ccj { cond := condition{} - if i > 0 && cj.Operator.Type != "and" && cj.Operator.Type != "or" { - return nil, fmt.Errorf("condition %v operator must be `and` or `or`", i+1) + if i > 0 && + cj.Operator.Type != ConditionOperatorAnd && + cj.Operator.Type != ConditionOperatorOr && + cj.Operator.Type != ConditionOperatorLogicOr { + return nil, fmt.Errorf("condition %v operator must be `and`, `or` or `logic-or`", i+1) } cond.Operator = cj.Operator.Type diff --git a/pkg/expr/classic/classic_test.go b/pkg/expr/classic/classic_test.go index 200beb0de80..14db0059f0f 100644 --- a/pkg/expr/classic/classic_test.go +++ b/pkg/expr/classic/classic_test.go @@ -595,6 +595,46 @@ func TestConditionsCmd(t *testing.T) { v.SetMeta([]EvalMatch{{Value: util.Pointer(5.0)}, {Metric: "NoData"}}) return newResults(v) }, + }, { + name: "LogicOr will stop subsequent logic checks in condition: true AND true LogicOr false AND false", + vars: mathexp.Vars{ + "A": mathexp.Results{ + Values: []mathexp.Value{ + newSeries(util.Pointer(1.0), util.Pointer(5.0)), + }, + }, + }, + cmd: &ConditionsCmd{ + Conditions: []condition{ + { + InputRefID: "A", + Reducer: reducer("max"), + Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 2}, + }, + { + InputRefID: "A", + Reducer: reducer("min"), + Operator: "and", + Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 0}, + }, + { + InputRefID: "A", + Reducer: reducer("avg"), + Operator: "logic-or", + Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 6}, + }, + { + InputRefID: "A", + Reducer: reducer("last"), + Operator: "and", + Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 6}, + }, + }}, + expected: func() mathexp.Results { + v := newNumber(util.Pointer(1.0)) + v.SetMeta([]EvalMatch{{Value: util.Pointer(5.0)}, {Value: util.Pointer(1.0)}}) + return newResults(v) + }, }} for _, tt := range tests { diff --git a/pkg/expr/query.panel.schema.json b/pkg/expr/query.panel.schema.json index 22c997a0f3c..388781100ee 100644 --- a/pkg/expr/query.panel.schema.json +++ b/pkg/expr/query.panel.schema.json @@ -525,7 +525,8 @@ "type": "string", "enum": [ "and", - "or" + "or", + "logic-or" ], "x-enum-description": {} } diff --git a/pkg/expr/query.request.schema.json b/pkg/expr/query.request.schema.json index d0691de1a64..aa08911fc3c 100644 --- a/pkg/expr/query.request.schema.json +++ b/pkg/expr/query.request.schema.json @@ -559,7 +559,8 @@ "type": "string", "enum": [ "and", - "or" + "or", + "logic-or" ], "x-enum-description": {} } diff --git a/pkg/expr/query.types.json b/pkg/expr/query.types.json index 7e5d218010e..092abaa7393 100644 --- a/pkg/expr/query.types.json +++ b/pkg/expr/query.types.json @@ -228,7 +228,7 @@ { "metadata": { "name": "classic_conditions", - "resourceVersion": "1709915973363", + "resourceVersion": "1723675389127", "creationTimestamp": "2024-02-21T22:09:26Z" }, "spec": { @@ -273,7 +273,8 @@ "type": { "enum": [ "and", - "or" + "or", + "logic-or" ], "type": "string", "x-enum-description": {} diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index 348d89f91bc..4fea58c9edc 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -48,6 +48,7 @@ const evalFunctions = [ const evalOperators = [ { text: 'OR', value: 'or' }, { text: 'AND', value: 'and' }, + { text: 'LOGIC OR', value: 'logic-or' }, ]; const reducerTypes = [ diff --git a/public/app/features/expressions/components/Condition.tsx b/public/app/features/expressions/components/Condition.tsx index d5118e1b768..0b5383921de 100644 --- a/public/app/features/expressions/components/Condition.tsx +++ b/public/app/features/expressions/components/Condition.tsx @@ -65,7 +65,7 @@ export const Condition = ({ condition, index, onChange, onRemoveCondition, refId }; const buttonWidth = css` - width: 60px; + width: 75px; `; const isRange =