Plugins: Use error plane for api/ds/query (#54750)

* plugin client returns error base

* fix api test

* add plugin client test

* add fallback err

* fix linting

* wip

* replace bad query

* template is an error

* failing test of templated error

* add one test passing

* fix failing test

* move test

* rename ErrBadQuery to ErrQueryValidationFailure

* tidy diff

* Change to one error per specific error kind

* last err + fix test

* fix imports

* more tests

* keep req vars together

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
Will Browne
2022-09-14 18:19:57 +02:00
committed by GitHub
co-authored by Marcus Efraimsson
parent deb86e3250
commit 29327cbba2
12 changed files with 342 additions and 66 deletions
+3 -2
View File
@@ -46,6 +46,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/web"
"github.com/centrifugal/centrifuge"
@@ -598,8 +599,8 @@ func (g *GrafanaLive) handleOnRPC(client *centrifuge.Client, e centrifuge.RPCEve
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
return centrifuge.RPCReply{}, &centrifuge.Error{Code: uint32(http.StatusForbidden), Message: http.StatusText(http.StatusForbidden)}
}
var badQuery *query.ErrBadQuery
if errors.As(err, &badQuery) {
var gfErr *errutil.Error
if errors.As(err, &gfErr) && gfErr.Reason.Status() == errutil.StatusBadRequest {
return centrifuge.RPCReply{}, &centrifuge.Error{Code: uint32(http.StatusBadRequest), Message: http.StatusText(http.StatusBadRequest)}
}
return centrifuge.RPCReply{}, centrifuge.ErrorInternal
+9 -14
View File
@@ -1,17 +1,12 @@
package query
import "fmt"
import (
"github.com/grafana/grafana/pkg/util/errutil"
)
// ErrBadQuery returned whenever request is malformed and must contain a message
// suitable to return in API response.
type ErrBadQuery struct {
Message string
}
func NewErrBadQuery(msg string) *ErrBadQuery {
return &ErrBadQuery{Message: msg}
}
func (e ErrBadQuery) Error() string {
return fmt.Sprintf("bad query: %s", e.Message)
}
var (
ErrNoQueriesFound = errutil.NewBase(errutil.StatusBadRequest, "query.noQueries", errutil.WithPublicMessage("No queries found")).Errorf("no queries found")
ErrInvalidDatasourceID = errutil.NewBase(errutil.StatusBadRequest, "query.invalidDatasourceId", errutil.WithPublicMessage("Query does not contain a valid data source identifier")).Errorf("invalid data source identifier")
ErrMultipleDatasources = errutil.NewBase(errutil.StatusBadRequest, "query.differentDatasources", errutil.WithPublicMessage("All queries must use the same datasource")).Errorf("all queries must use the same datasource")
ErrMissingDataSourceInfo = errutil.NewBase(errutil.StatusBadRequest, "query.missingDataSourceInfo").MustTemplate("query missing datasource info: {{ .Public.RefId }}", errutil.WithPublic("Query {{ .Public.RefId }} is missing datasource information"))
)
+10 -5
View File
@@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafanads"
"github.com/grafana/grafana/pkg/tsdb/legacydata"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -117,7 +118,11 @@ func (s *Service) handleExpressions(ctx context.Context, user *user.SignedInUser
for _, pq := range parsedReq.parsedQueries {
if pq.datasource == nil {
return nil, NewErrBadQuery(fmt.Sprintf("query mising datasource info: %s", pq.query.RefID))
return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{
Public: map[string]interface{}{
"RefId": pq.query.RefID,
},
})
}
exprReq.Queries = append(exprReq.Queries, expr.Query{
@@ -211,7 +216,7 @@ type parsedRequest struct {
func (s *Service) parseMetricRequest(ctx context.Context, user *user.SignedInUser, skipCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) {
if len(reqDTO.Queries) == 0 {
return nil, NewErrBadQuery("no queries found")
return nil, ErrNoQueriesFound
}
timeRange := legacydata.NewDataTimeRange(reqDTO.From, reqDTO.To)
@@ -228,7 +233,7 @@ func (s *Service) parseMetricRequest(ctx context.Context, user *user.SignedInUse
return nil, err
}
if ds == nil {
return nil, NewErrBadQuery("invalid data source ID")
return nil, ErrInvalidDatasourceID
}
datasourcesByUid[ds.Uid] = ds
@@ -262,7 +267,7 @@ func (s *Service) parseMetricRequest(ctx context.Context, user *user.SignedInUse
if !req.hasExpression {
if len(datasourcesByUid) > 1 {
// We do not (yet) support mixed query type
return nil, NewErrBadQuery("all queries must use the same datasource")
return nil, ErrMultipleDatasources
}
}
@@ -314,7 +319,7 @@ func (s *Service) getDataSourceFromQuery(ctx context.Context, user *user.SignedI
return ds, nil
}
return nil, NewErrBadQuery("missing data source ID/UID")
return nil, ErrInvalidDatasourceID
}
func (s *Service) decryptSecureJsonDataFn(ctx context.Context) func(ds *datasources.DataSource) (map[string]string, error) {