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:"-"`
|
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 {
|
func (mr *MetricRequest) CloneWithQueries(queries []*simplejson.Json) MetricRequest {
|
||||||
return MetricRequest{
|
return MetricRequest{
|
||||||
From: mr.From,
|
From: mr.From,
|
||||||
|
@ -3,11 +3,71 @@ package dtos
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestIsHiddenUser(t *testing.T) {
|
||||||
emptyHiddenUsers := map[string]struct{}{}
|
emptyHiddenUsers := map[string]struct{}{}
|
||||||
hiddenUser := 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/dtos"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"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/middleware"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
@ -25,6 +26,7 @@ type Api struct {
|
|||||||
RouteRegister routing.RouteRegister
|
RouteRegister routing.RouteRegister
|
||||||
AccessControl accesscontrol.AccessControl
|
AccessControl accesscontrol.AccessControl
|
||||||
Features *featuremgmt.FeatureManager
|
Features *featuremgmt.FeatureManager
|
||||||
|
Log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideApi(
|
func ProvideApi(
|
||||||
@ -38,6 +40,7 @@ func ProvideApi(
|
|||||||
RouteRegister: rr,
|
RouteRegister: rr,
|
||||||
AccessControl: ac,
|
AccessControl: ac,
|
||||||
Features: features,
|
Features: features,
|
||||||
|
Log: log.New("publicdashboards.api"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// attach api if PublicDashboards feature flag is enabled
|
// attach api if PublicDashboards feature flag is enabled
|
||||||
@ -83,7 +86,7 @@ func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
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{
|
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 {
|
func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response {
|
||||||
pdc, err := api.PublicDashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgID, web.Params(c.Req)[":uid"])
|
pdc, err := api.PublicDashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgID, web.Params(c.Req)[":uid"])
|
||||||
if err != nil {
|
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)
|
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
|
// exit if we don't have a valid dashboardUid
|
||||||
dashboardUid := web.Params(c.Req)[":uid"]
|
dashboardUid := web.Params(c.Req)[":uid"]
|
||||||
if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) {
|
if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) {
|
||||||
handleDashboardErr(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
|
api.handleError(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
pubdash := &PublicDashboard{}
|
pubdash := &PublicDashboard{}
|
||||||
@ -144,7 +147,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
|
|||||||
// Save the public dashboard
|
// Save the public dashboard
|
||||||
pubdash, err := api.PublicDashboardService.SavePublicDashboardConfig(c.Req.Context(), c.SignedInUser, &dto)
|
pubdash, err := api.PublicDashboardService.SavePublicDashboardConfig(c.Req.Context(), c.SignedInUser, &dto)
|
||||||
if err != nil {
|
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)
|
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"])
|
resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, web.Params(c.Req)[":accessToken"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handlePublicDashboardErr(err)
|
return api.handleError(http.StatusInternalServerError, "error running public dashboard panel queries", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return toJsonStreamingResponse(api.Features, resp)
|
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
|
// 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
|
// 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.
|
// 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
|
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 {
|
if ok := errors.As(err, &publicDashboardErr); ok {
|
||||||
return response.Error(publicDashboardErr.StatusCode, publicDashboardErr.Error(), publicDashboardErr)
|
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(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Error(defaultCode, defaultMsg, err)
|
return response.Error(code, message, err)
|
||||||
}
|
|
||||||
|
|
||||||
func handlePublicDashboardErr(err error) response.Response {
|
|
||||||
return handleDashboardErr(http.StatusInternalServerError, "Unexpected Error", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from pkg/api/metrics.go
|
// Copied from pkg/api/metrics.go
|
||||||
|
@ -26,33 +26,33 @@ func (e PublicDashboardErr) Error() string {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPublicDashboardFailedGenerateUniqueUid = PublicDashboardErr{
|
ErrPublicDashboardFailedGenerateUniqueUid = PublicDashboardErr{
|
||||||
Reason: "Failed to generate unique public dashboard id",
|
Reason: "failed to generate unique public dashboard id",
|
||||||
StatusCode: 500,
|
StatusCode: 500,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{
|
ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{
|
||||||
Reason: "Failed to public dashboard access token",
|
Reason: "failed to public dashboard access token",
|
||||||
StatusCode: 500,
|
StatusCode: 500,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardNotFound = PublicDashboardErr{
|
ErrPublicDashboardNotFound = PublicDashboardErr{
|
||||||
Reason: "Public dashboard not found",
|
Reason: "public dashboard not found",
|
||||||
StatusCode: 404,
|
StatusCode: 404,
|
||||||
Status: "not-found",
|
Status: "not-found",
|
||||||
}
|
}
|
||||||
ErrPublicDashboardPanelNotFound = PublicDashboardErr{
|
ErrPublicDashboardPanelNotFound = PublicDashboardErr{
|
||||||
Reason: "Panel not found in dashboard",
|
Reason: "panel not found in dashboard",
|
||||||
StatusCode: 404,
|
StatusCode: 404,
|
||||||
Status: "not-found",
|
Status: "not-found",
|
||||||
}
|
}
|
||||||
ErrPublicDashboardIdentifierNotSet = PublicDashboardErr{
|
ErrPublicDashboardIdentifierNotSet = PublicDashboardErr{
|
||||||
Reason: "No Uid for public dashboard specified",
|
Reason: "no Uid for public dashboard specified",
|
||||||
StatusCode: 400,
|
StatusCode: 400,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardHasTemplateVariables = PublicDashboardErr{
|
ErrPublicDashboardHasTemplateVariables = PublicDashboardErr{
|
||||||
Reason: "Public dashboard has template variables",
|
Reason: "public dashboard has template variables",
|
||||||
StatusCode: 422,
|
StatusCode: 422,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardBadRequest = PublicDashboardErr{
|
ErrPublicDashboardBadRequest = PublicDashboardErr{
|
||||||
Reason: "Bad Request",
|
Reason: "bad Request",
|
||||||
StatusCode: 400,
|
StatusCode: 400,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -205,7 +205,17 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context,
|
|||||||
return nil, err
|
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) {
|
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,
|
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}
|
dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
|
||||||
|
|
||||||
err := ValidateSavePublicDashboard(dto, dashboard)
|
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) {
|
t.Run("Returns no validation error when dashboard has no template variables", func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user