From eeb84d09c2c10f1ad878675921bd7589f0ce9009 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Thu, 3 Jun 2021 18:06:12 +0300 Subject: [PATCH] SSE: Change math expression to accept any value convertible to float (#34996) * SSE: Change math expression to accept any scalar value * Apply suggestions from code review * Update test * Remove TODO --- go.mod | 2 +- go.sum | 2 + pkg/expr/mathexp/exp_test.go | 17 +++++ pkg/expr/mathexp/type_series.go | 24 +++++- pkg/expr/mathexp/types_test.go | 126 ++++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 354979a78cb..ecea43452ab 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/gosimple/slug v1.9.0 github.com/grafana/grafana-aws-sdk v0.4.0 github.com/grafana/grafana-live-sdk v0.0.6 - github.com/grafana/grafana-plugin-sdk-go v0.102.0 + github.com/grafana/grafana-plugin-sdk-go v0.104.0 github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/hashicorp/go-hclog v0.16.0 diff --git a/go.sum b/go.sum index a7bb084d10a..169db3dde77 100644 --- a/go.sum +++ b/go.sum @@ -922,6 +922,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CF github.com/grafana/grafana-plugin-sdk-go v0.91.0/go.mod h1:Ot3k7nY7P6DXmUsDgKvNB7oG1v7PRyTdmnYVoS554bU= github.com/grafana/grafana-plugin-sdk-go v0.102.0 h1:Pknh7mlOaJvdhPgKHxcimDOSd9h29eSpA34W0/sOF6c= github.com/grafana/grafana-plugin-sdk-go v0.102.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= +github.com/grafana/grafana-plugin-sdk-go v0.104.0 h1:Ij2tPdEasSjCb2MxHaaiylyW4RLVZYyWpApzN/mlTxo= +github.com/grafana/grafana-plugin-sdk-go v0.104.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 h1:qCmofFVwQR9QnsinstVqI1NPLMVl33jNCnOCXEAVn6E= github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103/go.mod h1:GHIsn+EohCChsdu5YouNZewqLeV9L2FNw4DEJU3P9qE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= diff --git a/pkg/expr/mathexp/exp_test.go b/pkg/expr/mathexp/exp_test.go index 3fc57462519..e368545e5c1 100644 --- a/pkg/expr/mathexp/exp_test.go +++ b/pkg/expr/mathexp/exp_test.go @@ -30,10 +30,27 @@ func makeNumber(name string, labels data.Labels, f *float64) Number { return newNumber } +func unixTimePointer(sec, nsec int64) *time.Time { + t := time.Unix(sec, nsec) + return &t +} + func float64Pointer(f float64) *float64 { return &f } +func strPointer(s string) *string { + return &s +} + +func int64Pointer(i int64) *int64 { + return &i +} + +func boolPointer(b bool) *bool { + return &b +} + var aSeries = Vars{ "A": Results{ []Value{ diff --git a/pkg/expr/mathexp/type_series.go b/pkg/expr/mathexp/type_series.go index 5633519ffda..00962141d43 100644 --- a/pkg/expr/mathexp/type_series.go +++ b/pkg/expr/mathexp/type_series.go @@ -18,8 +18,6 @@ const seriesTypeValIdx = 1 // Series has a time.Time and a *float64 fields. type Series struct { Frame *data.Frame - // TODO: - // - Value can be different number types } // SeriesFromFrame validates that the dataframe can be considered a Series type @@ -49,6 +47,26 @@ FIELDS: valueNullable = true valueIdx = i default: + // Handle default case + // try to convert to *float64 + var convertedField *data.Field + for j := 0; j < field.Len(); j++ { + ff, err := field.NullableFloatAt(j) + if err != nil { + break + } + if convertedField == nil { // initialise field + convertedField = data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, field.Len()) + convertedField.Name = field.Name + convertedField.Labels = field.Labels + } + convertedField.Set(j, ff) + } + if convertedField != nil { + frame.Fields[i] = convertedField + valueNullable = true + valueIdx = i + } if valueIdx != -1 && timeIdx != -1 { break FIELDS } @@ -65,7 +83,7 @@ FIELDS: if timeNullable { // make time not nullable if it is in the input timeSlice := make([]time.Time, 0, frame.Fields[timeIdx].Len()) for rowIdx := 0; rowIdx < frame.Fields[timeIdx].Len(); rowIdx++ { - val, ok := frame.At(0, rowIdx).(*time.Time) + val, ok := frame.At(timeIdx, rowIdx).(*time.Time) if !ok { return s, fmt.Errorf("unexpected time type, expected *time.Time but got %T", val) } diff --git a/pkg/expr/mathexp/types_test.go b/pkg/expr/mathexp/types_test.go index 397ccdf7577..d5ec735711b 100644 --- a/pkg/expr/mathexp/types_test.go +++ b/pkg/expr/mathexp/types_test.go @@ -136,6 +136,132 @@ func TestSeriesFromFrame(t *testing.T) { }, }, }, + { + name: "[]*int, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []*int64{int64Pointer(5)}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(5)}), + }, + }, + }, + }, + { + name: "[]int, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []int64{5}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(5)}), + }, + }, + }, + }, + { + name: "[]string, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []string{"5"}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(5)}), + }, + }, + }, + }, + { + name: "[]*string, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []*string{strPointer("5")}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(5)}), + }, + }, + }, + }, + { + name: "[]bool, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []bool{true}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(1)}), + }, + }, + }, + }, + { + name: "[]*bool, []*time frame should convert", + frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}), + data.NewField("value", nil, []*bool{boolPointer(true)}), + }, + }, + errIs: assert.NoError, + Is: assert.Equal, + Series: Series{ + Frame: &data.Frame{ + Name: "test", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Unix(5, 0)}), + data.NewField("value", nil, []*float64{float64Pointer(1)}), + }, + }, + }, + }, { name: "[]*time, []*time frame should error", frame: &data.Frame{