2023-04-12 11:24:34 -05:00
|
|
|
package expr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/grafana/dataplane/examples"
|
2023-09-13 12:58:16 -05:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
2023-04-12 11:24:34 -05:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2023-04-18 07:04:51 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
2023-06-08 06:59:51 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins"
|
2023-09-21 04:33:31 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/config"
|
|
|
|
pluginFakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
2023-04-12 11:24:34 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
|
|
datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2023-06-08 06:59:51 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
2023-09-11 06:59:24 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
2023-06-08 06:59:51 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2023-04-12 11:24:34 -05:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
"github.com/grafana/grafana/pkg/util"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPassThroughDataplaneExamples(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
validExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, collection := range validExamples.Collections() {
|
|
|
|
for _, example := range collection.ExampleSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
_, err := framesPassThroughService(t, example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func framesPassThroughService(t *testing.T, frames data.Frames) (data.Frames, error) {
|
|
|
|
me := &mockEndpoint{
|
2023-09-13 12:58:16 -05:00
|
|
|
map[string]backend.DataResponse{"A": {Frames: frames}},
|
2023-04-12 11:24:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
cfg := setting.NewCfg()
|
|
|
|
|
|
|
|
s := Service{
|
2023-06-08 06:59:51 -05:00
|
|
|
cfg: cfg,
|
|
|
|
dataService: me,
|
|
|
|
features: &featuremgmt.FeatureManager{},
|
2023-09-21 04:33:31 -05:00
|
|
|
pCtxProvider: plugincontext.ProvideService(cfg, nil, &pluginstore.FakePluginStore{
|
2023-09-11 06:59:24 -05:00
|
|
|
PluginList: []pluginstore.Plugin{
|
2023-06-08 06:59:51 -05:00
|
|
|
{JSONData: plugins.JSONData{ID: "test"}},
|
|
|
|
}},
|
2023-09-21 04:33:31 -05:00
|
|
|
&datafakes.FakeDataSourceService{}, nil, pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
|
2023-06-08 06:59:51 -05:00
|
|
|
tracer: tracing.InitializeTracerForTest(),
|
|
|
|
metrics: newMetrics(nil),
|
2023-04-12 11:24:34 -05:00
|
|
|
}
|
|
|
|
queries := []Query{{
|
|
|
|
RefID: "A",
|
|
|
|
DataSource: &datasources.DataSource{
|
|
|
|
OrgID: 1,
|
|
|
|
UID: "test",
|
|
|
|
Type: "test",
|
|
|
|
},
|
|
|
|
JSON: json.RawMessage(`{ "datasource": { "uid": "1" }, "intervalMs": 1000, "maxDataPoints": 1000 }`),
|
|
|
|
TimeRange: AbsoluteTimeRange{
|
|
|
|
From: time.Time{},
|
|
|
|
To: time.Time{},
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
2023-06-08 06:59:51 -05:00
|
|
|
req := &Request{
|
|
|
|
Queries: queries,
|
|
|
|
User: &user.SignedInUser{},
|
|
|
|
}
|
2023-04-12 11:24:34 -05:00
|
|
|
|
|
|
|
pl, err := s.BuildPipeline(req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
res, err := s.ExecutePipeline(context.Background(), time.Now(), pl)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Contains(t, res.Responses, "A")
|
|
|
|
|
|
|
|
return res.Responses["A"].Frames, res.Responses["A"].Error
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestShouldUseDataplane(t *testing.T) {
|
|
|
|
t.Run("zero frames returns no data and is allowed", func(t *testing.T) {
|
|
|
|
f := data.Frames{}
|
|
|
|
dt, use, err := shouldUseDataplane(f, log.New(""), false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, use)
|
|
|
|
require.Equal(t, data.KindUnknown, dt.Kind())
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a frame with Type and TypeVersion 0.0 will not use dataplane", func(t *testing.T) {
|
|
|
|
f := data.Frames{(&data.Frame{}).SetMeta(
|
|
|
|
&data.FrameMeta{
|
|
|
|
TypeVersion: data.FrameTypeVersion{},
|
|
|
|
Type: data.FrameTypeTimeSeriesMulti,
|
|
|
|
},
|
|
|
|
)}
|
|
|
|
_, use, err := shouldUseDataplane(f, log.New(""), false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, use)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a frame without Type and TypeVersion > 0.0 will not use dataplane", func(t *testing.T) {
|
|
|
|
f := data.Frames{(&data.Frame{}).SetMeta(
|
|
|
|
&data.FrameMeta{
|
|
|
|
TypeVersion: data.FrameTypeVersion{0, 1},
|
|
|
|
},
|
|
|
|
)}
|
|
|
|
_, use, err := shouldUseDataplane(f, log.New(""), false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, use)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a frame with no metadata will not use dataplane", func(t *testing.T) {
|
|
|
|
f := data.Frames{&data.Frame{}}
|
|
|
|
_, use, err := shouldUseDataplane(f, log.New(""), false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, use)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a newer version that supported will return a warning but still use dataplane", func(t *testing.T) {
|
|
|
|
ty := data.FrameTypeTimeSeriesMulti
|
|
|
|
v := data.FrameTypeVersion{999, 999}
|
|
|
|
f := data.Frames{(&data.Frame{}).SetMeta(
|
|
|
|
&data.FrameMeta{
|
|
|
|
Type: ty,
|
|
|
|
TypeVersion: v,
|
|
|
|
},
|
|
|
|
)}
|
|
|
|
dt, use, err := shouldUseDataplane(f, log.New(""), false)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.True(t, use)
|
|
|
|
require.Equal(t, data.KindTimeSeries, dt.Kind())
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("all valid dataplane examples should use dataplane", func(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
validExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, collection := range validExamples.Collections() {
|
|
|
|
for _, example := range collection.ExampleSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
_, err := framesPassThroughService(t, example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHandleDataplaneNumeric(t *testing.T) {
|
|
|
|
t.Run("no data", func(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
validNoDataNumericExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
Kind: data.KindNumeric,
|
|
|
|
NoData: util.Pointer(true),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, example := range validNoDataNumericExamples.AsSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
res, err := handleDataplaneNumeric(example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Values, 1)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should read correct number of items from examples", func(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
numericExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
Kind: data.KindNumeric,
|
|
|
|
NoData: util.Pointer(false),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, example := range numericExamples.AsSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
res, err := handleDataplaneNumeric(example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Values, example.Info().ItemCount)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHandleDataplaneTS(t *testing.T) {
|
|
|
|
t.Run("no data", func(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
validNoDataTSExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
Kind: data.KindTimeSeries,
|
|
|
|
NoData: util.Pointer(true),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, example := range validNoDataTSExamples.AsSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
res, err := handleDataplaneTimeseries(example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Values, 1)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("should read correct number of items from examples", func(t *testing.T) {
|
|
|
|
es, err := examples.GetExamples()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tsExamples, err := es.Filter(examples.FilterOptions{
|
|
|
|
Version: data.FrameTypeVersion{0, 1},
|
|
|
|
Valid: util.Pointer(true),
|
|
|
|
Kind: data.KindTimeSeries,
|
|
|
|
NoData: util.Pointer(false),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, example := range tsExamples.AsSlice() {
|
|
|
|
t.Run(example.Info().ID, func(t *testing.T) {
|
|
|
|
res, err := handleDataplaneTimeseries(example.Frames("A"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Values, example.Info().ItemCount)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|