mirror of
https://github.com/grafana/grafana.git
synced 2024-11-30 04:34:23 -06:00
ab26c4dfa4
* InfluxDB: Flux: handle boolean data-columns * normalize data-type strings based on documentation * reference missing data type
307 lines
10 KiB
Go
307 lines
10 KiB
Go
package flux
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
"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/grafana/grafana-plugin-sdk-go/experimental"
|
|
"github.com/grafana/grafana/pkg/components/securejsondata"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/infra/httpclient"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/xorcare/pointer"
|
|
|
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
|
)
|
|
|
|
//--------------------------------------------------------------
|
|
// TestData -- reads result from saved files
|
|
//--------------------------------------------------------------
|
|
|
|
// MockRunner reads local file path for testdata.
|
|
type MockRunner struct {
|
|
testDataPath string
|
|
}
|
|
|
|
func (r *MockRunner) runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) {
|
|
bytes, err := ioutil.ReadFile(filepath.Join("testdata", r.testDataPath))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
if r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write(bytes)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to write response: %s", err))
|
|
}
|
|
} else {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := influxdb2.NewClient(server.URL, "a")
|
|
return client.QueryAPI("x").Query(ctx, q)
|
|
}
|
|
|
|
func executeMockedQuery(t *testing.T, name string, query queryModel) *backend.DataResponse {
|
|
runner := &MockRunner{
|
|
testDataPath: name + ".csv",
|
|
}
|
|
|
|
dr := executeQuery(context.Background(), query, runner, 50)
|
|
return &dr
|
|
}
|
|
|
|
func verifyGoldenResponse(t *testing.T, name string) *backend.DataResponse {
|
|
dr := executeMockedQuery(t, name, queryModel{MaxDataPoints: 100})
|
|
|
|
err := experimental.CheckGoldenDataResponse(filepath.Join("testdata", fmt.Sprintf("%s.golden.txt", name)),
|
|
dr, true)
|
|
require.NoError(t, err)
|
|
require.NoError(t, dr.Error)
|
|
|
|
return dr
|
|
}
|
|
|
|
func TestExecuteSimple(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "simple")
|
|
require.Len(t, dr.Frames, 1)
|
|
require.Contains(t, dr.Frames[0].Name, "test")
|
|
require.Len(t, dr.Frames[0].Fields[1].Labels, 2)
|
|
require.Equal(t, "_time", dr.Frames[0].Fields[0].Name)
|
|
|
|
st, err := dr.Frames[0].StringTable(-1, -1)
|
|
require.NoError(t, err)
|
|
fmt.Println(st)
|
|
fmt.Println("----------------------")
|
|
}
|
|
|
|
func TestExecuteSingle(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "single")
|
|
require.Len(t, dr.Frames, 1)
|
|
}
|
|
|
|
func TestExecuteMultiple(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "multiple")
|
|
require.Len(t, dr.Frames, 3)
|
|
require.Contains(t, dr.Frames[0].Name, "test")
|
|
require.Len(t, dr.Frames[0].Fields[1].Labels, 2)
|
|
require.Equal(t, "_time", dr.Frames[0].Fields[0].Name)
|
|
|
|
st, err := dr.Frames[0].StringTable(-1, -1)
|
|
require.NoError(t, err)
|
|
fmt.Println(st)
|
|
fmt.Println("----------------------")
|
|
}
|
|
|
|
func TestExecuteColumnNamedTable(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "table")
|
|
require.Len(t, dr.Frames, 1)
|
|
}
|
|
|
|
func TestExecuteGrouping(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "grouping")
|
|
require.Len(t, dr.Frames, 3)
|
|
require.Contains(t, dr.Frames[0].Name, "system")
|
|
require.Len(t, dr.Frames[0].Fields[0].Labels, 1)
|
|
require.Equal(t, "_time", dr.Frames[0].Fields[1].Name)
|
|
|
|
st, err := dr.Frames[0].StringTable(-1, -1)
|
|
require.NoError(t, err)
|
|
fmt.Println(st)
|
|
fmt.Println("----------------------")
|
|
}
|
|
|
|
func TestAggregateGrouping(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "aggregate")
|
|
require.Len(t, dr.Frames, 1)
|
|
|
|
str, err := dr.Frames[0].StringTable(-1, -1)
|
|
require.NoError(t, err)
|
|
fmt.Println(str)
|
|
|
|
// `Name:
|
|
// Dimensions: 2 Fields by 3 Rows
|
|
// +-------------------------------+--------------------------+
|
|
// | Name: _time | Name: _value |
|
|
// | Labels: | Labels: host=hostname.ru |
|
|
// | Type: []*time.Time | Type: []*float64 |
|
|
// +-------------------------------+--------------------------+
|
|
// | 2020-06-05 12:06:00 +0000 UTC | 8.291 |
|
|
// | 2020-06-05 12:07:00 +0000 UTC | 0.534 |
|
|
// | 2020-06-05 12:08:00 +0000 UTC | 0.667 |
|
|
// +-------------------------------+--------------------------+
|
|
// `
|
|
|
|
t1 := time.Date(2020, 6, 5, 12, 6, 0, 0, time.UTC)
|
|
t2 := time.Date(2020, 6, 5, 12, 7, 0, 0, time.UTC)
|
|
t3 := time.Date(2020, 6, 5, 12, 8, 0, 0, time.UTC)
|
|
|
|
expectedFrame := data.NewFrame("",
|
|
data.NewField("_time", nil, []*time.Time{&t1, &t2, &t3}),
|
|
data.NewField("_value", map[string]string{"host": "hostname.ru"}, []*float64{
|
|
pointer.Float64(8.291),
|
|
pointer.Float64(0.534),
|
|
pointer.Float64(0.667),
|
|
}),
|
|
)
|
|
expectedFrame.Meta = &data.FrameMeta{}
|
|
|
|
diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...)
|
|
assert.Empty(t, diff)
|
|
}
|
|
|
|
func TestNonStandardTimeColumn(t *testing.T) {
|
|
dr := verifyGoldenResponse(t, "non_standard_time_column")
|
|
require.Len(t, dr.Frames, 1)
|
|
|
|
str, err := dr.Frames[0].StringTable(-1, -1)
|
|
require.NoError(t, err)
|
|
fmt.Println(str)
|
|
|
|
// Dimensions: 3 Fields by 1 Rows
|
|
// +-----------------------------------------+-----------------------------------------+------------------+
|
|
// | Name: _start_water | Name: _stop_water | Name: _value |
|
|
// | Labels: st=1 | Labels: st=1 | Labels: st=1 |
|
|
// | Type: []*time.Time | Type: []*time.Time | Type: []*float64 |
|
|
// +-----------------------------------------+-----------------------------------------+------------------+
|
|
// | 2020-06-28 17:50:13.012584046 +0000 UTC | 2020-06-29 17:50:13.012584046 +0000 UTC | 156.304 |
|
|
// +-----------------------------------------+-----------------------------------------+------------------+
|
|
|
|
t1 := time.Date(2020, 6, 28, 17, 50, 13, 12584046, time.UTC)
|
|
t2 := time.Date(2020, 6, 29, 17, 50, 13, 12584046, time.UTC)
|
|
|
|
expectedFrame := data.NewFrame("",
|
|
data.NewField("_start_water", map[string]string{"st": "1"}, []*time.Time{&t1}),
|
|
data.NewField("_stop_water", map[string]string{"st": "1"}, []*time.Time{&t2}),
|
|
data.NewField("_value", map[string]string{"st": "1"}, []*float64{
|
|
pointer.Float64(156.304),
|
|
}),
|
|
)
|
|
expectedFrame.Meta = &data.FrameMeta{}
|
|
|
|
diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...)
|
|
assert.Empty(t, diff)
|
|
}
|
|
|
|
func TestBuckets(t *testing.T) {
|
|
verifyGoldenResponse(t, "buckets")
|
|
}
|
|
|
|
func TestBooleanTagGrouping(t *testing.T) {
|
|
verifyGoldenResponse(t, "boolean_tag")
|
|
}
|
|
|
|
func TestBooleanData(t *testing.T) {
|
|
verifyGoldenResponse(t, "boolean_data")
|
|
}
|
|
|
|
func TestGoldenFiles(t *testing.T) {
|
|
verifyGoldenResponse(t, "renamed")
|
|
}
|
|
|
|
func TestRealQuery(t *testing.T) {
|
|
t.Skip() // this is used for local testing
|
|
|
|
t.Run("Check buckets() query on localhost", func(t *testing.T) {
|
|
json := simplejson.New()
|
|
json.Set("organization", "test-org")
|
|
|
|
dsInfo := &models.DataSource{
|
|
Url: "http://localhost:9999", // NOTE! no api/v2
|
|
JsonData: json,
|
|
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
|
|
"token": "PjSEcM5oWhqg2eI6IXcqYJFe5UbMM_xt-UNlAL0BRYJqLeVpcdMWidiPfWxGhu4Xrh6wioRR-CiadCg-ady68Q==",
|
|
}),
|
|
}
|
|
|
|
runner, err := runnerFromDataSource(httpclient.NewProvider(), dsInfo)
|
|
require.NoError(t, err)
|
|
|
|
dr := executeQuery(context.Background(), queryModel{
|
|
MaxDataPoints: 100,
|
|
RawQuery: "buckets()",
|
|
}, runner, 50)
|
|
err = experimental.CheckGoldenDataResponse(filepath.Join("testdata", "buckets-real.golden.txt"), &dr, true)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func assertDataResponseDimensions(t *testing.T, dr *backend.DataResponse, rows int, columns int) {
|
|
require.Len(t, dr.Frames, 1)
|
|
fields := dr.Frames[0].Fields
|
|
require.Len(t, fields, rows)
|
|
require.Equal(t, fields[0].Len(), columns)
|
|
require.Equal(t, fields[1].Len(), columns)
|
|
}
|
|
|
|
func TestMaxDataPointsExceededNoAggregate(t *testing.T) {
|
|
// unfortunately the golden-response style tests do not support
|
|
// responses that contain errors, so we can only do manual checks
|
|
// on the DataResponse
|
|
dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{MaxDataPoints: 2})
|
|
|
|
// it should contain the error-message
|
|
require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2. Try using the aggregateWindow() function in your query to reduce the number of points returned.")
|
|
assertDataResponseDimensions(t, dr, 2, 21)
|
|
}
|
|
|
|
func TestMaxDataPointsExceededWithAggregate(t *testing.T) {
|
|
// unfortunately the golden-response style tests do not support
|
|
// responses that contain errors, so we can only do manual checks
|
|
// on the DataResponse
|
|
dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{RawQuery: "aggregateWindow()", MaxDataPoints: 2})
|
|
|
|
// it should contain the error-message
|
|
require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2.")
|
|
assertDataResponseDimensions(t, dr, 2, 21)
|
|
}
|
|
|
|
func TestMultivalue(t *testing.T) {
|
|
// we await a non-labeled _time column
|
|
// and two value-columns named _value and _value2
|
|
dr := verifyGoldenResponse(t, "multivalue")
|
|
require.Len(t, dr.Frames, 4)
|
|
frame := dr.Frames[0]
|
|
require.Len(t, frame.Fields, 3)
|
|
require.Equal(t, frame.Fields[0].Name, "_time")
|
|
require.Equal(t, frame.Fields[0].Len(), 2)
|
|
require.Len(t, frame.Fields[0].Labels, 0)
|
|
require.Equal(t, frame.Fields[1].Name, "_value")
|
|
require.Len(t, frame.Fields[1].Labels, 5)
|
|
require.Equal(t, frame.Fields[2].Name, "_value2")
|
|
require.Len(t, frame.Fields[2].Labels, 5)
|
|
}
|
|
|
|
func TestMultiTime(t *testing.T) {
|
|
// we await three columns, _time, _time2, _value
|
|
// all have all labels
|
|
dr := verifyGoldenResponse(t, "multitime")
|
|
require.Len(t, dr.Frames, 4)
|
|
frame := dr.Frames[0]
|
|
require.Len(t, frame.Fields, 3)
|
|
require.Equal(t, frame.Fields[0].Name, "_time")
|
|
require.Equal(t, frame.Fields[0].Len(), 1)
|
|
require.Len(t, frame.Fields[0].Labels, 5)
|
|
require.Equal(t, frame.Fields[1].Name, "_time2")
|
|
require.Len(t, frame.Fields[1].Labels, 5)
|
|
require.Equal(t, frame.Fields[2].Name, "_value")
|
|
require.Len(t, frame.Fields[2].Labels, 5)
|
|
}
|