When querying metric data (non-table data) with SQL Expressions, we need to convert the data to table format. This is alternative format which does not have the same issues with sparse data. There is now a __metric_name__ column and one __value__ column. Also a __display_name__ column if there is DisplayNameFromDS metadata. --------- Co-authored-by: Adam Simpson <adam@adamsimpson.net>
193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
package expr
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestConvertFromFullLongToNumericMulti(t *testing.T) {
|
|
t.Run("SingleRowNoLabels", func(t *testing.T) {
|
|
input := data.NewFrame("",
|
|
data.NewField(SQLMetricFieldName, nil, []string{"cpu"}),
|
|
data.NewField(SQLValueFieldName, nil, []*float64{fp(3.14)}),
|
|
)
|
|
input.Meta = &data.FrameMeta{Type: numericFullLongType}
|
|
|
|
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
|
|
require.NoError(t, err)
|
|
require.Len(t, out, 1)
|
|
|
|
expected := data.NewFrame("",
|
|
data.NewField("cpu", nil, []*float64{fp(3.14)}),
|
|
)
|
|
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
|
|
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run("TwoRowsWithLabelsAndDisplay", func(t *testing.T) {
|
|
input := data.NewFrame("",
|
|
data.NewField(SQLMetricFieldName, nil, []string{"cpu", "cpu"}),
|
|
data.NewField(SQLValueFieldName, nil, []*float64{fp(1.0), fp(2.0)}),
|
|
data.NewField(SQLDisplayFieldName, nil, []*string{sp("CPU A"), sp("CPU A")}),
|
|
data.NewField("host", nil, []*string{sp("a"), sp("a")}),
|
|
)
|
|
input.Meta = &data.FrameMeta{Type: numericFullLongType}
|
|
|
|
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
|
|
require.NoError(t, err)
|
|
require.Len(t, out, 1)
|
|
|
|
expected := data.NewFrame("",
|
|
func() *data.Field {
|
|
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0), fp(2.0)})
|
|
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
|
|
return f
|
|
}(),
|
|
)
|
|
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
|
|
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run("SkipsNullValues", func(t *testing.T) {
|
|
input := data.NewFrame("",
|
|
data.NewField(SQLMetricFieldName, nil, []string{"cpu", "cpu"}),
|
|
data.NewField(SQLValueFieldName, nil, []*float64{fp(1.0), nil}),
|
|
)
|
|
input.Meta = &data.FrameMeta{Type: numericFullLongType}
|
|
|
|
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
|
|
require.NoError(t, err)
|
|
require.Len(t, out, 1)
|
|
|
|
expected := data.NewFrame("",
|
|
data.NewField("cpu", nil, []*float64{fp(1.0)}),
|
|
)
|
|
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
|
|
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestConvertNumericMultiRoundTripToFullLongAndBack(t *testing.T) {
|
|
t.Run("TwoFieldsWithSparseLabels", func(t *testing.T) {
|
|
input := data.Frames{
|
|
data.NewFrame("",
|
|
data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)}),
|
|
),
|
|
data.NewFrame("",
|
|
data.NewField("cpu", data.Labels{"host": "b", "env": "prod"}, []*float64{fp(2.0)}),
|
|
),
|
|
}
|
|
for _, f := range input {
|
|
f.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
}
|
|
|
|
fullLong, err := ConvertToFullLong(input)
|
|
require.NoError(t, err)
|
|
require.Len(t, fullLong, 1)
|
|
|
|
roundTrip, err := ConvertFromFullLongToNumericMulti(fullLong)
|
|
require.NoError(t, err)
|
|
|
|
expected := data.Frames{
|
|
data.NewFrame("",
|
|
data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)}),
|
|
),
|
|
data.NewFrame("",
|
|
data.NewField("cpu", data.Labels{"host": "b", "env": "prod"}, []*float64{fp(2.0)}),
|
|
),
|
|
}
|
|
for _, f := range expected {
|
|
f.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
}
|
|
|
|
sortFramesByMetricDisplayAndLabels(expected)
|
|
sortFramesByMetricDisplayAndLabels(roundTrip)
|
|
|
|
require.Len(t, roundTrip, len(expected))
|
|
for i := range expected {
|
|
if diff := cmp.Diff(expected[i], roundTrip[i], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Mismatch on frame %d (-want +got):\n%s", i, diff)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("PreservesDisplayName", func(t *testing.T) {
|
|
input := data.Frames{
|
|
data.NewFrame("",
|
|
func() *data.Field {
|
|
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)})
|
|
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
|
|
return f
|
|
}(),
|
|
),
|
|
}
|
|
input[0].Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
|
|
fullLong, err := ConvertToFullLong(input)
|
|
require.NoError(t, err)
|
|
require.Len(t, fullLong, 1)
|
|
|
|
roundTrip, err := ConvertFromFullLongToNumericMulti(fullLong)
|
|
require.NoError(t, err)
|
|
|
|
expected := data.Frames{
|
|
data.NewFrame("",
|
|
func() *data.Field {
|
|
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)})
|
|
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
|
|
return f
|
|
}(),
|
|
),
|
|
}
|
|
expected[0].Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
|
|
|
|
sortFramesByMetricDisplayAndLabels(expected)
|
|
sortFramesByMetricDisplayAndLabels(roundTrip)
|
|
|
|
require.Len(t, roundTrip, 1)
|
|
if diff := cmp.Diff(expected[0], roundTrip[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func sortFramesByMetricDisplayAndLabels(frames data.Frames) {
|
|
sort.Slice(frames, func(i, j int) bool {
|
|
fi := frames[i].Fields[0]
|
|
fj := frames[j].Fields[0]
|
|
|
|
// 1. Metric name
|
|
if fi.Name != fj.Name {
|
|
return fi.Name < fj.Name
|
|
}
|
|
|
|
// 2. Display name (if set)
|
|
var di, dj string
|
|
if fi.Config != nil {
|
|
di = fi.Config.DisplayNameFromDS
|
|
}
|
|
if fj.Config != nil {
|
|
dj = fj.Config.DisplayNameFromDS
|
|
}
|
|
if di != dj {
|
|
return di < dj
|
|
}
|
|
|
|
// 3. Labels fingerprint
|
|
return fi.Labels.Fingerprint() < fj.Labels.Fingerprint()
|
|
})
|
|
}
|