The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/expr/graph_test.go

293 lines
6.1 KiB

package expr
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
func TestServicebuildPipeLine(t *testing.T) {
var tests = []struct {
name string
req *Request
expectedOrder []string
expectErrContains string
}{
{
name: "simple: a requires b",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "B",
"reducer": "mean",
"type": "reduce"
}`),
},
{
RefID: "B",
DataSource: &datasources.DataSource{
UID: "Fake",
},
TimeRange: AbsoluteTimeRange{},
},
},
},
expectedOrder: []string{"B", "A"},
},
{
name: "cycle will error",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "$B",
"type": "math"
}`),
},
{
RefID: "B",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "$A",
"type": "math"
}`),
},
},
},
expectErrContains: "cyclic components",
},
{
name: "self reference will error",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "$A",
"type": "math"
}`),
},
},
},
expectErrContains: "expression 'A' cannot reference itself. Must be query or another expression",
},
{
name: "missing dependency will error",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "$B",
"type": "math"
}`),
},
},
},
expectErrContains: "find dependent",
},
{
name: "classic can not take input from another expression",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"type": "classic_conditions",
"conditions": [
{
"evaluator": {
"params": [
2,
3
],
"type": "within_range"
},
"operator": {
"type": "or"
},
"query": {
"params": [
"B"
]
},
"reducer": {
"params": [],
"type": "diff"
},
"type": "query"
}
]
}`),
},
{
RefID: "B",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "C",
"reducer": "mean",
"type": "reduce"
}`),
},
{
RefID: "C",
DataSource: &datasources.DataSource{
UID: "Fake",
},
TimeRange: AbsoluteTimeRange{},
},
},
},
expectErrContains: "only data source queries may be inputs to a classic condition",
},
{
name: "classic can not output to another expression",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"type": "classic_conditions",
"conditions": [
{
"evaluator": {
"params": [
2,
3
],
"type": "within_range"
},
"operator": {
"type": "or"
},
"query": {
"params": [
"C"
]
},
"reducer": {
"params": [],
"type": "diff"
},
"type": "query"
}
]
}`),
},
{
RefID: "B",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "A",
"reducer": "mean",
"type": "reduce"
}`),
},
{
RefID: "C",
DataSource: &datasources.DataSource{
UID: "Fake",
},
TimeRange: AbsoluteTimeRange{},
},
},
},
expectErrContains: "classic conditions may not be the input for other expressions",
},
{
name: "Queries with new datasource ref object",
req: &Request{
Queries: []Query{
{
RefID: "A",
DataSource: dataSourceModel(),
JSON: json.RawMessage(`{
"expression": "B",
"reducer": "mean",
"type": "reduce"
}`),
},
{
RefID: "B",
DataSource: &datasources.DataSource{
UID: "Fake",
},
TimeRange: AbsoluteTimeRange{},
},
},
},
expectedOrder: []string{"B", "A"},
},
}
s := Service{
features: featuremgmt.WithFeatures(featuremgmt.FlagExpressionParser),
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nodes, err := s.buildPipeline(tt.req)
if tt.expectErrContains != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectErrContains)
} else {
require.NoError(t, err)
require.Equal(t, tt.expectedOrder, getRefIDOrder(nodes))
}
})
}
}
func TestGetCommandsFromPipeline(t *testing.T) {
pipeline := DataPipeline{
&MLNode{},
&DSNode{},
&CMDNode{
baseNode: baseNode{},
CMDType: 0,
Command: &ReduceCommand{},
},
&CMDNode{
baseNode: baseNode{},
CMDType: 0,
Command: &ReduceCommand{},
},
&CMDNode{
baseNode: baseNode{},
CMDType: 0,
Command: &HysteresisCommand{},
},
}
t.Run("should find command that exists", func(t *testing.T) {
cmds := GetCommandsFromPipeline[*HysteresisCommand](pipeline)
require.Len(t, cmds, 1)
require.Equal(t, pipeline[4].(*CMDNode).Command, cmds[0])
})
t.Run("should find all commands that exist", func(t *testing.T) {
cmds := GetCommandsFromPipeline[*ReduceCommand](pipeline)
require.Len(t, cmds, 2)
})
t.Run("should not find all command that does not exist", func(t *testing.T) {
cmds := GetCommandsFromPipeline[*MathCommand](pipeline)
require.Len(t, cmds, 0)
})
}
func getRefIDOrder(nodes []Node) []string {
ids := make([]string, 0, len(nodes))
for _, n := range nodes {
ids = append(ids, n.RefID())
}
return ids
}