Alerting: Add LogicOr operation option (#89258)

---------

Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
This commit is contained in:
Lu Shueh Chou
2024-08-16 11:02:14 -04:00
committed by GitHub
co-authored by brendamuir
parent 62abaea8f5
commit 126169f3ca
9 changed files with 77 additions and 11 deletions
@@ -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:
@@ -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.
+14 -5
View File
@@ -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
+40
View File
@@ -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 {
+2 -1
View File
@@ -525,7 +525,8 @@
"type": "string",
"enum": [
"and",
"or"
"or",
"logic-or"
],
"x-enum-description": {}
}
+2 -1
View File
@@ -559,7 +559,8 @@
"type": "string",
"enum": [
"and",
"or"
"or",
"logic-or"
],
"x-enum-description": {}
}
+3 -2
View File
@@ -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": {}
@@ -48,6 +48,7 @@ const evalFunctions = [
const evalOperators = [
{ text: 'OR', value: 'or' },
{ text: 'AND', value: 'and' },
{ text: 'LOGIC OR', value: 'logic-or' },
];
const reducerTypes = [
@@ -65,7 +65,7 @@ export const Condition = ({ condition, index, onChange, onRemoveCondition, refId
};
const buttonWidth = css`
width: 60px;
width: 75px;
`;
const isRange =