SSE: Support for ML query node (#69963)
* introduce a new node-type ML and implement a command outlier that uses ML plugin as a source of data. * add feature flag mlExpressions that guards the feature
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package ml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshalCommand(t *testing.T) {
|
||||
appURL := "https://grafana.com"
|
||||
|
||||
updateJson := func(cmd string, f func(m map[string]interface{})) func(t *testing.T) []byte {
|
||||
return func(t *testing.T) []byte {
|
||||
var d map[string]interface{}
|
||||
require.NoError(t, json.UnmarshalFromString(cmd, &d))
|
||||
f(d)
|
||||
data, err := json.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
return data
|
||||
}
|
||||
}
|
||||
t.Run("should parse outlier command", func(t *testing.T) {
|
||||
cmd, err := UnmarshalCommand([]byte(outlierQuery), appURL)
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &OutlierCommand{}, cmd)
|
||||
outlier := cmd.(*OutlierCommand)
|
||||
require.Equal(t, 1234*time.Millisecond, outlier.interval)
|
||||
require.Equal(t, appURL, outlier.appURL)
|
||||
require.Equal(t, OutlierCommandConfiguration{
|
||||
DatasourceType: "prometheus",
|
||||
DatasourceUID: "a4ce599c-4c93-44b9-be5b-76385b8c01be",
|
||||
QueryParams: map[string]interface{}{
|
||||
"expr": "go_goroutines{}",
|
||||
"range": true,
|
||||
"refId": "A",
|
||||
},
|
||||
Algorithm: map[string]interface{}{
|
||||
"name": "dbscan",
|
||||
"config": map[string]interface{}{
|
||||
"epsilon": 7.667,
|
||||
},
|
||||
"sensitivity": 0.83,
|
||||
},
|
||||
ResponseType: "binary",
|
||||
}, outlier.config)
|
||||
})
|
||||
t.Run("should fallback to default if 'intervalMs' is not specified", func(t *testing.T) {
|
||||
data := updateJson(outlierQuery, func(m map[string]interface{}) {
|
||||
delete(m, "intervalMs")
|
||||
})(t)
|
||||
cmd, err := UnmarshalCommand(data, appURL)
|
||||
require.NoError(t, err)
|
||||
outlier := cmd.(*OutlierCommand)
|
||||
require.Equal(t, defaultInterval, outlier.interval)
|
||||
})
|
||||
t.Run("fails when", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config func(t *testing.T) []byte
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "field 'type' is missing",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
delete(cmd, "type")
|
||||
}),
|
||||
err: "required field 'type' is not specified or empty. Should be one of [outlier]",
|
||||
},
|
||||
{
|
||||
name: "field 'type' is not known",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cmd["type"] = uuid.NewString()
|
||||
}),
|
||||
err: "unsupported command type. Should be one of [outlier]",
|
||||
},
|
||||
{
|
||||
name: "field 'type' is not string",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cmd["type"] = map[string]interface{}{
|
||||
"data": 1,
|
||||
}
|
||||
}),
|
||||
err: "failed to unmarshall Machine learning command",
|
||||
},
|
||||
{
|
||||
name: "field 'config' is missing",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
delete(cmd, "config")
|
||||
}),
|
||||
err: "required field 'config' is not specified",
|
||||
},
|
||||
{
|
||||
name: "field 'intervalMs' is not number",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cmd["intervalMs"] = "test"
|
||||
}),
|
||||
err: "failed to unmarshall Machine learning command",
|
||||
},
|
||||
{
|
||||
name: "field 'config.datasource_uid' is not specified",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cfg := cmd["config"].(map[string]interface{})
|
||||
delete(cfg, "datasource_uid")
|
||||
}),
|
||||
err: "required field `config.datasource_uid` is not specified",
|
||||
},
|
||||
{
|
||||
name: "field 'config.algorithm' is not specified",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cfg := cmd["config"].(map[string]interface{})
|
||||
delete(cfg, "algorithm")
|
||||
}),
|
||||
err: "required field `config.algorithm` is not specified",
|
||||
},
|
||||
{
|
||||
name: "field 'config.response_type' is not specified",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cfg := cmd["config"].(map[string]interface{})
|
||||
delete(cfg, "response_type")
|
||||
}),
|
||||
err: "required field `config.response_type` is not specified",
|
||||
},
|
||||
{
|
||||
name: "fields 'config.query' and 'config.query_params' are not specified",
|
||||
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
|
||||
cfg := cmd["config"].(map[string]interface{})
|
||||
delete(cfg, "query")
|
||||
delete(cfg, "query_params")
|
||||
}),
|
||||
err: "neither of required fields `config.query_params` or `config.query` are specified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
_, err := UnmarshalCommand(testCase.config(t), appURL)
|
||||
require.ErrorContains(t, err, testCase.err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const outlierQuery = `
|
||||
{
|
||||
"type": "outlier",
|
||||
"intervalMs": 1234,
|
||||
"config": {
|
||||
"datasource_uid": "a4ce599c-4c93-44b9-be5b-76385b8c01be",
|
||||
"datasource_type": "prometheus",
|
||||
"query_params": {
|
||||
"expr": "go_goroutines{}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
"response_type": "binary",
|
||||
"algorithm": {
|
||||
"name": "dbscan",
|
||||
"config": {
|
||||
"epsilon": 7.667
|
||||
},
|
||||
"sensitivity": 0.83
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user