PublicDashboards: Log api layer errors and which datasources fail/succeed (#55056)

Adds pubdash api error logging and logs array of datasource names with success status when running pubdash queries.
This commit is contained in:
owensmallwood 2022-09-14 13:19:21 -06:00 committed by GitHub
parent d639b5a7f8
commit e37420f0a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 22 deletions

View File

@ -76,6 +76,25 @@ type MetricRequest struct {
HTTPRequest *http.Request `json:"-"`
}
func (mr *MetricRequest) GetUniqueDatasourceTypes() []string {
dsTypes := make(map[string]bool)
for _, query := range mr.Queries {
if dsType, ok := query.Get("datasource").CheckGet("type"); ok {
name := dsType.MustString()
if _, ok := dsTypes[name]; !ok {
dsTypes[name] = true
}
}
}
res := make([]string, 0)
for dsType := range dsTypes {
res = append(res, dsType)
}
return res
}
func (mr *MetricRequest) CloneWithQueries(queries []*simplejson.Json) MetricRequest {
return MetricRequest{
From: mr.From,

View File

@ -3,11 +3,71 @@ package dtos
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
)
func TestGetUniqueDatasourceTypes(t *testing.T) {
testcases := []struct {
desc string
queries []*simplejson.Json
result []string
}{
{
desc: "can get unique datasource names",
result: []string{"prometheus", "mysql"},
queries: []*simplejson.Json{
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "prometheus",
"uid": "uid1",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "prometheus",
"uid": "uid2",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "mysql",
"uid": "uid3",
},
}),
},
},
{
desc: "returns empty slice when datasources have no type property",
result: []string{},
queries: []*simplejson.Json{
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"uid": "uid1",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"uid": "uid3",
},
}),
},
},
}
for _, testcase := range testcases {
t.Run(testcase.desc, func(t *testing.T) {
metReq := MetricRequest{
Queries: testcase.queries,
}
result := metReq.GetUniqueDatasourceTypes()
assert.Equal(t, testcase.result, result)
})
}
}
func TestIsHiddenUser(t *testing.T) {
emptyHiddenUsers := map[string]struct{}{}
hiddenUser := map[string]struct{}{

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@ -25,6 +26,7 @@ type Api struct {
RouteRegister routing.RouteRegister
AccessControl accesscontrol.AccessControl
Features *featuremgmt.FeatureManager
Log log.Logger
}
func ProvideApi(
@ -38,6 +40,7 @@ func ProvideApi(
RouteRegister: rr,
AccessControl: ac,
Features: features,
Log: log.New("publicdashboards.api"),
}
// attach api if PublicDashboards feature flag is enabled
@ -83,7 +86,7 @@ func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
)
if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err)
return api.handleError(http.StatusInternalServerError, "failed to get public dashboard", err)
}
meta := dtos.DashboardMeta{
@ -113,7 +116,7 @@ func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response {
pdc, err := api.PublicDashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgID, web.Params(c.Req)[":uid"])
if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard config", err)
return api.handleError(http.StatusInternalServerError, "failed to get public dashboard config", err)
}
return response.JSON(http.StatusOK, pdc)
}
@ -124,7 +127,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
// exit if we don't have a valid dashboardUid
dashboardUid := web.Params(c.Req)[":uid"]
if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) {
handleDashboardErr(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
api.handleError(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
}
pubdash := &PublicDashboard{}
@ -144,7 +147,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
// Save the public dashboard
pubdash, err := api.PublicDashboardService.SavePublicDashboardConfig(c.Req.Context(), c.SignedInUser, &dto)
if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err)
return api.handleError(http.StatusInternalServerError, "failed to save public dashboard configuration", err)
}
return response.JSON(http.StatusOK, pubdash)
@ -165,7 +168,7 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, web.Params(c.Req)[":accessToken"])
if err != nil {
return handlePublicDashboardErr(err)
return api.handleError(http.StatusInternalServerError, "error running public dashboard panel queries", err)
}
return toJsonStreamingResponse(api.Features, resp)
@ -174,10 +177,12 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
// util to help us unpack dashboard and publicdashboard errors or use default http code and message
// we should look to do some future refactoring of these errors as publicdashboard err is the same as a dashboarderr, just defined in a
// different package.
func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.Response {
func (api *Api) handleError(code int, message string, err error) response.Response {
var publicDashboardErr PublicDashboardErr
// handle public dashboard er
api.Log.Error(message, "error", err.Error())
// handle public dashboard error
if ok := errors.As(err, &publicDashboardErr); ok {
return response.Error(publicDashboardErr.StatusCode, publicDashboardErr.Error(), publicDashboardErr)
}
@ -188,11 +193,7 @@ func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr)
}
return response.Error(defaultCode, defaultMsg, err)
}
func handlePublicDashboardErr(err error) response.Response {
return handleDashboardErr(http.StatusInternalServerError, "Unexpected Error", err)
return response.Error(code, message, err)
}
// Copied from pkg/api/metrics.go

View File

@ -26,33 +26,33 @@ func (e PublicDashboardErr) Error() string {
var (
ErrPublicDashboardFailedGenerateUniqueUid = PublicDashboardErr{
Reason: "Failed to generate unique public dashboard id",
Reason: "failed to generate unique public dashboard id",
StatusCode: 500,
}
ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{
Reason: "Failed to public dashboard access token",
Reason: "failed to public dashboard access token",
StatusCode: 500,
}
ErrPublicDashboardNotFound = PublicDashboardErr{
Reason: "Public dashboard not found",
Reason: "public dashboard not found",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardPanelNotFound = PublicDashboardErr{
Reason: "Panel not found in dashboard",
Reason: "panel not found in dashboard",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardIdentifierNotSet = PublicDashboardErr{
Reason: "No Uid for public dashboard specified",
Reason: "no Uid for public dashboard specified",
StatusCode: 400,
}
ErrPublicDashboardHasTemplateVariables = PublicDashboardErr{
Reason: "Public dashboard has template variables",
Reason: "public dashboard has template variables",
StatusCode: 422,
}
ErrPublicDashboardBadRequest = PublicDashboardErr{
Reason: "Bad Request",
Reason: "bad Request",
StatusCode: 400,
}
)

View File

@ -205,7 +205,17 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context,
return nil, err
}
return pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReq, true)
res, err := pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReq, true)
// We want to track which datasources were successful and which were not
reqDatasources := metricReq.GetUniqueDatasourceTypes()
if err != nil {
pd.log.Error("Error querying datasources for public dashboard", "error", err.Error(), "datasources", reqDatasources)
return nil, err
}
pd.log.Info("Successfully queried datasources for public dashboard", "datasources", reqDatasources)
return res, nil
}
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) {

View File

@ -499,7 +499,7 @@ func TestBuildMetricRequest(t *testing.T) {
publicDashboardQueryDTO,
)
require.ErrorContains(t, err, "Panel not found")
require.ErrorContains(t, err, ErrPublicDashboardPanelNotFound.Reason)
})
}

View File

@ -25,7 +25,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard)
require.ErrorContains(t, err, "Public dashboard has template variables")
require.ErrorContains(t, err, publicdashboardModels.ErrPublicDashboardHasTemplateVariables.Reason)
})
t.Run("Returns no validation error when dashboard has no template variables", func(t *testing.T) {