diff --git a/pkg/tsdb/influxdb/response_parser.go b/pkg/tsdb/influxdb/response_parser.go index a498088dcfd..baae4314241 100644 --- a/pkg/tsdb/influxdb/response_parser.go +++ b/pkg/tsdb/influxdb/response_parser.go @@ -91,7 +91,7 @@ func transformRows(rows []Row, query Query) data.Frames { var floatArray []*float64 var stringArray []string var boolArray []bool - valType := typeof(row.Values[0][colIndex]) + valType := typeof(row.Values, colIndex) name := formatFrameName(row, column, query) for _, valuePair := range row.Values { @@ -108,6 +108,8 @@ func transformRows(rows []Row, query Query) data.Frames { } else if valType == "bool" { value := valuePair[colIndex].(bool) boolArray = append(boolArray, value) + } else if valType == "null" { + floatArray = append(floatArray, nil) } } } @@ -125,6 +127,10 @@ func transformRows(rows []Row, query Query) data.Frames { valueField := data.NewField("value", row.Tags, boolArray) valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: name}) frames = append(frames, newDataFrame(name, query.RawQuery, timeField, valueField)) + } else if valType == "null" { + valueField := data.NewField("value", row.Tags, floatArray) + valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: name}) + frames = append(frames, newDataFrame(name, query.RawQuery, timeField, valueField)) } } } @@ -213,8 +219,13 @@ func parseTimestamp(value interface{}) (time.Time, error) { return t, nil } -func typeof(v interface{}) string { - return fmt.Sprintf("%T", v) +func typeof(values [][]interface{}, colIndex int) string { + for _, value := range values { + if value != nil && value[colIndex] != nil { + return fmt.Sprintf("%T", value[colIndex]) + } + } + return "null" } func parseNumber(value interface{}) *float64 { diff --git a/pkg/tsdb/influxdb/response_parser_test.go b/pkg/tsdb/influxdb/response_parser_test.go index e05fc6ebec3..600f62c8797 100644 --- a/pkg/tsdb/influxdb/response_parser_test.go +++ b/pkg/tsdb/influxdb/response_parser_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -729,3 +730,76 @@ func TestInfluxdbResponseParser(t *testing.T) { require.Error(t, err) }) } + +func TestResponseParser_Parse(t *testing.T) { + tests := []struct { + name string + input string + f func(t *testing.T, got *backend.QueryDataResponse) + }{ + { + name: "Influxdb response parser with valid value when null values returned", + input: `{ "results": [ { "series": [ { + "name": "cpu", + "columns": ["time","mean"], + "values": [ + [100,null], + [101,null], + [102,52] + ] + }]}]}`, + f: func(t *testing.T, got *backend.QueryDataResponse) { + newField := data.NewField("value", nil, []*float64{nil, nil, pointer.Float64(52)}) + newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"} + testFrame := data.NewFrame("cpu.mean", + data.NewField("time", nil, + []time.Time{ + time.Date(1970, 1, 1, 0, 1, 40, 0, time.UTC), + time.Date(1970, 1, 1, 0, 1, 41, 0, time.UTC), + time.Date(1970, 1, 1, 0, 1, 42, 0, time.UTC), + }), + newField, + ) + testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} + assert.Equal(t, testFrame, got.Responses["A"].Frames[0]) + }, + }, + { + name: "Influxdb response parser with valid value when all values are null", + input: `{ "results": [ { "series": [ { + "name": "cpu", + "columns": ["time","mean"], + "values": [ + [100,null], + [101,null], + [102,null] + ] + }]}]}`, + f: func(t *testing.T, got *backend.QueryDataResponse) { + newField := data.NewField("value", nil, []*float64{nil, nil, nil}) + newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"} + testFrame := data.NewFrame("cpu.mean", + data.NewField("time", nil, + []time.Time{ + time.Date(1970, 1, 1, 0, 1, 40, 0, time.UTC), + time.Date(1970, 1, 1, 0, 1, 41, 0, time.UTC), + time.Date(1970, 1, 1, 0, 1, 42, 0, time.UTC), + }), + newField, + ) + testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} + assert.Equal(t, testFrame, got.Responses["A"].Frames[0]) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := &ResponseParser{} + got := parser.Parse(prepare(tt.input), addQueryToQueries(Query{})) + require.NotNil(t, got) + if tt.f != nil { + tt.f(t, got) + } + }) + } +}