SSE: Localize/Contain Errors within an Expression (#73163)
Changes SSE to not always fail all queries when one fails. Now only the query itself, and nodes that depend on it will error. --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
co-authored by
Gilles De Mey
parent
01755608db
commit
35e488b22b
@@ -3,6 +3,7 @@ package expr
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
@@ -29,7 +31,9 @@ func TestService(t *testing.T) {
|
||||
data.NewField("value", data.Labels{"test": "label"}, []*float64{fp(2)}))
|
||||
|
||||
me := &mockEndpoint{
|
||||
Frames: []*data.Frame{dsDF},
|
||||
Responses: map[string]backend.DataResponse{
|
||||
"A": {Frames: data.Frames{dsDF}},
|
||||
},
|
||||
}
|
||||
|
||||
pCtxProvider := plugincontext.ProvideService(nil, &pluginstore.FakePluginStore{
|
||||
@@ -110,18 +114,82 @@ func TestService(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSQueryError(t *testing.T) {
|
||||
me := &mockEndpoint{
|
||||
Responses: map[string]backend.DataResponse{
|
||||
"A": {Error: fmt.Errorf("womp womp")},
|
||||
"B": {Frames: data.Frames{}},
|
||||
},
|
||||
}
|
||||
|
||||
pCtxProvider := plugincontext.ProvideService(nil, &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
{JSONData: plugins.JSONData{ID: "test"}},
|
||||
},
|
||||
}, &datafakes.FakeDataSourceService{}, nil)
|
||||
|
||||
s := Service{
|
||||
cfg: setting.NewCfg(),
|
||||
dataService: me,
|
||||
pCtxProvider: pCtxProvider,
|
||||
features: &featuremgmt.FeatureManager{},
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
metrics: newMetrics(nil),
|
||||
}
|
||||
|
||||
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{},
|
||||
},
|
||||
},
|
||||
{
|
||||
RefID: "B",
|
||||
DataSource: dataSourceModel(),
|
||||
JSON: json.RawMessage(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "math", "expression": "$A * 2" }`),
|
||||
},
|
||||
{
|
||||
RefID: "C",
|
||||
DataSource: dataSourceModel(),
|
||||
JSON: json.RawMessage(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "math", "expression": "42" }`),
|
||||
},
|
||||
}
|
||||
|
||||
req := &Request{Queries: queries, User: &user.SignedInUser{}}
|
||||
|
||||
pl, err := s.BuildPipeline(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.ExecutePipeline(context.Background(), time.Now(), pl)
|
||||
require.NoError(t, err)
|
||||
|
||||
var utilErr errutil.Error
|
||||
require.ErrorContains(t, resp.Responses["A"].Error, "womp womp")
|
||||
require.ErrorAs(t, resp.Responses["B"].Error, &utilErr)
|
||||
require.ErrorIs(t, utilErr, DependencyError)
|
||||
require.Equal(t, fp(42), resp.Responses["C"].Frames[0].Fields[0].At(0))
|
||||
}
|
||||
|
||||
func fp(f float64) *float64 {
|
||||
return &f
|
||||
}
|
||||
|
||||
type mockEndpoint struct {
|
||||
Frames data.Frames
|
||||
Responses map[string]backend.DataResponse
|
||||
}
|
||||
|
||||
func (me *mockEndpoint) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
resp := backend.NewQueryDataResponse()
|
||||
resp.Responses["A"] = backend.DataResponse{
|
||||
Frames: me.Frames,
|
||||
for _, ref := range req.Queries {
|
||||
resp.Responses[ref.RefID] = me.Responses[ref.RefID]
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user