Skip to content

Commit 126169f

Browse files
Alerting: Add LogicOr operation option (grafana#89258)
--------- Co-authored-by: brendamuir <[email protected]>
1 parent 62abaea commit 126169f

File tree

9 files changed

+77
-11
lines changed

9 files changed

+77
-11
lines changed

docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ The pending period specifies how long the condition must be met before firing, e
5353

5454
You can also set the pending period to zero to skip it and have the alert fire immediately once the condition is met.
5555

56+
## Condition operator
57+
58+
There are several condition operators available.
59+
60+
- **and**: Two conditions before and after must be true for the overall condition to be true.
61+
- **or**: If one of conditions before and after are true, the overall condition is true.
62+
- **logic-or**: If the condition before logic-or is true, the overall condition is immediately true, without evaluating subsequent conditions.
63+
64+
Here are some examples of operators.
65+
66+
- `TRUE and TRUE or FALSE and FALSE` evaluate to `FALSE`, because last two conditions return `FALSE`.
67+
- `TRUE and TRUE logic-or FALSE and FALSE` evaluate to `TRUE`, because the preceding condition returns `TRUE`.
68+
5669
## Evaluation example
5770

5871
Keep in mind:

docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ These functions are available for **Reduce** and **Classic condition** expressio
136136

137137
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.
138138

139-
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.
139+
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.
140140

141141
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.
142142

pkg/expr/classic/classic.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ func (cmd *ConditionsCmd) Execute(ctx context.Context, t time.Time, vars mathexp
7979
// matches contains the list of matches for all conditions
8080
matches := make([]EvalMatch, 0)
8181
for i, cond := range cmd.Conditions {
82+
// Avoid operate subsequent conditions for LogicOr when it is already firing, see #87483
83+
if isFiring && cond.Operator == ConditionOperatorLogicOr {
84+
break
85+
}
86+
8287
isCondFiring, isCondNoData, condMatches, err := cmd.executeCond(ctx, t, cond, vars)
8388
if err != nil {
8489
return mathexp.Results{}, err
@@ -221,7 +226,7 @@ func (cmd *ConditionsCmd) Type() string {
221226
}
222227

223228
func compareWithOperator(b1, b2 bool, operator ConditionOperatorType) bool {
224-
if operator == "or" {
229+
if operator == ConditionOperatorOr || operator == ConditionOperatorLogicOr {
225230
return b1 || b2
226231
} else {
227232
return b1 && b2
@@ -271,8 +276,9 @@ type ConditionEvalJSON struct {
271276
type ConditionOperatorType string
272277

273278
const (
274-
ConditionOperatorAnd ConditionOperatorType = "and"
275-
ConditionOperatorOr ConditionOperatorType = "or"
279+
ConditionOperatorAnd ConditionOperatorType = "and"
280+
ConditionOperatorOr ConditionOperatorType = "or"
281+
ConditionOperatorLogicOr ConditionOperatorType = "logic-or"
276282
)
277283

278284
type ConditionOperatorJSON struct {
@@ -297,8 +303,11 @@ func NewConditionCmd(refID string, ccj []ConditionJSON) (*ConditionsCmd, error)
297303
for i, cj := range ccj {
298304
cond := condition{}
299305

300-
if i > 0 && cj.Operator.Type != "and" && cj.Operator.Type != "or" {
301-
return nil, fmt.Errorf("condition %v operator must be `and` or `or`", i+1)
306+
if i > 0 &&
307+
cj.Operator.Type != ConditionOperatorAnd &&
308+
cj.Operator.Type != ConditionOperatorOr &&
309+
cj.Operator.Type != ConditionOperatorLogicOr {
310+
return nil, fmt.Errorf("condition %v operator must be `and`, `or` or `logic-or`", i+1)
302311
}
303312
cond.Operator = cj.Operator.Type
304313

pkg/expr/classic/classic_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,46 @@ func TestConditionsCmd(t *testing.T) {
595595
v.SetMeta([]EvalMatch{{Value: util.Pointer(5.0)}, {Metric: "NoData"}})
596596
return newResults(v)
597597
},
598+
}, {
599+
name: "LogicOr will stop subsequent logic checks in condition: true AND true LogicOr false AND false",
600+
vars: mathexp.Vars{
601+
"A": mathexp.Results{
602+
Values: []mathexp.Value{
603+
newSeries(util.Pointer(1.0), util.Pointer(5.0)),
604+
},
605+
},
606+
},
607+
cmd: &ConditionsCmd{
608+
Conditions: []condition{
609+
{
610+
InputRefID: "A",
611+
Reducer: reducer("max"),
612+
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 2},
613+
},
614+
{
615+
InputRefID: "A",
616+
Reducer: reducer("min"),
617+
Operator: "and",
618+
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 0},
619+
},
620+
{
621+
InputRefID: "A",
622+
Reducer: reducer("avg"),
623+
Operator: "logic-or",
624+
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 6},
625+
},
626+
{
627+
InputRefID: "A",
628+
Reducer: reducer("last"),
629+
Operator: "and",
630+
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 6},
631+
},
632+
}},
633+
expected: func() mathexp.Results {
634+
v := newNumber(util.Pointer(1.0))
635+
v.SetMeta([]EvalMatch{{Value: util.Pointer(5.0)}, {Value: util.Pointer(1.0)}})
636+
return newResults(v)
637+
},
598638
}}
599639

600640
for _, tt := range tests {

pkg/expr/query.panel.schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,8 @@
525525
"type": "string",
526526
"enum": [
527527
"and",
528-
"or"
528+
"or",
529+
"logic-or"
529530
],
530531
"x-enum-description": {}
531532
}

pkg/expr/query.request.schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,8 @@
559559
"type": "string",
560560
"enum": [
561561
"and",
562-
"or"
562+
"or",
563+
"logic-or"
563564
],
564565
"x-enum-description": {}
565566
}

pkg/expr/query.types.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@
228228
{
229229
"metadata": {
230230
"name": "classic_conditions",
231-
"resourceVersion": "1709915973363",
231+
"resourceVersion": "1723675389127",
232232
"creationTimestamp": "2024-02-21T22:09:26Z"
233233
},
234234
"spec": {
@@ -273,7 +273,8 @@
273273
"type": {
274274
"enum": [
275275
"and",
276-
"or"
276+
"or",
277+
"logic-or"
277278
],
278279
"type": "string",
279280
"x-enum-description": {}

public/app/features/alerting/state/alertDef.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const evalFunctions = [
4848
const evalOperators = [
4949
{ text: 'OR', value: 'or' },
5050
{ text: 'AND', value: 'and' },
51+
{ text: 'LOGIC OR', value: 'logic-or' },
5152
];
5253

5354
const reducerTypes = [

public/app/features/expressions/components/Condition.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const Condition = ({ condition, index, onChange, onRemoveCondition, refId
6565
};
6666

6767
const buttonWidth = css`
68-
width: 60px;
68+
width: 75px;
6969
`;
7070

7171
const isRange =

0 commit comments

Comments
 (0)