mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d639b5a7f8
commit
e37420f0a8
@ -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,
|
||||
|
@ -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{}{
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -499,7 +499,7 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
publicDashboardQueryDTO,
|
||||
)
|
||||
|
||||
require.ErrorContains(t, err, "Panel not found")
|
||||
require.ErrorContains(t, err, ErrPublicDashboardPanelNotFound.Reason)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user