mirror of
https://github.com/grafana/grafana.git
synced 2026-08-13 06:34:55 -05:00
SSE: Support hysteresis threshold expression (#70998)
* extend threshold command with second evaluator called `unloadEvaluator` * Introduce a new expression command Hysteresis and update Threshold unmarshaller to create the HysteresisCommand if the second eval * add feature flag `recoveryThreshold` * update unmarshal threshold command to not re-marshall because it breaks frame definition by shuffling the schema and data fields
This commit is contained in:
+93
-35
@@ -2,9 +2,13 @@ package expr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
)
|
||||
|
||||
func TestNewThresholdCommand(t *testing.T) {
|
||||
@@ -82,6 +86,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
|
||||
query string
|
||||
shouldError bool
|
||||
expectedError string
|
||||
assert func(*testing.T, Command)
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
@@ -97,7 +102,13 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
|
||||
}
|
||||
}]
|
||||
}`,
|
||||
shouldError: false,
|
||||
assert: func(t *testing.T, command Command) {
|
||||
require.IsType(t, &ThresholdCommand{}, command)
|
||||
cmd := command.(*ThresholdCommand)
|
||||
require.Equal(t, []string{"A"}, cmd.NeedsVars())
|
||||
require.Equal(t, "gt", cmd.ThresholdFunc)
|
||||
require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "unmarshal with missing conditions should error",
|
||||
@@ -107,17 +118,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
|
||||
"conditions": []
|
||||
}`,
|
||||
shouldError: true,
|
||||
expectedError: "requires exactly one condition",
|
||||
},
|
||||
{
|
||||
description: "unmarshal with missing conditions should error",
|
||||
query: `{
|
||||
"expression" : "A",
|
||||
"type": "threshold",
|
||||
"conditions": []
|
||||
}`,
|
||||
shouldError: true,
|
||||
expectedError: "requires exactly one condition",
|
||||
expectedError: "threshold expression requires exactly one condition",
|
||||
},
|
||||
{
|
||||
description: "unmarshal with unsupported threshold function",
|
||||
@@ -141,37 +142,86 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
|
||||
"type": "threshold",
|
||||
"conditions": []
|
||||
}`,
|
||||
shouldError: true,
|
||||
expectedError: "expected threshold variable to be a string",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
description: "unmarshal as hysteresis command if two evaluators",
|
||||
query: `{
|
||||
"expression": "B",
|
||||
"conditions": [
|
||||
{
|
||||
"evaluator": {
|
||||
"params": [
|
||||
100
|
||||
],
|
||||
"type": "gt"
|
||||
},
|
||||
"unloadEvaluator": {
|
||||
"params": [
|
||||
31
|
||||
],
|
||||
"type": "lt"
|
||||
},
|
||||
"loadedDimensions": {"schema":{"name":"test","meta":{"type":"fingerprints","typeVersion":[1,0]},"fields":[{"name":"fingerprints","type":"number","typeInfo":{"frame":"uint64"}}]},"data":{"values":[[1,2,3,4,5]]}}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
assert: func(t *testing.T, c Command) {
|
||||
require.IsType(t, &HysteresisCommand{}, c)
|
||||
cmd := c.(*HysteresisCommand)
|
||||
require.Equal(t, []string{"B"}, cmd.NeedsVars())
|
||||
require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars())
|
||||
require.Equal(t, "gt", cmd.LoadingThresholdFunc.ThresholdFunc)
|
||||
require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions)
|
||||
require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars())
|
||||
require.Equal(t, "lt", cmd.UnloadingThresholdFunc.ThresholdFunc)
|
||||
require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions)
|
||||
require.True(t, cmd.UnloadingThresholdFunc.Invert)
|
||||
require.NotNil(t, cmd.LoadedDimensions)
|
||||
actual := make([]uint64, 0, len(cmd.LoadedDimensions))
|
||||
for fingerprint := range cmd.LoadedDimensions {
|
||||
actual = append(actual, uint64(fingerprint))
|
||||
}
|
||||
sort.Slice(actual, func(i, j int) bool {
|
||||
return actual[i] < actual[j]
|
||||
})
|
||||
|
||||
require.EqualValues(t, []uint64{1, 2, 3, 4, 5}, actual)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
q := []byte(tc.query)
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
q := []byte(tc.query)
|
||||
var qmap = make(map[string]any)
|
||||
require.NoError(t, json.Unmarshal(q, &qmap))
|
||||
|
||||
var qmap = make(map[string]any)
|
||||
require.NoError(t, json.Unmarshal(q, &qmap))
|
||||
cmd, err := UnmarshalThresholdCommand(&rawNode{
|
||||
RefID: "",
|
||||
Query: qmap,
|
||||
QueryRaw: []byte(tc.query),
|
||||
QueryType: "",
|
||||
DataSource: nil,
|
||||
}, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold))
|
||||
|
||||
cmd, err := UnmarshalThresholdCommand(&rawNode{
|
||||
RefID: "",
|
||||
Query: qmap,
|
||||
QueryType: "",
|
||||
DataSource: nil,
|
||||
if tc.shouldError {
|
||||
require.Nil(t, cmd)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedError)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, cmd)
|
||||
if tc.assert != nil {
|
||||
tc.assert(t, cmd)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if tc.shouldError {
|
||||
require.Nil(t, cmd)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedError)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestThresholdCommandVars(t *testing.T) {
|
||||
cmd, err := NewThresholdCommand("B", "A", "is_above", []float64{})
|
||||
cmd, err := NewThresholdCommand("B", "A", "lt", []float64{1.0})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, cmd.NeedsVars(), []string{"A"})
|
||||
}
|
||||
@@ -219,17 +269,25 @@ func TestCreateMathExpression(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
expr, err := createMathExpression(tc.ref, tc.function, tc.params)
|
||||
expr, err := createMathExpression(tc.ref, tc.function, tc.params, false)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, expr)
|
||||
|
||||
require.Equal(t, expr, tc.expected)
|
||||
require.Equal(t, tc.expected, expr)
|
||||
|
||||
t.Run("inverted", func(t *testing.T) {
|
||||
expr, err := createMathExpression(tc.ref, tc.function, tc.params, true)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, expr)
|
||||
|
||||
require.Equal(t, fmt.Sprintf("!(%s)", tc.expected), expr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("should error if function is unsupported", func(t *testing.T) {
|
||||
expr, err := createMathExpression("A", "foo", []float64{0})
|
||||
expr, err := createMathExpression("A", "foo", []float64{0}, false)
|
||||
require.Equal(t, expr, "")
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), "no such threshold function")
|
||||
|
||||
Reference in New Issue
Block a user