diff --git a/pkg/tsdb/grafanads/grafana.go b/pkg/tsdb/grafanads/grafana.go index 63774d25925..a10258f05cc 100644 --- a/pkg/tsdb/grafanads/grafana.go +++ b/pkg/tsdb/grafanads/grafana.go @@ -165,7 +165,7 @@ func (s *Service) doReadQuery(ctx context.Context, query backend.DataQuery) back func (s *Service) doRandomWalk(query backend.DataQuery) backend.DataResponse { response := backend.DataResponse{} - model := simplejson.New() + model := testdatasource.JSONModel{} response.Frames = data.Frames{testdatasource.RandomWalk(query, model, 0)} return response diff --git a/pkg/tsdb/testdatasource/scenarios.go b/pkg/tsdb/testdatasource/scenarios.go index b6b1bab0f2f..a38694450fa 100644 --- a/pkg/tsdb/testdatasource/scenarios.go +++ b/pkg/tsdb/testdatasource/scenarios.go @@ -13,8 +13,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" - - "github.com/grafana/grafana/pkg/components/simplejson" ) const ( @@ -233,18 +231,63 @@ func (s *Service) registerScenario(scenario *Scenario) { s.queryMux.HandleFunc(scenario.ID, scenario.handler) } +type JSONModel struct { + ScenarioID string `json:"scenarioId"` + SeriesCount int `json:"seriesCount"` + StringInput string `json:"stringInput"` + Lines int64 `json:"lines"` + IncludeLevelColumn bool `json:"includeLevelColumn"` + StartValue float64 `json:"startValue"` + Spread float64 `json:"spread"` + Noise float64 `json:"noise"` + Drop float64 `json:"drop"` + Min *float64 `json:"min,omitempty"` + Max *float64 `json:"max,omitempty"` + Labels string `json:"labels"` + WithNil bool `json:"withNil"` + PulseWave pulseWave `json:"pulseWave"` + Alias string `json:"alias"` + // Cannot specify a type for csvWave since legacy queries + // does not follow the same format as the new ones (and there is no migration). + CSVWave interface{} `json:"csvWave"` +} + +type pulseWave struct { + TimeStep int64 `json:"timeStep"` + OnCount int64 `json:"onCount"` + OffCount int64 `json:"offCount"` + OnValue interface{} `json:"onValue"` + OffValue interface{} `json:"offValue"` +} + +func getModel(j json.RawMessage) (JSONModel, error) { + model := JSONModel{ + // Default values + ScenarioID: string(randomWalkQuery), + SeriesCount: 1, + Lines: 10, + StartValue: rand.Float64() * 100, + Spread: 1, + } + err := json.Unmarshal(j, &model) + if err != nil { + return JSONModel{}, err + } + return model, nil +} + // handleFallbackScenario handles the scenario where queryType is not set and fallbacks to scenarioId. func (s *Service) handleFallbackScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { scenarioQueries := map[string][]backend.DataQuery{} for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { s.logger.Error("Failed to unmarshal query model to JSON", "error", err) continue } - scenarioID := model.Get("scenarioId").MustString(string(randomWalkQuery)) + scenarioID := model.ScenarioID if _, exist := s.scenarios[scenarioID]; exist { if _, ok := scenarioQueries[scenarioID]; !ok { scenarioQueries[scenarioID] = []backend.DataQuery{} @@ -281,11 +324,11 @@ func (s *Service) handleRandomWalkScenario(ctx context.Context, req *backend.Que resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } - seriesCount := model.Get("seriesCount").MustInt(1) + seriesCount := model.SeriesCount for i := 0; i < seriesCount; i++ { respD := resp.Responses[q.RefID] @@ -301,7 +344,7 @@ func (s *Service) handleDatapointsOutsideRangeScenario(ctx context.Context, req resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } @@ -325,13 +368,12 @@ func (s *Service) handleCSVMetricValuesScenario(ctx context.Context, req *backen resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } - stringInput := model.Get("stringInput").MustString() - + stringInput := model.StringInput valueField, err := csvLineToField(stringInput) if err != nil { return nil, err @@ -369,7 +411,7 @@ func (s *Service) handleRandomWalkWithErrorScenario(ctx context.Context, req *ba resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } @@ -387,12 +429,12 @@ func (s *Service) handleRandomWalkSlowScenario(ctx context.Context, req *backend resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } - stringInput := model.Get("stringInput").MustString() + stringInput := model.StringInput parsedInterval, _ := time.ParseDuration(stringInput) time.Sleep(parsedInterval) @@ -408,7 +450,7 @@ func (s *Service) handleRandomWalkTableScenario(ctx context.Context, req *backen resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } @@ -425,7 +467,7 @@ func (s *Service) handlePredictableCSVWaveScenario(ctx context.Context, req *bac resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { return nil, err } @@ -446,7 +488,7 @@ func (s *Service) handlePredictablePulseScenario(ctx context.Context, req *backe resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } @@ -465,12 +507,12 @@ func (s *Service) handlePredictablePulseScenario(ctx context.Context, req *backe func (s *Service) handleServerError500Scenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } - stringInput := model.Get("stringInput").MustString() + stringInput := model.StringInput if stringInput == "" { panic("Test Data Panic!") } @@ -487,7 +529,7 @@ func (s *Service) handleArrowScenario(ctx context.Context, req *backend.QueryDat resp := backend.NewQueryDataResponse() for _, q := range req.Queries { - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { return nil, err } @@ -573,13 +615,13 @@ func (s *Service) handleLogsScenario(ctx context.Context, req *backend.QueryData from := q.TimeRange.From.UnixNano() / int64(time.Millisecond) to := q.TimeRange.To.UnixNano() / int64(time.Millisecond) - model, err := simplejson.NewJson(q.JSON) + model, err := getModel(q.JSON) if err != nil { continue } - lines := model.Get("lines").MustInt64(10) - includeLevelColumn := model.Get("levelColumn").MustBool(false) + lines := model.Lines + includeLevelColumn := model.IncludeLevelColumn logLevelGenerator := newRandomStringProvider([]string{ "emerg", @@ -651,19 +693,27 @@ func (s *Service) handleLogsScenario(ctx context.Context, req *backend.QueryData return resp, nil } -func RandomWalk(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame { +func RandomWalk(query backend.DataQuery, model JSONModel, index int) *data.Frame { rand := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index))) timeWalkerMs := query.TimeRange.From.UnixNano() / int64(time.Millisecond) to := query.TimeRange.To.UnixNano() / int64(time.Millisecond) - startValue := model.Get("startValue").MustFloat64(rand.Float64() * 100) - spread := model.Get("spread").MustFloat64(1) - noise := model.Get("noise").MustFloat64(0) - drop := model.Get("drop").MustFloat64(0) / 100.0 // value is 0-100 + startValue := model.StartValue + spread := model.Spread + noise := model.Noise + drop := model.Drop / 100.0 // value is 0-100 - min, err := model.Get("min").Float64() - hasMin := err == nil - max, err := model.Get("max").Float64() - hasMax := err == nil + min := float64(0) + hasMin := false + if model.Min != nil { + hasMin = true + min = *model.Min + } + max := float64(0) + hasMax := false + if model.Max != nil { + hasMax = true + max = *model.Max + } timeVec := make([]*time.Time, 0) floatVec := make([]*float64, 0) @@ -704,12 +754,12 @@ func RandomWalk(query backend.DataQuery, model *simplejson.Json, index int) *dat ) } -func randomWalkTable(query backend.DataQuery, model *simplejson.Json) *data.Frame { +func randomWalkTable(query backend.DataQuery, model JSONModel) *data.Frame { rand := rand.New(rand.NewSource(time.Now().UnixNano())) timeWalkerMs := query.TimeRange.From.UnixNano() / int64(time.Millisecond) to := query.TimeRange.To.UnixNano() / int64(time.Millisecond) - withNil := model.Get("withNil").MustBool(false) - walker := model.Get("startValue").MustFloat64(rand.Float64() * 100) + withNil := model.WithNil + walker := model.StartValue spread := 2.5 stateField := data.NewFieldFromFieldType(data.FieldTypeEnum, 0) @@ -784,14 +834,13 @@ type pCSVOptions struct { Name string `json:"name"` } -func predictableCSVWave(query backend.DataQuery, model *simplejson.Json) ([]*data.Frame, error) { - rawQueries, err := model.Get("csvWave").ToDB() +func predictableCSVWave(query backend.DataQuery, model JSONModel) ([]*data.Frame, error) { + queries := []pCSVOptions{} + input, err := json.Marshal(model.CSVWave) if err != nil { return nil, err } - - queries := []pCSVOptions{} - err = json.Unmarshal(rawQueries, &queries) + err = json.Unmarshal(input, &queries) if err != nil { return nil, err } @@ -882,7 +931,7 @@ func predictableSeries(timeRange backend.TimeRange, timeStep, length int64, getV }, nil } -func predictablePulse(query backend.DataQuery, model *simplejson.Json) (*data.Frame, error) { +func predictablePulse(query backend.DataQuery, model JSONModel) (*data.Frame, error) { // Process Input var timeStep int64 var onCount int64 @@ -890,26 +939,20 @@ func predictablePulse(query backend.DataQuery, model *simplejson.Json) (*data.Fr var onValue *float64 var offValue *float64 - options := model.Get("pulseWave") + options := model.PulseWave var err error - if timeStep, err = options.Get("timeStep").Int64(); err != nil { - return nil, fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err) - } - if onCount, err = options.Get("onCount").Int64(); err != nil { - return nil, fmt.Errorf("failed to parse onCount value '%v' into integer: %v", options.Get("onCount"), err) - } - if offCount, err = options.Get("offCount").Int64(); err != nil { - return nil, fmt.Errorf("failed to parse offCount value '%v' into integer: %v", options.Get("offCount"), err) - } + timeStep = options.TimeStep + onCount = options.OnCount + offCount = options.OffCount - onValue, err = fromStringOrNumber(options.Get("onValue")) + onValue, err = fromStringOrNumber(options.OnValue) if err != nil { - return nil, fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err) + return nil, fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.OnValue, err) } - offValue, err = fromStringOrNumber(options.Get("offValue")) + offValue, err = fromStringOrNumber(options.OffValue) if err != nil { - return nil, fmt.Errorf("failed to parse offValue value '%v' into float: %v", options.Get("offValue"), err) + return nil, fmt.Errorf("failed to parse offValue value '%v' into float: %v", options.OffValue, err) } timeStep *= 1000 // Seconds to Milliseconds @@ -958,8 +1001,8 @@ func randomHeatmapData(query backend.DataQuery, fnBucketGen func(index int) floa return frame } -func doArrowQuery(query backend.DataQuery, model *simplejson.Json) (*data.Frame, error) { - encoded := model.Get("stringInput").MustString("") +func doArrowQuery(query backend.DataQuery, model JSONModel) (*data.Frame, error) { + encoded := model.StringInput if encoded == "" { return nil, nil } @@ -970,8 +1013,8 @@ func doArrowQuery(query backend.DataQuery, model *simplejson.Json) (*data.Frame, return data.UnmarshalArrowFrame(arrow) } -func newSeriesForQuery(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame { - alias := model.Get("alias").MustString("") +func newSeriesForQuery(query backend.DataQuery, model JSONModel, index int) *data.Frame { + alias := model.Alias suffix := "" if index > 0 { @@ -998,9 +1041,8 @@ func newSeriesForQuery(query backend.DataQuery, model *simplejson.Json, index in * * '{job="foo", instance="bar"} => {job: "foo", instance: "bar"}` */ -func parseLabels(model *simplejson.Json, seriesIndex int) data.Labels { - labelText := model.Get("labels").MustString("") - return parseLabelsString(labelText, seriesIndex) +func parseLabels(model JSONModel, seriesIndex int) data.Labels { + return parseLabelsString(model.Labels, seriesIndex) } func parseLabelsString(labelText string, seriesIndex int) data.Labels { @@ -1027,8 +1069,8 @@ func parseLabelsString(labelText string, seriesIndex int) data.Labels { return tags } -func frameNameForQuery(query backend.DataQuery, model *simplejson.Json, index int) string { - name := model.Get("alias").MustString("") +func frameNameForQuery(query backend.DataQuery, model JSONModel, index int) string { + name := model.Alias suffix := "" if index > 0 { @@ -1050,13 +1092,10 @@ func frameNameForQuery(query backend.DataQuery, model *simplejson.Json, index in return name } -func fromStringOrNumber(val *simplejson.Json) (*float64, error) { - switch v := val.Interface().(type) { - case json.Number: - fV, err := v.Float64() - if err != nil { - return nil, err - } +func fromStringOrNumber(val interface{}) (*float64, error) { + switch v := val.(type) { + case float64: + fV := val.(float64) return &fV, nil case string: switch v { diff --git a/pkg/tsdb/testdatasource/scenarios_test.go b/pkg/tsdb/testdatasource/scenarios_test.go index 95f3b8e509c..d0553f04e93 100644 --- a/pkg/tsdb/testdatasource/scenarios_test.go +++ b/pkg/tsdb/testdatasource/scenarios_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/tsdb/legacydata" ) @@ -23,11 +22,6 @@ func TestTestdataScenarios(t *testing.T) { t.Run("Should start at the requested value", func(t *testing.T) { timeRange := legacydata.DataTimeRange{From: "5m", To: "now", Now: time.Now()} - model := simplejson.New() - model.Set("startValue", 1.234) - modelBytes, err := model.MarshalJSON() - require.NoError(t, err) - query := backend.DataQuery{ RefID: "A", TimeRange: backend.TimeRange{ @@ -36,7 +30,7 @@ func TestTestdataScenarios(t *testing.T) { }, Interval: 100 * time.Millisecond, MaxDataPoints: 100, - JSON: modelBytes, + JSON: []byte(`{"startValue": 1.234}`), } req := &backend.QueryDataRequest{ @@ -67,10 +61,6 @@ func TestTestdataScenarios(t *testing.T) { t.Run("Should return a table that looks like value/min/max", func(t *testing.T) { timeRange := legacydata.DataTimeRange{From: "5m", To: "now", Now: time.Now()} - model := simplejson.New() - modelBytes, err := model.MarshalJSON() - require.NoError(t, err) - query := backend.DataQuery{ RefID: "A", TimeRange: backend.TimeRange{ @@ -79,7 +69,7 @@ func TestTestdataScenarios(t *testing.T) { }, Interval: 100 * time.Millisecond, MaxDataPoints: 100, - JSON: modelBytes, + JSON: []byte(`{}`), } req := &backend.QueryDataRequest{ @@ -121,12 +111,6 @@ func TestTestdataScenarios(t *testing.T) { t.Run("Should return a table with some nil values", func(t *testing.T) { timeRange := legacydata.DataTimeRange{From: "5m", To: "now", Now: time.Now()} - model := simplejson.New() - model.Set("withNil", true) - - modelBytes, err := model.MarshalJSON() - require.NoError(t, err) - query := backend.DataQuery{ RefID: "A", TimeRange: backend.TimeRange{ @@ -135,7 +119,7 @@ func TestTestdataScenarios(t *testing.T) { }, Interval: 100 * time.Millisecond, MaxDataPoints: 100, - JSON: modelBytes, + JSON: []byte(`{"withNil": true}`), } req := &backend.QueryDataRequest{ @@ -199,32 +183,32 @@ func TestParseLabels(t *testing.T) { tests := []struct { name string - model map[string]interface{} + model JSONModel expected data.Labels }{ { name: "wrapped in {} and quoted value ", - model: map[string]interface{}{"labels": `{job="foo", instance="bar"}`}, + model: JSONModel{Labels: `{job="foo", instance="bar"}`}, expected: expectedTags, }, { name: "comma-separated non-quoted", - model: map[string]interface{}{"labels": `job=foo, instance=bar`}, + model: JSONModel{Labels: `job=foo, instance=bar`}, expected: expectedTags, }, { name: "comma-separated quoted", - model: map[string]interface{}{"labels": `job="foo"", instance="bar"`}, + model: JSONModel{Labels: `job="foo"", instance="bar"`}, expected: expectedTags, }, { name: "comma-separated with spaces, non quoted", - model: map[string]interface{}{"labels": `job = foo,instance = bar`}, + model: JSONModel{Labels: `job = foo,instance = bar`}, expected: expectedTags, }, { name: "expands $seriesIndex", - model: map[string]interface{}{"labels": `job=series-$seriesIndex,instance=bar`}, + model: JSONModel{Labels: `job=series-$seriesIndex,instance=bar`}, expected: data.Labels{ "job": fmt.Sprintf("series-%d", seriesIndex), "instance": "bar", @@ -234,8 +218,7 @@ func TestParseLabels(t *testing.T) { for i, tc := range tests { t.Run(tc.name, func(t *testing.T) { - model := simplejson.NewFromAny(tc.model) - assert.Equal(t, tc.expected, parseLabels(model, seriesIndex), fmt.Sprintf("Actual tags in test case %d doesn't match expected tags", i+1)) + assert.Equal(t, tc.expected, parseLabels(tc.model, seriesIndex), fmt.Sprintf("Actual tags in test case %d doesn't match expected tags", i+1)) }) } }