Plugins: Introduce HTTP 207 Multi Status response to api/ds/query (#48550)

* feature toggles

* return HTTP 207 from ds/query

* add ft check

* add API test

* add 207 check for qr

* change to OR

* revert check

* add explicit toggle check for cloudwatch

* remove unused import

* remove from defaults.ini

* add status codes to md and update swagger

* new fangled http api tests pattern

* update swagger

* Update docs/sources/http_api/data_source.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* add missing word and reformat

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
Will Browne
2022-05-03 18:02:20 +02:00
committed by GitHub
co-authored by Christopher Moyer
parent 88eeb878a4
commit 4ecd57f49c
11 changed files with 150 additions and 45 deletions
+1
View File
@@ -14,6 +14,7 @@ import (
//
// Responses:
// 200: queryDataResponse
// 207: queryDataResponse
// 401: unauthorisedError
// 400: badRequestError
// 403: forbiddenError
+9 -4
View File
@@ -31,7 +31,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext) response.Response {
if err != nil {
return hs.handleQueryMetricsError(err)
}
return toJsonStreamingResponse(resp)
return hs.toJsonStreamingResponse(resp)
}
func (hs *HTTPServer) handleQueryMetricsError(err error) *response.NormalResponse {
@@ -147,7 +147,7 @@ func (hs *HTTPServer) QueryMetricsFromDashboard(c *models.ReqContext) response.R
if err != nil {
return hs.handleQueryMetricsError(err)
}
return toJsonStreamingResponse(resp)
return hs.toJsonStreamingResponse(resp)
}
// QueryMetrics returns query metrics
@@ -198,11 +198,16 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext) response.Response {
return response.JSON(statusCode, &legacyResp)
}
func toJsonStreamingResponse(qdr *backend.QueryDataResponse) response.Response {
func (hs *HTTPServer) toJsonStreamingResponse(qdr *backend.QueryDataResponse) response.Response {
statusWhenError := http.StatusBadRequest
if hs.Features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) {
statusWhenError = http.StatusMultiStatus
}
statusCode := http.StatusOK
for _, res := range qdr.Responses {
if res.Error != nil {
statusCode = http.StatusBadRequest
statusCode = statusWhenError
}
}
+51
View File
@@ -12,6 +12,7 @@ import (
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/web/webtest"
"golang.org/x/oauth2"
@@ -19,12 +20,14 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
datasources "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@@ -500,3 +503,51 @@ func TestAPIEndpoint_Metrics_ParseDashboardQueryParams(t *testing.T) {
})
}
}
// `/ds/query` endpoint test
func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
qds := query.ProvideService(
nil,
nil,
nil,
&fakePluginRequestValidator{},
&fakeDatasources.FakeDataSourceService{},
&fakePluginClient{
QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
resp := backend.Responses{
"A": backend.DataResponse{
Error: fmt.Errorf("query failed"),
},
}
return &backend.QueryDataResponse{Responses: resp}, nil
},
},
&fakeOAuthTokenService{},
)
serverFeatureEnabled := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, true)
})
serverFeatureDisabled := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, false)
})
t.Run("Status code is 400 when data source response has an error and feature toggle is disabled", func(t *testing.T) {
req := serverFeatureDisabled.NewPostRequest("/api/ds/query", strings.NewReader(queryDatasourceInput))
webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER})
resp, err := serverFeatureDisabled.SendJSON(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("Status code is 207 when data source response has an error and feature toggle is enabled", func(t *testing.T) {
req := serverFeatureEnabled.NewPostRequest("/api/ds/query", strings.NewReader(queryDatasourceInput))
webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER})
resp, err := serverFeatureEnabled.SendJSON(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusMultiStatus, resp.StatusCode)
})
}
+11 -1
View File
@@ -249,9 +249,11 @@ type fakePluginClient struct {
plugins.Client
req *backend.CallResourceRequest
backend.QueryDataHandlerFunc
}
func (c *fakePluginClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
func (c *fakePluginClient) CallResource(_ context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
c.req = req
bytes, err := json.Marshal(map[string]interface{}{
"message": "hello",
@@ -266,3 +268,11 @@ func (c *fakePluginClient) CallResource(ctx context.Context, req *backend.CallRe
Body: bytes,
})
}
func (c *fakePluginClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if c.QueryDataHandlerFunc != nil {
return c.QueryDataHandlerFunc.QueryData(ctx, req)
}
return backend.NewQueryDataResponse(), nil
}