diff --git a/.changeset/friendly-panthers-yell.md b/.changeset/friendly-panthers-yell.md new file mode 100644 index 000000000..07994b63a --- /dev/null +++ b/.changeset/friendly-panthers-yell.md @@ -0,0 +1,5 @@ +--- +'grafana-infinity-datasource': patch +--- + +Fix error source for invalid queries diff --git a/.changeset/metal-cows-lie.md b/.changeset/metal-cows-lie.md new file mode 100644 index 000000000..019a3736f --- /dev/null +++ b/.changeset/metal-cows-lie.md @@ -0,0 +1,5 @@ +--- +'grafana-infinity-datasource': patch +--- + +Fixed a bug where base url not working as expected when there is no url in the query. Fixes [#908](https://github.com/grafana/grafana-infinity-datasource/issues/908) diff --git a/pkg/models/query.go b/pkg/models/query.go index 51aeaa80f..95b4947f4 100644 --- a/pkg/models/query.go +++ b/pkg/models/query.go @@ -188,7 +188,7 @@ type InfinityDataOverride struct { Override string `json:"override"` } -func ApplyDefaultsToQuery(ctx context.Context, query Query) Query { +func ApplyDefaultsToQuery(ctx context.Context, query Query, settings InfinitySettings) Query { if query.Type == "" { query.Type = QueryTypeJSON if query.Source == "" { @@ -198,20 +198,13 @@ func ApplyDefaultsToQuery(ctx context.Context, query Query) Query { if query.Type == QueryTypeJSON && query.Source == "inline" && query.Data == "" { query.Data = "[]" } - if (query.Type == QueryTypeJSON || query.Type == QueryTypeGraphQL || query.Type == QueryTypeUQL || query.Type == QueryTypeGROQ) && query.Source == "url" && query.URL == "" { - query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.json" - } - if query.Type == QueryTypeCSV && query.Source == "url" && query.URL == "" { - query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.csv" - } - if query.Type == QueryTypeTSV && query.Source == "url" && query.URL == "" { - query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.tsv" - } - if query.Type == QueryTypeXML && query.Source == "url" && query.URL == "" { - query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.xml" - } - if query.Type == QueryTypeHTML && query.Source == "url" && query.URL == "" { - query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.html" + if query.Source == "url" && query.URL == "" && strings.TrimSpace(settings.URL) == "" { + switch query.Type { + case QueryTypeJSON, QueryTypeCSV, QueryTypeTSV, QueryTypeXML, QueryTypeHTML: + query.URL = fmt.Sprintf("https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.%s", strings.ToLower(string(query.Type))) + case QueryTypeGraphQL, QueryTypeUQL, QueryTypeGROQ: + query.URL = "https://raw.githubusercontent.com/grafana/grafana-infinity-datasource/main/testdata/users.json" + } } if query.Source == "url" && strings.ToUpper(query.URLOptions.Method) == "POST" { if query.URLOptions.BodyType == "" { @@ -306,14 +299,13 @@ func ApplyDefaultsToQuery(ctx context.Context, query Query) Query { return query } -func LoadQuery(ctx context.Context, backendQuery backend.DataQuery, pluginContext backend.PluginContext) (Query, error) { +func LoadQuery(ctx context.Context, backendQuery backend.DataQuery, pluginContext backend.PluginContext, settings InfinitySettings) (Query, error) { var query Query err := json.Unmarshal(backendQuery.JSON, &query) if err != nil { - // Plugin error as the user should not have been able to send a bad query - return query, errorsource.PluginError(fmt.Errorf("error while parsing the query json. %w", err), false) + return query, errorsource.DownstreamError(fmt.Errorf("error while parsing the query json. %w", err), false) } - query = ApplyDefaultsToQuery(ctx, query) + query = ApplyDefaultsToQuery(ctx, query, settings) if query.PageMode == PaginationModeList && strings.TrimSpace(query.PageParamListFieldName) == "" { // Downstream error as user input is not correct return query, errorsource.DownstreamError(errors.New("pagination_param_list_field_name cannot be empty"), false) diff --git a/pkg/models/query_test.go b/pkg/models/query_test.go index f4571a54c..84b82ff24 100644 --- a/pkg/models/query_test.go +++ b/pkg/models/query_test.go @@ -15,6 +15,7 @@ func TestLoadQuery(t *testing.T) { tests := []struct { name string queryJSON string + settings models.InfinitySettings want models.Query wantErr error }{ @@ -121,7 +122,7 @@ func TestLoadQuery(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q := &backend.DataQuery{JSON: []byte(tt.queryJSON)} - got, err := models.LoadQuery(context.Background(), *q, backend.PluginContext{}) + got, err := models.LoadQuery(context.Background(), *q, backend.PluginContext{}, tt.settings) if tt.wantErr != nil { require.NotNil(t, err) assert.Equal(t, tt.wantErr, err) diff --git a/pkg/pluginhost/handler_querydata.go b/pkg/pluginhost/handler_querydata.go index 2387aa776..05c821361 100644 --- a/pkg/pluginhost/handler_querydata.go +++ b/pkg/pluginhost/handler_querydata.go @@ -25,7 +25,7 @@ func (ds *DataSource) QueryData(ctx context.Context, req *backend.QueryDataReque return response, errorsource.PluginError(errors.New("invalid infinity client"), false) } for _, q := range req.Queries { - query, err := models.LoadQuery(ctx, q, req.PluginContext) + query, err := models.LoadQuery(ctx, q, req.PluginContext, ds.client.Settings) if err != nil { span.RecordError(err) logger.Error("error un-marshaling the query", "error", err.Error()) @@ -51,23 +51,6 @@ func (ds *DataSource) QueryData(ctx context.Context, req *backend.QueryDataReque return response, nil } -func QueryData(ctx context.Context, backendQuery backend.DataQuery, infClient infinity.Client, requestHeaders map[string]string, pluginContext backend.PluginContext) (response backend.DataResponse) { - logger := backend.Logger.FromContext(ctx) - ctx, span := tracing.DefaultTracer().Start(ctx, "QueryData") - defer span.End() - query, err := models.LoadQuery(ctx, backendQuery, pluginContext) - if err != nil { - span.RecordError(err) - span.SetStatus(500, err.Error()) - logger.Error("error un-marshaling the query", "error", err.Error()) - response.Error = fmt.Errorf("error un-marshaling the query. %w", err) - // We should have error source from the original error, but in a case it is not there, we are using the plugin error as the default source - response.ErrorSource = errorsource.SourceError(backend.ErrorSourcePlugin, err, false).Source() - return response - } - return QueryDataQuery(ctx, query, infClient, requestHeaders, pluginContext) -} - func QueryDataQuery(ctx context.Context, query models.Query, infClient infinity.Client, requestHeaders map[string]string, pluginContext backend.PluginContext) (response backend.DataResponse) { logger := backend.Logger.FromContext(ctx) ctx, span := tracing.DefaultTracer().Start(ctx, "QueryDataQuery", trace.WithAttributes( diff --git a/pkg/testsuite/handler_querydata_errors_test.go b/pkg/testsuite/handler_querydata_errors_test.go index 7170d88af..32f39a1c8 100644 --- a/pkg/testsuite/handler_querydata_errors_test.go +++ b/pkg/testsuite/handler_querydata_errors_test.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana-infinity-datasource/pkg/infinity" "github.com/grafana/grafana-infinity-datasource/pkg/models" - "github.com/grafana/grafana-infinity-datasource/pkg/pluginhost" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/infinity-libs/lib/go/jsonframer" "github.com/stretchr/testify/require" @@ -25,7 +24,7 @@ func TestErrors(t *testing.T) { server.Start() defer server.Close() client, _ := infinity.NewClient(context.TODO(), models.InfinitySettings{}) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "url": "%s" }`, server.URL)), }, *client, map[string]string{}, backend.PluginContext{}) require.Nil(t, res.Error) @@ -38,7 +37,7 @@ func TestErrors(t *testing.T) { server.Start() defer server.Close() client, _ := infinity.NewClient(context.TODO(), models.InfinitySettings{}) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "url": "%s" }`, server.URL)), }, *client, map[string]string{}, backend.PluginContext{}) require.NotNil(t, res.Error) @@ -54,7 +53,7 @@ func TestErrors(t *testing.T) { server.Start() defer server.Close() client, _ := infinity.NewClient(context.TODO(), models.InfinitySettings{}) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "url": "%s" }`, server.URL)), }, *client, map[string]string{}, backend.PluginContext{}) require.NotNil(t, res.Error) @@ -70,7 +69,7 @@ func TestErrors(t *testing.T) { server.Start() defer server.Close() client, _ := infinity.NewClient(context.TODO(), models.InfinitySettings{}) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "url": "%s", "parser": "backend", "root_selector": "foo bar baz"}`, server.URL)), }, *client, map[string]string{}, backend.PluginContext{}) require.NotNil(t, res.Error) diff --git a/pkg/testsuite/handler_querydata_test.go b/pkg/testsuite/handler_querydata_test.go index 6ea2ff96a..8a5916445 100644 --- a/pkg/testsuite/handler_querydata_test.go +++ b/pkg/testsuite/handler_querydata_test.go @@ -20,6 +20,13 @@ import ( "github.com/stretchr/testify/require" ) +func queryData(t *testing.T, ctx context.Context, backendQuery backend.DataQuery, infClient infinity.Client, requestHeaders map[string]string, pluginContext backend.PluginContext) (response backend.DataResponse) { + t.Helper() + query, err := models.LoadQuery(ctx, backendQuery, pluginContext, infClient.Settings) + require.Nil(t, err) + return pluginhost.QueryDataQuery(ctx, query, infClient, requestHeaders, pluginContext) +} + func TestAuthentication(t *testing.T) { t.Run("should throw error when allowed hosts not configured", func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -29,7 +36,7 @@ func TestAuthentication(t *testing.T) { client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodApiKey}) require.Nil(t, err) require.NotNil(t, client) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -59,7 +66,7 @@ func TestAuthentication(t *testing.T) { Password: "myPassword", }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -94,7 +101,7 @@ func TestAuthentication(t *testing.T) { Password: "myIncorrectPassword", }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -124,7 +131,7 @@ func TestAuthentication(t *testing.T) { ForwardOauthIdentity: true, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -148,7 +155,7 @@ func TestAuthentication(t *testing.T) { ForwardOauthIdentity: false, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -188,7 +195,7 @@ func TestAuthentication(t *testing.T) { }, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "source": "url", @@ -228,7 +235,7 @@ func TestAuthentication(t *testing.T) { }, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "source": "url", @@ -263,7 +270,7 @@ func TestAuthentication(t *testing.T) { TLSCACert: mockPEMClientCACet, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -294,7 +301,7 @@ func TestAuthentication(t *testing.T) { InsecureSkipVerify: true, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -319,7 +326,7 @@ func TestAuthentication(t *testing.T) { AllowedHosts: []string{"http://httpbin.org/digest-auth"}, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -337,7 +344,7 @@ func TestAuthentication(t *testing.T) { AllowedHosts: []string{"http://httpbin.org/digest-auth"}, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -355,7 +362,7 @@ func TestAuthentication(t *testing.T) { AllowedHosts: []string{"http://httpbin.org/digest-auth"}, }) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -378,7 +385,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -442,7 +449,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "json", "url": "%s", @@ -478,7 +485,7 @@ func TestResponseFormats(t *testing.T) { t.Run("should parse the computed columns", func(t *testing.T) { client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: ""}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(`{ "type": "json", "data": "[{ \"Name\": \"amc ambassador dpl\", \"Miles_per_Gallon\": 15, \"Cylinders\": 8, \"Displacement\": 390, \"Horsepower\": 190, \"Weight_in_lbs\": 3850, \"Acceleration\": 8.5, \"Year\": \"1970-01-01\", \"Origin\": \"USA\" }, { \"Name\": \"citroen ds-21 pallas\", \"Miles_per_Gallon\": null, \"Cylinders\": null, \"Displacement\": 133, \"Horsepower\": 115, \"Weight_in_lbs\": 3090, \"Acceleration\": 17.5, \"Year\": \"1970-01-01\", \"Origin\": \"Europe\" }, { \"Name\": \"chevrolet hello concours (sw)\", \"Miles_per_Gallon\": null, \"Cylinders\": 8, \"Displacement\": 350, \"Horsepower\": 165, \"Weight_in_lbs\": 4142, \"Acceleration\": 11.5, \"Year\": \"1970-01-01\", \"Origin\": \"USA\" }]", @@ -496,7 +503,7 @@ func TestResponseFormats(t *testing.T) { t.Run("should filter computed columns", func(t *testing.T) { client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: ""}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(`{ "type": "json", "data": "[{\"id\":0,\"name\":\"iPhone 6S\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/phone/6s/images/goldbig.jpg\",\"price\":799},{\"id\":1,\"name\":\"iPhone 5S\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/phone/5s/images/silverbig.png\",\"price\":349},{\"id\":2,\"name\":\"Macbook\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/mac/macbook/images/pro.jpg\",\"price\":1499},{\"id\":3,\"name\":\"Macbook Air\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/mac/mbair/images/air.jpg\",\"price\":999},{\"id\":4,\"name\":\"Macbook Air 2013\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/mac/mbair/images/air.jpg\",\"price\":599},{\"id\":5,\"name\":\"Macbook Air 2012\",\"description\":\"Kogi skateboard tattooed, whatever portland fingerstache coloring book mlkshk leggings flannel dreamcatcher.\",\"imageUrl\":\"http://www.icentar.me/mac/mbair/images/air.jpg\",\"price\":499}]", @@ -522,7 +529,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "graphql", "url": "%s", @@ -548,7 +555,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "uql", "url": "%s", @@ -574,7 +581,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "xml", "url": "%s", @@ -600,7 +607,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "uql", "url": "%s", @@ -626,7 +633,7 @@ func TestResponseFormats(t *testing.T) { defer server.Close() client, err := infinity.NewClient(context.TODO(), models.InfinitySettings{URL: server.URL}) require.Nil(t, err) - res := pluginhost.QueryData(context.Background(), backend.DataQuery{ + res := queryData(t, context.Background(), backend.DataQuery{ JSON: []byte(fmt.Sprintf(`{ "type": "groq", "url": "%s", @@ -754,7 +761,7 @@ func TestInlineSources(t *testing.T) { queryJSON = "{}" } bq := backend.DataQuery{JSON: []byte(queryJSON), TimeRange: tt.timeRange} - query, err := models.LoadQuery(context.Background(), bq, backend.PluginContext{}) + query, err := models.LoadQuery(context.Background(), bq, backend.PluginContext{}, models.InfinitySettings{}) require.Nil(t, err) frame, err := infinity.GetFrameForInlineSources(context.TODO(), query) if tt.wantErr != nil { @@ -924,7 +931,7 @@ func TestRemoteSources(t *testing.T) { queryJSON = "{}" } bq := backend.DataQuery{JSON: []byte(queryJSON), TimeRange: tt.timeRange} - query, err := models.LoadQuery(context.Background(), bq, backend.PluginContext{}) + query, err := models.LoadQuery(context.Background(), bq, backend.PluginContext{}, models.InfinitySettings{}) require.Nil(t, err) client := tt.client if client == nil { diff --git a/src/constants.ts b/src/constants.ts index 087589198..3a83668fd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,7 +6,7 @@ export const DefaultInfinityQuery: InfinityQuery = { type: 'json', source: 'url', format: 'table', - url: 'https://github.com/grafana/grafana-infinity-datasource/blob/main/testdata/users.json', + url: '', url_options: { method: 'GET', data: '' }, root_selector: '', columns: [],