InfluxDB: Fix sending retention policy with the backend request (#72763)

* Add retention policy to the request

* refactor

* refactor influxql query flow

* fix healthcheck

* organize imports

* handle queries separately

* fix tests

* update bench test
pull/73839/head^2
ismail simsek 2 years ago committed by GitHub
parent dc26cdf6c9
commit 29ea0886e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      pkg/tsdb/influxdb/healthcheck.go
  2. 117
      pkg/tsdb/influxdb/influxdb.go
  3. 125
      pkg/tsdb/influxdb/influxql/influxql.go
  4. 14
      pkg/tsdb/influxdb/influxql/influxql_test.go
  5. 52
      pkg/tsdb/influxdb/influxql/response_parser.go
  6. 16
      pkg/tsdb/influxdb/influxql/response_parser_bench_test.go
  7. 326
      pkg/tsdb/influxdb/influxql/response_parser_test.go
  8. 0
      pkg/tsdb/influxdb/influxql/testdata/response.json
  9. 2
      pkg/tsdb/influxdb/mocks_test.go
  10. 22
      pkg/tsdb/influxdb/models/model_parser.go
  11. 10
      pkg/tsdb/influxdb/models/model_parser_test.go
  12. 2
      pkg/tsdb/influxdb/models/models.go
  13. 2
      pkg/tsdb/influxdb/models/query.go
  14. 2
      pkg/tsdb/influxdb/models/query_part.go
  15. 2
      pkg/tsdb/influxdb/models/query_part_test.go
  16. 2
      pkg/tsdb/influxdb/models/query_test.go

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/influxdb/flux" "github.com/grafana/grafana/pkg/tsdb/influxdb/flux"
"github.com/grafana/grafana/pkg/tsdb/influxdb/fsql" "github.com/grafana/grafana/pkg/tsdb/influxdb/fsql"
"github.com/grafana/grafana/pkg/tsdb/influxdb/influxql"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models" "github.com/grafana/grafana/pkg/tsdb/influxdb/models"
) )
@ -34,7 +35,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
case influxVersionFlux: case influxVersionFlux:
return CheckFluxHealth(ctx, dsInfo, req) return CheckFluxHealth(ctx, dsInfo, req)
case influxVersionInfluxQL: case influxVersionInfluxQL:
return CheckInfluxQLHealth(ctx, dsInfo, s) return CheckInfluxQLHealth(ctx, dsInfo)
case influxVersionSQL: case influxVersionSQL:
return CheckSQLHealth(ctx, dsInfo, req) return CheckSQLHealth(ctx, dsInfo, req)
default: default:
@ -77,30 +78,22 @@ func CheckFluxHealth(ctx context.Context, dsInfo *models.DatasourceInfo,
return getHealthCheckMessage(logger, "", errors.New("error getting flux query buckets")) return getHealthCheckMessage(logger, "", errors.New("error getting flux query buckets"))
} }
func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, s *Service) (*backend.CheckHealthResult, error) { func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo) (*backend.CheckHealthResult, error) {
logger := logger.FromContext(ctx) logger := logger.FromContext(ctx)
queryString := "SHOW measurements"
hcRequest, err := s.createRequest(ctx, logger, dsInfo, queryString)
if err != nil {
return getHealthCheckMessage(logger, "error creating influxDB healthcheck request", err)
}
res, err := dsInfo.HTTPClient.Do(hcRequest) resp, err := influxql.Query(ctx, dsInfo, &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
RefID: refID,
QueryType: "health",
JSON: []byte(`{"query": "SHOW measurements", "rawQuery": true}`),
},
},
})
if err != nil { if err != nil {
return getHealthCheckMessage(logger, "error performing influxQL query", err) return getHealthCheckMessage(logger, "error performing influxQL query", err)
} }
defer func() {
if err := res.Body.Close(); err != nil {
logger.Warn("failed to close response body", "err", err)
}
}()
resp := s.responseParser.Parse(res.Body, res.StatusCode, []Query{{
RefID: refID,
UseRawQuery: true,
RawQuery: queryString,
}})
if res, ok := resp.Responses[refID]; ok { if res, ok := resp.Responses[refID]; ok {
if res.Error != nil { if res.Error != nil {
return getHealthCheckMessage(logger, "error reading influxDB", res.Error) return getHealthCheckMessage(logger, "error reading influxDB", res.Error)

@ -3,12 +3,7 @@ package influxdb
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http"
"net/url"
"path"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
@ -19,26 +14,19 @@ import (
"github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/influxdb/influxql"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models" "github.com/grafana/grafana/pkg/tsdb/influxdb/models"
) )
var logger log.Logger = log.New("tsdb.influxdb") var logger log.Logger = log.New("tsdb.influxdb")
type Service struct { type Service struct {
queryParser *InfluxdbQueryParser
responseParser *ResponseParser
im instancemgmt.InstanceManager im instancemgmt.InstanceManager
} }
var ErrInvalidHttpMode = errors.New("'httpMode' should be either 'GET' or 'POST'")
func ProvideService(httpClient httpclient.Provider) *Service { func ProvideService(httpClient httpclient.Provider) *Service {
return &Service{ return &Service{
queryParser: &InfluxdbQueryParser{}, im: datasource.NewInstanceManager(newInstanceSettings(httpClient)),
responseParser: &ResponseParser{},
im: datasource.NewInstanceManager(newInstanceSettings(httpClient)),
} }
} }
@ -106,102 +94,19 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
version := dsInfo.Version
if version == influxVersionFlux {
return flux.Query(ctx, dsInfo, *req)
}
if version == influxVersionSQL {
return fsql.Query(ctx, dsInfo, *req)
}
logger.Debug("Making a non-Flux type query") logger.Debug(fmt.Sprintf("Making a %s type query", dsInfo.Version))
var allRawQueries string switch dsInfo.Version {
queries := make([]Query, 0, len(req.Queries)) case influxVersionFlux:
return flux.Query(ctx, dsInfo, *req)
for _, reqQuery := range req.Queries { case influxVersionInfluxQL:
query, err := s.queryParser.Parse(reqQuery) return influxql.Query(ctx, dsInfo, req)
if err != nil { case influxVersionSQL:
return &backend.QueryDataResponse{}, err return fsql.Query(ctx, dsInfo, *req)
}
rawQuery, err := query.Build(req)
if err != nil {
return &backend.QueryDataResponse{}, err
}
allRawQueries = allRawQueries + rawQuery + ";"
query.RefID = reqQuery.RefID
query.RawQuery = rawQuery
queries = append(queries, *query)
}
if setting.Env == setting.Dev {
logger.Debug("Influxdb query", "raw query", allRawQueries)
}
request, err := s.createRequest(ctx, logger, dsInfo, allRawQueries)
if err != nil {
return &backend.QueryDataResponse{}, err
}
res, err := dsInfo.HTTPClient.Do(request)
if err != nil {
return &backend.QueryDataResponse{}, err
}
defer func() {
if err := res.Body.Close(); err != nil {
logger.Warn("Failed to close response body", "err", err)
}
}()
resp := s.responseParser.Parse(res.Body, res.StatusCode, queries)
return resp, nil
}
func (s *Service) createRequest(ctx context.Context, logger log.Logger, dsInfo *models.DatasourceInfo, query string) (*http.Request, error) {
u, err := url.Parse(dsInfo.URL)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "query")
httpMode := dsInfo.HTTPMode
var req *http.Request
switch httpMode {
case "GET":
req, err = http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
case "POST":
bodyValues := url.Values{}
bodyValues.Add("q", query)
body := bodyValues.Encode()
req, err = http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(body))
if err != nil {
return nil, err
}
default: default:
return nil, ErrInvalidHttpMode return nil, fmt.Errorf("unknown influxdb version")
} }
params := req.URL.Query()
params.Set("db", dsInfo.DbName)
params.Set("epoch", "ms")
if httpMode == "GET" {
params.Set("q", query)
} else if httpMode == "POST" {
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
}
req.URL.RawQuery = params.Encode()
logger.Debug("Influxdb request", "url", req.URL.String())
return req, nil
} }
func (s *Service) getDSInfo(ctx context.Context, pluginCtx backend.PluginContext) (*models.DatasourceInfo, error) { func (s *Service) getDSInfo(ctx context.Context, pluginCtx backend.PluginContext) (*models.DatasourceInfo, error) {

@ -0,0 +1,125 @@
package influxql
import (
"context"
"errors"
"net/http"
"net/url"
"path"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
const defaultRetentionPolicy = "default"
var (
ErrInvalidHttpMode = errors.New("'httpMode' should be either 'GET' or 'POST'")
glog = log.New("tsdb.influx_influxql")
)
func Query(ctx context.Context, dsInfo *models.DatasourceInfo, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
logger := glog.FromContext(ctx)
response := backend.NewQueryDataResponse()
for _, reqQuery := range req.Queries {
query, err := models.QueryParse(reqQuery)
if err != nil {
return &backend.QueryDataResponse{}, err
}
rawQuery, err := query.Build(req)
if err != nil {
return &backend.QueryDataResponse{}, err
}
query.RefID = reqQuery.RefID
query.RawQuery = rawQuery
if setting.Env == setting.Dev {
logger.Debug("Influxdb query", "raw query", rawQuery)
}
request, err := createRequest(ctx, logger, dsInfo, rawQuery, query.Policy)
if err != nil {
return &backend.QueryDataResponse{}, err
}
resp, err := execute(dsInfo, logger, query, request)
if err != nil {
response.Responses[query.RefID] = backend.DataResponse{Error: err}
} else {
response.Responses[query.RefID] = resp
}
}
return response, nil
}
func createRequest(ctx context.Context, logger log.Logger, dsInfo *models.DatasourceInfo, queryStr string, retentionPolicy string) (*http.Request, error) {
u, err := url.Parse(dsInfo.URL)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "query")
httpMode := dsInfo.HTTPMode
var req *http.Request
switch httpMode {
case "GET":
req, err = http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
case "POST":
bodyValues := url.Values{}
bodyValues.Add("q", queryStr)
body := bodyValues.Encode()
req, err = http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(body))
if err != nil {
return nil, err
}
default:
return nil, ErrInvalidHttpMode
}
params := req.URL.Query()
params.Set("db", dsInfo.DbName)
params.Set("epoch", "ms")
// default is hardcoded default retention policy
// InfluxDB will use the default policy when it is not added to the request
if retentionPolicy != "" && retentionPolicy != "default" {
params.Set("rp", retentionPolicy)
}
if httpMode == "GET" {
params.Set("q", queryStr)
} else if httpMode == "POST" {
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
}
req.URL.RawQuery = params.Encode()
logger.Debug("Influxdb request", "url", req.URL.String())
return req, nil
}
func execute(dsInfo *models.DatasourceInfo, logger log.Logger, query *models.Query, request *http.Request) (backend.DataResponse, error) {
res, err := dsInfo.HTTPClient.Do(request)
if err != nil {
return backend.DataResponse{}, err
}
defer func() {
if err := res.Body.Close(); err != nil {
logger.Warn("Failed to close response body", "err", err)
}
}()
resp := ResponseParse(res.Body, res.StatusCode, query)
return *resp, nil
}

@ -1,4 +1,4 @@
package influxdb package influxql
import ( import (
"context" "context"
@ -9,23 +9,21 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models" "github.com/grafana/grafana/pkg/tsdb/influxdb/models"
) )
func TestExecutor_createRequest(t *testing.T) { func TestExecutor_createRequest(t *testing.T) {
logger := log.New("tsdb.influx_influxql_test")
datasource := &models.DatasourceInfo{ datasource := &models.DatasourceInfo{
URL: "http://awesome-influxdb:1337", URL: "http://awesome-influxdb:1337",
DbName: "awesome-db", DbName: "awesome-db",
HTTPMode: "GET", HTTPMode: "GET",
} }
query := "SELECT awesomeness FROM somewhere" query := "SELECT awesomeness FROM somewhere"
s := &Service{
queryParser: &InfluxdbQueryParser{},
responseParser: &ResponseParser{},
}
t.Run("createRequest with GET httpMode", func(t *testing.T) { t.Run("createRequest with GET httpMode", func(t *testing.T) {
req, err := s.createRequest(context.Background(), logger, datasource, query) req, err := createRequest(context.Background(), logger, datasource, query, defaultRetentionPolicy)
require.NoError(t, err) require.NoError(t, err)
@ -39,7 +37,7 @@ func TestExecutor_createRequest(t *testing.T) {
t.Run("createRequest with POST httpMode", func(t *testing.T) { t.Run("createRequest with POST httpMode", func(t *testing.T) {
datasource.HTTPMode = "POST" datasource.HTTPMode = "POST"
req, err := s.createRequest(context.Background(), logger, datasource, query) req, err := createRequest(context.Background(), logger, datasource, query, defaultRetentionPolicy)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "POST", req.Method) assert.Equal(t, "POST", req.Method)
@ -58,7 +56,7 @@ func TestExecutor_createRequest(t *testing.T) {
t.Run("createRequest with PUT httpMode", func(t *testing.T) { t.Run("createRequest with PUT httpMode", func(t *testing.T) {
datasource.HTTPMode = "PUT" datasource.HTTPMode = "PUT"
_, err := s.createRequest(context.Background(), logger, datasource, query) _, err := createRequest(context.Background(), logger, datasource, query, defaultRetentionPolicy)
require.EqualError(t, err, ErrInvalidHttpMode.Error()) require.EqualError(t, err, ErrInvalidHttpMode.Error())
}) })
} }

@ -1,4 +1,4 @@
package influxdb package influxql
import ( import (
"encoding/json" "encoding/json"
@ -11,51 +11,45 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
)
type ResponseParser struct{} "github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
var ( var (
legendFormat = regexp.MustCompile(`\[\[([\@\/\w-]+)(\.[\@\/\w-]+)*\]\]*|\$([\@\w-]+?)*`) legendFormat = regexp.MustCompile(`\[\[([\@\/\w-]+)(\.[\@\/\w-]+)*\]\]*|\$([\@\w-]+?)*`)
) )
func (rp *ResponseParser) Parse(buf io.ReadCloser, statusCode int, queries []Query) *backend.QueryDataResponse { func ResponseParse(buf io.ReadCloser, statusCode int, query *models.Query) *backend.DataResponse {
return rp.parse(buf, statusCode, queries) return parse(buf, statusCode, query)
} }
// parse is the same as Parse, but without the io.ReadCloser (we don't need to // parse is the same as Parse, but without the io.ReadCloser (we don't need to
// close the buffer) // close the buffer)
func (*ResponseParser) parse(buf io.Reader, statusCode int, queries []Query) *backend.QueryDataResponse { func parse(buf io.Reader, statusCode int, query *models.Query) *backend.DataResponse {
resp := backend.NewQueryDataResponse()
response, jsonErr := parseJSON(buf) response, jsonErr := parseJSON(buf)
if statusCode/100 != 2 { if statusCode/100 != 2 {
resp.Responses["A"] = backend.DataResponse{Error: fmt.Errorf("InfluxDB returned error: %s", response.Error)} return &backend.DataResponse{Error: fmt.Errorf("InfluxDB returned error: %s", response.Error)}
} }
if jsonErr != nil { if jsonErr != nil {
resp.Responses["A"] = backend.DataResponse{Error: jsonErr} return &backend.DataResponse{Error: jsonErr}
return resp
} }
if response.Error != "" { if response.Error != "" {
resp.Responses["A"] = backend.DataResponse{Error: fmt.Errorf(response.Error)} return &backend.DataResponse{Error: fmt.Errorf(response.Error)}
return resp
} }
for i, result := range response.Results { result := response.Results[0]
if result.Error != "" { if result.Error != "" {
resp.Responses[queries[i].RefID] = backend.DataResponse{Error: fmt.Errorf(result.Error)} return &backend.DataResponse{Error: fmt.Errorf(result.Error)}
} else { } else {
resp.Responses[queries[i].RefID] = backend.DataResponse{Frames: transformRows(result.Series, queries[i])} return &backend.DataResponse{Frames: transformRows(result.Series, *query)}
}
} }
return resp
} }
func parseJSON(buf io.Reader) (Response, error) { func parseJSON(buf io.Reader) (models.Response, error) {
var response Response var response models.Response
dec := json.NewDecoder(buf) dec := json.NewDecoder(buf)
dec.UseNumber() dec.UseNumber()
@ -65,7 +59,7 @@ func parseJSON(buf io.Reader) (Response, error) {
return response, err return response, err
} }
func transformRows(rows []Row, query Query) data.Frames { func transformRows(rows []models.Row, query models.Query) data.Frames {
// pre-allocate frames - this can save many allocations // pre-allocate frames - this can save many allocations
cols := 0 cols := 0
for _, row := range rows { for _, row := range rows {
@ -106,7 +100,7 @@ func transformRows(rows []Row, query Query) data.Frames {
return frames return frames
} }
func newFrameWithTimeField(row Row, column string, colIndex int, query Query, frameName []byte) *data.Frame { func newFrameWithTimeField(row models.Row, column string, colIndex int, query models.Query, frameName []byte) *data.Frame {
var timeArray []time.Time var timeArray []time.Time
var floatArray []*float64 var floatArray []*float64
var stringArray []*string var stringArray []*string
@ -164,7 +158,7 @@ func newFrameWithTimeField(row Row, column string, colIndex int, query Query, fr
return newDataFrame(name, query.RawQuery, timeField, valueField) return newDataFrame(name, query.RawQuery, timeField, valueField)
} }
func newFrameWithoutTimeField(row Row, retentionPolicyQuery bool, tagValuesQuery bool) *data.Frame { func newFrameWithoutTimeField(row models.Row, retentionPolicyQuery bool, tagValuesQuery bool) *data.Frame {
var values []string var values []string
if retentionPolicyQuery { if retentionPolicyQuery {
@ -213,7 +207,7 @@ func newDataFrame(name string, queryString string, timeField *data.Field, valueF
return frame return frame
} }
func formatFrameName(row Row, column string, query Query, frameName []byte) []byte { func formatFrameName(row models.Row, column string, query models.Query, frameName []byte) []byte {
if query.Alias == "" { if query.Alias == "" {
return buildFrameNameFromQuery(row, column, frameName) return buildFrameNameFromQuery(row, column, frameName)
} }
@ -253,7 +247,7 @@ func formatFrameName(row Row, column string, query Query, frameName []byte) []by
return result return result
} }
func buildFrameNameFromQuery(row Row, column string, frameName []byte) []byte { func buildFrameNameFromQuery(row models.Row, column string, frameName []byte) []byte {
frameName = append(frameName, row.Name...) frameName = append(frameName, row.Name...)
frameName = append(frameName, '.') frameName = append(frameName, '.')
frameName = append(frameName, column...) frameName = append(frameName, column...)
@ -329,10 +323,10 @@ func parseNumber(value interface{}) *float64 {
return &fvalue return &fvalue
} }
func isTagValuesQuery(query Query) bool { func isTagValuesQuery(query models.Query) bool {
return strings.Contains(strings.ToLower(query.RawQuery), strings.ToLower("SHOW TAG VALUES")) return strings.Contains(strings.ToLower(query.RawQuery), strings.ToLower("SHOW TAG VALUES"))
} }
func isRetentionPolicyQuery(query Query) bool { func isRetentionPolicyQuery(query models.Query) bool {
return strings.Contains(strings.ToLower(query.RawQuery), strings.ToLower("SHOW RETENTION POLICIES")) return strings.Contains(strings.ToLower(query.RawQuery), strings.ToLower("SHOW RETENTION POLICIES"))
} }

@ -1,4 +1,4 @@
package influxdb package influxql
import ( import (
_ "embed" _ "embed"
@ -6,24 +6,24 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
) )
//go:embed testdata/response.json //go:embed testdata/response.json
var testResponse string var testResponse string
// go test -benchmem -run=^$ -memprofile memprofile.out -count=10 -bench ^BenchmarkParseJson$ github.com/grafana/grafana/pkg/tsdb/influxdb // go test -benchmem -run=^$ -memprofile memprofile.out -count=10 -bench ^BenchmarkParseJson$ github.com/grafana/grafana/pkg/tsdb/influxdb/influxql
// go tool pprof -http=localhost:9999 memprofile.out // go tool pprof -http=localhost:9999 memprofile.out
func BenchmarkParseJson(b *testing.B) { func BenchmarkParseJson(b *testing.B) {
parser := &ResponseParser{} query := generateQuery(models.Query{})
query := &Query{}
queries := addQueryToQueries(*query)
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
buf := strings.NewReader(testResponse) buf := strings.NewReader(testResponse)
result := parser.parse(buf, 200, queries) result := parse(buf, 200, query)
require.NotNil(b, result.Responses["A"].Frames) require.NotNil(b, result.Frames)
require.NoError(b, result.Responses["A"].Error) require.NoError(b, result.Error)
} }
} }

@ -1,4 +1,4 @@
package influxdb package influxql
import ( import (
"encoding/json" "encoding/json"
@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -20,31 +21,29 @@ func prepare(text string) io.ReadCloser {
return io.NopCloser(strings.NewReader(text)) return io.NopCloser(strings.NewReader(text))
} }
func addQueryToQueries(query Query) []Query { func generateQuery(query models.Query) *models.Query {
var queries []Query if query.RefID == "" {
query.RefID = "A" query.RefID = "A"
query.RawQuery = "Test raw query" }
queries = append(queries, query) if query.RawQuery == "" {
return queries query.RawQuery = "Test raw query"
}
return &query
} }
func TestInfluxdbResponseParser(t *testing.T) { func TestInfluxdbResponseParser(t *testing.T) {
t.Run("Influxdb response parser should handle invalid JSON", func(t *testing.T) { t.Run("Influxdb response parser should handle invalid JSON", func(t *testing.T) {
parser := &ResponseParser{}
response := `{ invalid }` response := `{ invalid }`
query := &Query{} query := models.Query{}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
require.Nil(t, result.Responses["A"].Frames) require.Nil(t, result.Frames)
require.Error(t, result.Responses["A"].Error) require.Error(t, result.Error)
}) })
t.Run("Influxdb response parser should parse everything normally including nil bools and nil strings", func(t *testing.T) { t.Run("Influxdb response parser should parse everything normally including nil bools and nil strings", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -66,7 +65,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{} query := models.Query{}
labels, err := data.LabelsFromString("datacenter=America") labels, err := data.LabelsFromString("datacenter=America")
require.Nil(t, err) require.Nil(t, err)
@ -118,23 +117,20 @@ func TestInfluxdbResponseParser(t *testing.T) {
) )
boolFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} boolFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"] if diff := cmp.Diff(floatFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(floatFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
if diff := cmp.Diff(stringFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(stringFrame, result.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
if diff := cmp.Diff(boolFrame, frame.Frames[2], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(boolFrame, result.Frames[2], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("Influxdb response parser should parse metricFindQueries normally", func(t *testing.T) { t.Run("Influxdb response parser should parse metricFindQueries normally", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -155,8 +151,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
var queries []Query query := models.Query{RefID: "metricFindQuery"}
queries = append(queries, Query{RefID: "metricFindQuery"})
newField := data.NewField("Value", nil, []string{ newField := data.NewField("Value", nil, []string{
"cpu", "disk", "logs", "cpu", "disk", "logs",
}) })
@ -164,17 +159,14 @@ func TestInfluxdbResponseParser(t *testing.T) {
newField, newField,
) )
result := parser.Parse(prepare(response), 200, queries) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["metricFindQuery"] if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("Influxdb response parser should parse metricFindQueries->SHOW TAG VALUES normally", func(t *testing.T) { t.Run("Influxdb response parser should parse metricFindQueries->SHOW TAG VALUES normally", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -194,8 +186,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
var queries []Query query := models.Query{RawQuery: "SHOW TAG VALUES", RefID: "metricFindQuery"}
queries = append(queries, Query{RawQuery: "SHOW TAG VALUES", RefID: "metricFindQuery"})
newField := data.NewField("Value", nil, []string{ newField := data.NewField("Value", nil, []string{
"cpu-total", "cpu0", "cpu1", "cpu-total", "cpu0", "cpu1",
}) })
@ -203,46 +194,14 @@ func TestInfluxdbResponseParser(t *testing.T) {
newField, newField,
) )
result := parser.Parse(prepare(response), 200, queries) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["metricFindQuery"] if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("Influxdb response parser should parse two responses with different refIDs", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [{}]
},
{
"series": [{}]
}
]
}
`
query := &Query{}
var queries = addQueryToQueries(*query)
queryB := &Query{}
queryB.RefID = "B"
queries = append(queries, *queryB)
result := parser.Parse(prepare(response), 200, queries)
assert.Len(t, result.Responses, 2)
assert.Contains(t, result.Responses, "A")
assert.Contains(t, result.Responses, "B")
assert.NotContains(t, result.Responses, "C")
})
t.Run("Influxdb response parser populates the RawQuery in the response meta ExecutedQueryString", func(t *testing.T) { t.Run("Influxdb response parser populates the RawQuery in the response meta ExecutedQueryString", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -263,17 +222,14 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{} query := models.Query{}
query.RawQuery = "Test raw query" query.RawQuery = "Test raw query"
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"] assert.Equal(t, result.Frames[0].Meta.ExecutedQueryString, "Test raw query")
assert.Equal(t, frame.Frames[0].Meta.ExecutedQueryString, "Test raw query")
}) })
t.Run("Influxdb response parser with invalid value-format", func(t *testing.T) { t.Run("Influxdb response parser with invalid value-format", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -294,7 +250,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{} query := models.Query{}
newField := data.NewField("Value", nil, []*float64{ newField := data.NewField("Value", nil, []*float64{
util.Pointer(50.0), nil, util.Pointer(52.0), util.Pointer(50.0), nil, util.Pointer(52.0),
@ -311,17 +267,14 @@ func TestInfluxdbResponseParser(t *testing.T) {
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"] if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("Influxdb response parser with invalid timestamp-format", func(t *testing.T) { t.Run("Influxdb response parser with invalid timestamp-format", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -343,7 +296,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{} query := models.Query{}
newField := data.NewField("Value", nil, []*float64{ newField := data.NewField("Value", nil, []*float64{
util.Pointer(50.0), util.Pointer(52.0), util.Pointer(50.0), util.Pointer(52.0),
@ -359,17 +312,14 @@ func TestInfluxdbResponseParser(t *testing.T) {
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"] if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("Influxdb response parser with alias", func(t *testing.T) { t.Run("Influxdb response parser with alias", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -395,7 +345,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{Alias: "series alias"} query := models.Query{Alias: "series alias"}
labels, err := data.LabelsFromString("/cluster/name/=Cluster/, @cluster@name@=Cluster@, cluster-name=Cluster, datacenter=America, dc.region.name=Northeast") labels, err := data.LabelsFromString("/cluster/name/=Cluster/, @cluster@name@=Cluster@, cluster-name=Cluster, datacenter=America, dc.region.name=Northeast")
require.Nil(t, err) require.Nil(t, err)
newField := data.NewField("Value", labels, []*float64{ newField := data.NewField("Value", labels, []*float64{
@ -410,31 +360,28 @@ func TestInfluxdbResponseParser(t *testing.T) {
newField, newField,
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
t.Run("should parse aliases", func(t *testing.T) { t.Run("should parse aliases", func(t *testing.T) {
frame := result.Responses["A"] if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $m $measurement", Measurement: "10m"} query = models.Query{Alias: "alias $m $measurement", Measurement: "10m"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name := "alias 10m 10m" name := "alias 10m 10m"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $col", Measurement: "10m"} query = models.Query{Alias: "alias $col", Measurement: "10m"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias mean" name = "alias mean"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
name = "alias sum" name = "alias sum"
@ -444,13 +391,12 @@ func TestInfluxdbResponseParser(t *testing.T) {
}) })
testFrame.Fields[1] = newField testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name} testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $tag_datacenter"} query = models.Query{Alias: "alias $tag_datacenter"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias America" name = "alias America"
testFrame.Name = name testFrame.Name = name
newField = data.NewField("Value", labels, []*float64{ newField = data.NewField("Value", labels, []*float64{
@ -458,13 +404,12 @@ func TestInfluxdbResponseParser(t *testing.T) {
}) })
testFrame.Fields[1] = newField testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name} testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $tag_datacenter/$tag_datacenter"} query = models.Query{Alias: "alias $tag_datacenter/$tag_datacenter"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias America/America" name = "alias America/America"
testFrame.Name = name testFrame.Name = name
newField = data.NewField("Value", labels, []*float64{ newField = data.NewField("Value", labels, []*float64{
@ -472,183 +417,152 @@ func TestInfluxdbResponseParser(t *testing.T) {
}) })
testFrame.Fields[1] = newField testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name} testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[col]]", Measurement: "10m"} query = models.Query{Alias: "alias [[col]]", Measurement: "10m"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias mean" name = "alias mean"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $0 $1 $2 $3 $4"} query = models.Query{Alias: "alias $0 $1 $2 $3 $4"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias cpu upc $2 $3 $4" name = "alias cpu upc $2 $3 $4"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $0, $1 - $2 - $3, $4: something"} query = models.Query{Alias: "alias $0, $1 - $2 - $3, $4: something"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias cpu, upc - $2 - $3, $4: something" name = "alias cpu, upc - $2 - $3, $4: something"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $1"} query = models.Query{Alias: "alias $1"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias upc" name = "alias upc"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias $5"} query = models.Query{Alias: "alias $5"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias $5" name = "alias $5"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "series alias"} query = models.Query{Alias: "series alias"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "series alias" name = "series alias"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"} query = models.Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias 10m 10m" name = "alias 10m 10m"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[tag_datacenter]]"} query = models.Query{Alias: "alias [[tag_datacenter]]"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias America" name = "alias America"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[tag_dc.region.name]]"} query = models.Query{Alias: "alias [[tag_dc.region.name]]"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Northeast" name = "alias Northeast"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[tag_cluster-name]]"} query = models.Query{Alias: "alias [[tag_cluster-name]]"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Cluster" name = "alias Cluster"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[tag_/cluster/name/]]"} query = models.Query{Alias: "alias [[tag_/cluster/name/]]"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Cluster/" name = "alias Cluster/"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias [[tag_@cluster@name@]]"} query = models.Query{Alias: "alias [[tag_@cluster@name@]]"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Cluster@" name = "alias Cluster@"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
t.Run("shouldn't parse aliases", func(t *testing.T) { t.Run("shouldn't parse aliases", func(t *testing.T) {
query = &Query{Alias: "alias words with no brackets"} query = models.Query{Alias: "alias words with no brackets"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"]
name := "alias words with no brackets" name := "alias words with no brackets"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias Test 1.5"} query = models.Query{Alias: "alias Test 1.5"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Test 1.5" name = "alias Test 1.5"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
query = &Query{Alias: "alias Test -1"} query = models.Query{Alias: "alias Test -1"}
result = parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result = ResponseParse(prepare(response), 200, generateQuery(query))
frame = result.Responses["A"]
name = "alias Test -1" name = "alias Test -1"
testFrame.Name = name testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
}) })
t.Run("Influxdb response parser with errors", func(t *testing.T) { t.Run("Influxdb response parser with errors", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","mean","sum"],
"tags": {"datacenter": "America"},
"values": [
[111,222,333],
[111,222,333],
[111,null,333]
]
}
]
},
{ {
"error": "query-timeout limit exceeded" "error": "query-timeout limit exceeded"
} }
@ -656,11 +570,7 @@ func TestInfluxdbResponseParser(t *testing.T) {
} }
` `
query := &Query{} query := models.Query{}
var queries = addQueryToQueries(*query)
queryB := &Query{}
queryB.RefID = "B"
queries = append(queries, *queryB)
labels, err := data.LabelsFromString("datacenter=America") labels, err := data.LabelsFromString("datacenter=America")
require.Nil(t, err) require.Nil(t, err)
newField := data.NewField("Value", labels, []*float64{ newField := data.NewField("Value", labels, []*float64{
@ -677,32 +587,25 @@ func TestInfluxdbResponseParser(t *testing.T) {
newField, newField,
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), 200, queries) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["A"] require.EqualError(t, result.Error, "query-timeout limit exceeded")
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
require.EqualError(t, result.Responses["B"].Error, "query-timeout limit exceeded")
}) })
t.Run("Influxdb response parser with top-level error", func(t *testing.T) { t.Run("Influxdb response parser with top-level error", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"error": "error parsing query: found THING" "error": "error parsing query: found THING"
} }
` `
query := &Query{} query := models.Query{}
result := parser.Parse(prepare(response), 200, addQueryToQueries(*query)) result := ResponseParse(prepare(response), 200, generateQuery(query))
require.Nil(t, result.Responses["A"].Frames) require.Nil(t, result.Frames)
require.EqualError(t, result.Responses["A"].Error, "error parsing query: found THING") require.EqualError(t, result.Error, "error parsing query: found THING")
}) })
t.Run("Influxdb response parser parseNumber nil", func(t *testing.T) { t.Run("Influxdb response parser parseNumber nil", func(t *testing.T) {
@ -736,8 +639,6 @@ func TestInfluxdbResponseParser(t *testing.T) {
func TestResponseParser_Parse_RetentionPolicy(t *testing.T) { func TestResponseParser_Parse_RetentionPolicy(t *testing.T) {
t.Run("Influxdb response parser should parse metricFindQueries->SHOW RETENTION POLICIES normally", func(t *testing.T) { t.Run("Influxdb response parser should parse metricFindQueries->SHOW RETENTION POLICIES normally", func(t *testing.T) {
parser := &ResponseParser{}
response := ` response := `
{ {
"results": [ "results": [
@ -789,18 +690,16 @@ func TestResponseParser_Parse_RetentionPolicy(t *testing.T) {
} }
` `
var queries []Query query := models.Query{RefID: "metricFindQuery", RawQuery: "SHOW RETENTION POLICIES"}
queries = append(queries, Query{RefID: "metricFindQuery", RawQuery: "SHOW RETENTION POLICIES"})
policyFrame := data.NewFrame("", policyFrame := data.NewFrame("",
data.NewField("Value", nil, []string{ data.NewField("Value", nil, []string{
"bar", "autogen", "5m_avg", "1m_avg", "bar", "autogen", "5m_avg", "1m_avg",
}), }),
) )
result := parser.Parse(prepare(response), 200, queries) result := ResponseParse(prepare(response), 200, generateQuery(query))
frame := result.Responses["metricFindQuery"] if diff := cmp.Diff(policyFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
if diff := cmp.Diff(policyFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
@ -810,7 +709,7 @@ func TestResponseParser_Parse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input string input string
f func(t *testing.T, got *backend.QueryDataResponse) f func(t *testing.T, got backend.DataResponse)
}{ }{
{ {
name: "Influxdb response parser with valid value when null values returned", name: "Influxdb response parser with valid value when null values returned",
@ -823,7 +722,7 @@ func TestResponseParser_Parse(t *testing.T) {
[102,52] [102,52]
] ]
}]}]}`, }]}]}`,
f: func(t *testing.T, got *backend.QueryDataResponse) { f: func(t *testing.T, got backend.DataResponse) {
newField := data.NewField("Value", nil, []*float64{nil, nil, util.Pointer(52.0)}) newField := data.NewField("Value", nil, []*float64{nil, nil, util.Pointer(52.0)})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"} newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
testFrame := data.NewFrame("cpu.mean", testFrame := data.NewFrame("cpu.mean",
@ -836,7 +735,7 @@ func TestResponseParser_Parse(t *testing.T) {
newField, newField,
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
assert.Equal(t, testFrame, got.Responses["A"].Frames[0]) assert.Equal(t, testFrame, got.Frames[0])
}, },
}, },
{ {
@ -850,7 +749,7 @@ func TestResponseParser_Parse(t *testing.T) {
[102,null] [102,null]
] ]
}]}]}`, }]}]}`,
f: func(t *testing.T, got *backend.QueryDataResponse) { f: func(t *testing.T, got backend.DataResponse) {
newField := data.NewField("Value", nil, []*float64{nil, nil, nil}) newField := data.NewField("Value", nil, []*float64{nil, nil, nil})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"} newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
testFrame := data.NewFrame("cpu.mean", testFrame := data.NewFrame("cpu.mean",
@ -863,17 +762,16 @@ func TestResponseParser_Parse(t *testing.T) {
newField, newField,
) )
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"} testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
assert.Equal(t, testFrame, got.Responses["A"].Frames[0]) assert.Equal(t, testFrame, got.Frames[0])
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
parser := &ResponseParser{} got := ResponseParse(prepare(tt.input), 200, generateQuery(models.Query{}))
got := parser.Parse(prepare(tt.input), 200, addQueryToQueries(Query{}))
require.NotNil(t, got) require.NotNil(t, got)
if tt.f != nil { if tt.f != nil {
tt.f(t, got) tt.f(t, *got)
} }
}) })
} }

@ -113,8 +113,6 @@ func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
func GetMockService(version string, rt RoundTripper) *Service { func GetMockService(version string, rt RoundTripper) *Service {
return &Service{ return &Service{
queryParser: &InfluxdbQueryParser{},
responseParser: &ResponseParser{},
im: &fakeInstance{ im: &fakeInstance{
version: version, version: version,
fakeRoundTripper: rt, fakeRoundTripper: rt,

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"fmt" "fmt"
@ -12,7 +12,7 @@ import (
type InfluxdbQueryParser struct{} type InfluxdbQueryParser struct{}
func (qp *InfluxdbQueryParser) Parse(query backend.DataQuery) (*Query, error) { func QueryParse(query backend.DataQuery) (*Query, error) {
model, err := simplejson.NewJson(query.JSON) model, err := simplejson.NewJson(query.JSON)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't unmarshal query") return nil, fmt.Errorf("couldn't unmarshal query")
@ -29,17 +29,17 @@ func (qp *InfluxdbQueryParser) Parse(query backend.DataQuery) (*Query, error) {
measurement := model.Get("measurement").MustString("") measurement := model.Get("measurement").MustString("")
tags, err := qp.parseTags(model) tags, err := parseTags(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
groupBys, err := qp.parseGroupBy(model) groupBys, err := parseGroupBy(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
selects, err := qp.parseSelects(model) selects, err := parseSelects(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,7 +70,7 @@ func (qp *InfluxdbQueryParser) Parse(query backend.DataQuery) (*Query, error) {
}, nil }, nil
} }
func (qp *InfluxdbQueryParser) parseSelects(model *simplejson.Json) ([]*Select, error) { func parseSelects(model *simplejson.Json) ([]*Select, error) {
selectObjs := model.Get("select").MustArray() selectObjs := model.Get("select").MustArray()
result := make([]*Select, 0, len(selectObjs)) result := make([]*Select, 0, len(selectObjs))
@ -80,7 +80,7 @@ func (qp *InfluxdbQueryParser) parseSelects(model *simplejson.Json) ([]*Select,
for _, partObj := range selectJson.MustArray() { for _, partObj := range selectJson.MustArray() {
part := simplejson.NewFromAny(partObj) part := simplejson.NewFromAny(partObj)
queryPart, err := qp.parseQueryPart(part) queryPart, err := parseQueryPart(part)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,7 +94,7 @@ func (qp *InfluxdbQueryParser) parseSelects(model *simplejson.Json) ([]*Select,
return result, nil return result, nil
} }
func (*InfluxdbQueryParser) parseTags(model *simplejson.Json) ([]*Tag, error) { func parseTags(model *simplejson.Json) ([]*Tag, error) {
tags := model.Get("tags").MustArray() tags := model.Get("tags").MustArray()
result := make([]*Tag, 0, len(tags)) result := make([]*Tag, 0, len(tags))
for _, t := range tags { for _, t := range tags {
@ -128,7 +128,7 @@ func (*InfluxdbQueryParser) parseTags(model *simplejson.Json) ([]*Tag, error) {
return result, nil return result, nil
} }
func (*InfluxdbQueryParser) parseQueryPart(model *simplejson.Json) (*QueryPart, error) { func parseQueryPart(model *simplejson.Json) (*QueryPart, error) {
typ, err := model.Get("type").String() typ, err := model.Get("type").String()
if err != nil { if err != nil {
return nil, err return nil, err
@ -161,12 +161,12 @@ func (*InfluxdbQueryParser) parseQueryPart(model *simplejson.Json) (*QueryPart,
return qp, nil return qp, nil
} }
func (qp *InfluxdbQueryParser) parseGroupBy(model *simplejson.Json) ([]*QueryPart, error) { func parseGroupBy(model *simplejson.Json) ([]*QueryPart, error) {
groupBy := model.Get("groupBy").MustArray() groupBy := model.Get("groupBy").MustArray()
result := make([]*QueryPart, 0, len(groupBy)) result := make([]*QueryPart, 0, len(groupBy))
for _, groupObj := range groupBy { for _, groupObj := range groupBy {
groupJson := simplejson.NewFromAny(groupObj) groupJson := simplejson.NewFromAny(groupObj)
queryPart, err := qp.parseQueryPart(groupJson) queryPart, err := parseQueryPart(groupJson)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"testing" "testing"
@ -9,8 +9,6 @@ import (
) )
func TestInfluxdbQueryParser_Parse(t *testing.T) { func TestInfluxdbQueryParser_Parse(t *testing.T) {
parser := &InfluxdbQueryParser{}
t.Run("can parse influxdb json model", func(t *testing.T) { t.Run("can parse influxdb json model", func(t *testing.T) {
json := ` json := `
{ {
@ -110,7 +108,7 @@ func TestInfluxdbQueryParser_Parse(t *testing.T) {
Interval: time.Second * 20, Interval: time.Second * 20,
} }
res, err := parser.Parse(query) res, err := QueryParse(query)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, res.GroupBy, 3) require.Len(t, res.GroupBy, 3)
require.Len(t, res.Selects, 3) require.Len(t, res.Selects, 3)
@ -173,7 +171,7 @@ func TestInfluxdbQueryParser_Parse(t *testing.T) {
Interval: time.Second * 10, Interval: time.Second * 10,
} }
res, err := parser.Parse(query) res, err := QueryParse(query)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "RawDummyQuery", res.RawQuery) require.Equal(t, "RawDummyQuery", res.RawQuery)
require.Len(t, res.GroupBy, 2) require.Len(t, res.GroupBy, 2)
@ -196,7 +194,7 @@ func TestInfluxdbQueryParser_Parse(t *testing.T) {
Interval: time.Millisecond * 0, Interval: time.Millisecond * 0,
} }
res, err := parser.Parse(query) res, err := QueryParse(query)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, time.Millisecond*1, res.Interval) require.Equal(t, time.Millisecond*1, res.Interval)
}) })

@ -1,4 +1,4 @@
package influxdb package models
import "time" import "time"

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"fmt" "fmt"

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"fmt" "fmt"

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"testing" "testing"

@ -1,4 +1,4 @@
package influxdb package models
import ( import (
"strings" "strings"
Loading…
Cancel
Save