From 18bbbaf53e7e84a8fc4aeff59e6f4139d6da96c9 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 20 Apr 2021 11:17:06 -0700 Subject: [PATCH] TestData: support boolean and strings in CSV metric response (#33099) --- pkg/tsdb/testdatasource/scenarios.go | 73 +++++++++++++++++------ pkg/tsdb/testdatasource/scenarios_test.go | 29 +++++++++ 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/pkg/tsdb/testdatasource/scenarios.go b/pkg/tsdb/testdatasource/scenarios.go index aabcae8dabe..46f76d21f36 100644 --- a/pkg/tsdb/testdatasource/scenarios.go +++ b/pkg/tsdb/testdatasource/scenarios.go @@ -328,6 +328,47 @@ func (p *testDataPlugin) handleManualEntryScenario(ctx context.Context, req *bac return resp, nil } +func csvToFieldValues(stringInput string) (*data.Field, error) { + parts := strings.Split(strings.ReplaceAll(stringInput, " ", ""), ",") + if len(parts) < 1 { + return nil, fmt.Errorf("csv must have at least one value") + } + + first := parts[0] + if first == "T" || first == "F" { + field := data.NewFieldFromFieldType(data.FieldTypeNullableBool, len(parts)) + for idx, strVal := range parts { + if strVal == "null" || strVal == "" { + continue + } + field.SetConcrete(idx, strVal == "T") + } + return field, nil + } + + // If we can not parse the first value as a number, assume strings + _, err := strconv.ParseFloat(first, 64) + if err != nil { + field := data.NewFieldFromFieldType(data.FieldTypeNullableString, len(parts)) + for idx, strVal := range parts { + if strVal == "null" || strVal == "" { + continue + } + field.SetConcrete(idx, strVal) + } + return field, nil + } + + // Set any valid numbers + field := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(parts)) + for idx, strVal := range parts { + if val, err := strconv.ParseFloat(strVal, 64); err == nil { + field.SetConcrete(idx, val) + } + } + return field, nil +} + func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() @@ -338,38 +379,32 @@ func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req } stringInput := model.Get("stringInput").MustString() - stringInput = strings.ReplaceAll(stringInput, " ", "") - var values []*float64 - for _, strVal := range strings.Split(stringInput, ",") { - if strVal == "null" { - values = append(values, nil) - } - if val, err := strconv.ParseFloat(strVal, 64); err == nil { - values = append(values, &val) - } + valueField, err := csvToFieldValues(stringInput) + if err != nil { + return nil, err } + valueField.Name = frameNameForQuery(q, model, 0) - if len(values) == 0 { - return resp, nil - } + timeField := data.NewFieldFromFieldType(data.FieldTypeTime, valueField.Len()) + timeField.Name = "time" - frame := data.NewFrame("", - data.NewField("time", nil, []*time.Time{}), - data.NewField(frameNameForQuery(q, model, 0), nil, []*float64{})) startTime := q.TimeRange.From.UnixNano() / int64(time.Millisecond) endTime := q.TimeRange.To.UnixNano() / int64(time.Millisecond) + count := valueField.Len() var step int64 = 0 - if len(values) > 1 { - step = (endTime - startTime) / int64(len(values)-1) + if count > 1 { + step = (endTime - startTime) / int64(count-1) } - for _, val := range values { + for i := 0; i < count; i++ { t := time.Unix(startTime/int64(1e+3), (startTime%int64(1e+3))*int64(1e+6)) - frame.AppendRow(&t, val) + timeField.Set(i, t) startTime += step } + frame := data.NewFrame("", timeField, valueField) + respD := resp.Responses[q.RefID] respD.Frames = append(respD.Frames, frame) resp.Responses[q.RefID] = respD diff --git a/pkg/tsdb/testdatasource/scenarios_test.go b/pkg/tsdb/testdatasource/scenarios_test.go index 4c1a821d89b..680cde2e5b2 100644 --- a/pkg/tsdb/testdatasource/scenarios_test.go +++ b/pkg/tsdb/testdatasource/scenarios_test.go @@ -263,3 +263,32 @@ func TestParseLabels(t *testing.T) { assert.Equal(t, expectedTags, parseLabels(model), fmt.Sprintf("Actual tags in test case %d doesn't match expected tags", i+1)) } } + +func TestReadCSV(t *testing.T) { + fBool, err := csvToFieldValues("T, F,F,T ,") + require.NoError(t, err) + + fNum, err := csvToFieldValues("1,2,,4,5") + require.NoError(t, err) + + fStr, err := csvToFieldValues("a,b,,,c") + require.NoError(t, err) + + frame := data.NewFrame("", fBool, fNum, fStr) + out, err := data.FrameToJSON(frame, true, true) + require.NoError(t, err) + + // require.Equal(t, "", string(out)) + + require.JSONEq(t, `{"schema":{ + "fields":[ + {"type":"bool","typeInfo":{"frame":"bool","nullable":true}}, + {"type":"number","typeInfo":{"frame":"float64","nullable":true}}, + {"type":"string","typeInfo":{"frame":"string","nullable":true}} + ]},"data":{ + "values":[ + [true,false,false,true,null], + [1,2,null,4,5], + ["a","b",null,null,"c"] + ]}}`, string(out)) +}