mirror of
https://github.com/grafana/grafana.git
synced 2026-08-26 05:17:26 -05:00
Public Dashboards: Only use queries from dashboard for caching (#54596)
Don't accept frontend queries for query caching when its from a public dashboard. Use queries on the dashboard instead.
This commit is contained in:
@@ -158,8 +158,8 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
|
|||||||
return response.Error(http.StatusBadRequest, "invalid panel ID", err)
|
return response.Error(http.StatusBadRequest, "invalid panel ID", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reqDTO := &PublicDashboardQueryDTO{}
|
reqDTO := PublicDashboardQueryDTO{}
|
||||||
if err = web.Bind(c.Req, reqDTO); err != nil {
|
if err = web.Bind(c.Req, &reqDTO); err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gets time range from dashboard in unix milliseconds
|
||||||
|
func GetTimeRangeFromDashboard(t *testing.T, dashboardData *simplejson.Json) (string, string) {
|
||||||
|
to := dashboardData.GetPath("time", "to").MustString()
|
||||||
|
from := dashboardData.GetPath("time", "from").MustString()
|
||||||
|
toTime, err := time.Parse("2006-01-02T15:04:05.000Z", to)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fromTime, err := time.Parse("2006-01-02T15:04:05.000Z", from)
|
||||||
|
require.NoError(t, err)
|
||||||
|
toUnixMilli := strconv.FormatInt(toTime.UnixMilli(), 10)
|
||||||
|
fromUnixMilli := strconv.FormatInt(fromTime.UnixMilli(), 10)
|
||||||
|
|
||||||
|
return fromUnixMilli, toUnixMilli
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicDashboardErr represents a dashboard error.
|
// PublicDashboardErr represents a dashboard error.
|
||||||
@@ -81,24 +83,21 @@ type TimeSettings struct {
|
|||||||
|
|
||||||
// build time settings object from json on public dashboard. If empty, use
|
// build time settings object from json on public dashboard. If empty, use
|
||||||
// defaults on the dashboard
|
// defaults on the dashboard
|
||||||
func (pd PublicDashboard) BuildTimeSettings(dashboard *models.Dashboard) *TimeSettings {
|
func (pd PublicDashboard) BuildTimeSettings(dashboard *models.Dashboard) TimeSettings {
|
||||||
ts := &TimeSettings{
|
from := dashboard.Data.GetPath("time", "from").MustString()
|
||||||
From: dashboard.Data.GetPath("time", "from").MustString(),
|
to := dashboard.Data.GetPath("time", "to").MustString()
|
||||||
To: dashboard.Data.GetPath("time", "to").MustString(),
|
timeRange := legacydata.NewDataTimeRange(from, to)
|
||||||
|
|
||||||
|
// Were using epoch ms because this is used to build a MetricRequest, which is used by query caching, which expected the time range in epoch milliseconds.
|
||||||
|
ts := TimeSettings{
|
||||||
|
From: strconv.FormatInt(timeRange.GetFromAsMsEpoch(), 10),
|
||||||
|
To: strconv.FormatInt(timeRange.GetToAsMsEpoch(), 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
if pd.TimeSettings == nil {
|
if pd.TimeSettings == nil {
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge time settings from public dashboard
|
|
||||||
to := pd.TimeSettings.Get("to").MustString("")
|
|
||||||
from := pd.TimeSettings.Get("from").MustString("")
|
|
||||||
if to != "" && from != "" {
|
|
||||||
ts.From = from
|
|
||||||
ts.To = to
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/publicdashboards/internal"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,38 +14,30 @@ func TestPublicDashboardTableName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildTimeSettings(t *testing.T) {
|
func TestBuildTimeSettings(t *testing.T) {
|
||||||
var dashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-8", "to": "now"}})
|
var dashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "2022-09-01T00:00:00.000Z", "to": "2022-09-01T12:00:00.000Z"}})
|
||||||
|
fromMs, toMs := internal.GetTimeRangeFromDashboard(t, dashboardData)
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
dashboard *models.Dashboard
|
dashboard *models.Dashboard
|
||||||
pubdash *PublicDashboard
|
pubdash *PublicDashboard
|
||||||
timeResult *TimeSettings
|
timeResult TimeSettings
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "should use dashboard time if pubdash time empty",
|
name: "should use dashboard time if pubdash time empty",
|
||||||
dashboard: &models.Dashboard{Data: dashboardData},
|
dashboard: &models.Dashboard{Data: dashboardData},
|
||||||
pubdash: &PublicDashboard{},
|
pubdash: &PublicDashboard{},
|
||||||
timeResult: &TimeSettings{
|
timeResult: TimeSettings{
|
||||||
From: "now-8",
|
From: fromMs,
|
||||||
To: "now",
|
To: toMs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "should use dashboard time if pubdash to/from empty",
|
name: "should use dashboard time even if pubdash time exists",
|
||||||
dashboard: &models.Dashboard{Data: dashboardData},
|
|
||||||
pubdash: &PublicDashboard{},
|
|
||||||
timeResult: &TimeSettings{
|
|
||||||
From: "now-8",
|
|
||||||
To: "now",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "should use pubdash time",
|
|
||||||
dashboard: &models.Dashboard{Data: dashboardData},
|
dashboard: &models.Dashboard{Data: dashboardData},
|
||||||
pubdash: &PublicDashboard{TimeSettings: simplejson.NewFromAny(map[string]interface{}{"from": "now-12", "to": "now"})},
|
pubdash: &PublicDashboard{TimeSettings: simplejson.NewFromAny(map[string]interface{}{"from": "now-12", "to": "now"})},
|
||||||
timeResult: &TimeSettings{
|
timeResult: TimeSettings{
|
||||||
From: "now-12",
|
From: fromMs,
|
||||||
To: "now",
|
To: toMs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ package publicdashboards
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
testing "testing"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
models "github.com/grafana/grafana/pkg/models"
|
|
||||||
publicdashboardsmodels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
dtos "github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
models "github.com/grafana/grafana/pkg/models"
|
||||||
|
|
||||||
|
publicdashboardsmodels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
|
|
||||||
|
testing "testing"
|
||||||
|
|
||||||
user "github.com/grafana/grafana/pkg/services/user"
|
user "github.com/grafana/grafana/pkg/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,27 +69,6 @@ func (_m *FakePublicDashboardService) BuildAnonymousUser(ctx context.Context, da
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryDataResponse provides a mock function with given fields: ctx, skipCache, reqDTO, panelId, accessToken
|
|
||||||
func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO *publicdashboardsmodels.PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
|
||||||
ret := _m.Called(ctx, skipCache, reqDTO, panelId, accessToken)
|
|
||||||
|
|
||||||
var r0 *backend.QueryDataResponse
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, bool, *publicdashboardsmodels.PublicDashboardQueryDTO, int64, string) *backend.QueryDataResponse); ok {
|
|
||||||
r0 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(*backend.QueryDataResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, bool, *publicdashboardsmodels.PublicDashboardQueryDTO, int64, string) error); ok {
|
|
||||||
r1 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDashboard provides a mock function with given fields: ctx, dashboardUid
|
// GetDashboard provides a mock function with given fields: ctx, dashboardUid
|
||||||
func (_m *FakePublicDashboardService) GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error) {
|
func (_m *FakePublicDashboardService) GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error) {
|
||||||
ret := _m.Called(ctx, dashboardUid)
|
ret := _m.Called(ctx, dashboardUid)
|
||||||
@@ -107,6 +92,27 @@ func (_m *FakePublicDashboardService) GetDashboard(ctx context.Context, dashboar
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId, reqDTO
|
||||||
|
func (_m *FakePublicDashboardService) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *publicdashboardsmodels.PublicDashboard, panelId int64, reqDTO publicdashboardsmodels.PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||||
|
ret := _m.Called(ctx, dashboard, publicDashboard, panelId, reqDTO)
|
||||||
|
|
||||||
|
var r0 dtos.MetricRequest
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard, *publicdashboardsmodels.PublicDashboard, int64, publicdashboardsmodels.PublicDashboardQueryDTO) dtos.MetricRequest); ok {
|
||||||
|
r0 = rf(ctx, dashboard, publicDashboard, panelId, reqDTO)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(dtos.MetricRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard, *publicdashboardsmodels.PublicDashboard, int64, publicdashboardsmodels.PublicDashboardQueryDTO) error); ok {
|
||||||
|
r1 = rf(ctx, dashboard, publicDashboard, panelId, reqDTO)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
||||||
func (_m *FakePublicDashboardService) GetPublicDashboard(ctx context.Context, accessToken string) (*publicdashboardsmodels.PublicDashboard, *models.Dashboard, error) {
|
func (_m *FakePublicDashboardService) GetPublicDashboard(ctx context.Context, accessToken string) (*publicdashboardsmodels.PublicDashboard, *models.Dashboard, error) {
|
||||||
ret := _m.Called(ctx, accessToken)
|
ret := _m.Called(ctx, accessToken)
|
||||||
@@ -162,6 +168,29 @@ func (_m *FakePublicDashboardService) GetPublicDashboardConfig(ctx context.Conte
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetQueryDataResponse provides a mock function with given fields: ctx, skipCache, reqDTO, panelId, accessToken
|
||||||
|
func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO publicdashboardsmodels.PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
||||||
|
ret := _m.Called(ctx, skipCache, reqDTO, panelId, accessToken)
|
||||||
|
|
||||||
|
var r0 *backend.QueryDataResponse
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, bool, publicdashboardsmodels.PublicDashboardQueryDTO, int64, string) *backend.QueryDataResponse); ok {
|
||||||
|
r0 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*backend.QueryDataResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, bool, publicdashboardsmodels.PublicDashboardQueryDTO, int64, string) error); ok {
|
||||||
|
r1 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// PublicDashboardEnabled provides a mock function with given fields: ctx, dashboardUid
|
// PublicDashboardEnabled provides a mock function with given fields: ctx, dashboardUid
|
||||||
func (_m *FakePublicDashboardService) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
func (_m *FakePublicDashboardService) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
||||||
ret := _m.Called(ctx, dashboardUid)
|
ret := _m.Called(ctx, dashboardUid)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
@@ -13,25 +14,26 @@ import (
|
|||||||
|
|
||||||
//go:generate mockery --name Service --structname FakePublicDashboardService --inpackage --filename public_dashboard_service_mock.go
|
//go:generate mockery --name Service --structname FakePublicDashboardService --inpackage --filename public_dashboard_service_mock.go
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
||||||
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*user.SignedInUser, error)
|
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*user.SignedInUser, error)
|
||||||
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
||||||
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
||||||
|
GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
|
||||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||||
SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error)
|
GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error)
|
||||||
GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO *PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error)
|
|
||||||
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
|
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
|
||||||
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go
|
//go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go
|
||||||
type Store interface {
|
type Store interface {
|
||||||
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
||||||
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
||||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
|
||||||
GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error)
|
|
||||||
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
||||||
|
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
||||||
|
GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error)
|
||||||
|
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||||
|
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
|
||||||
SavePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
SavePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
||||||
UpdatePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
UpdatePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
||||||
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
|
|
||||||
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,23 +190,13 @@ func (pd *PublicDashboardServiceImpl) updatePublicDashboardConfig(ctx context.Co
|
|||||||
return dto.PublicDashboard.Uid, pd.store.UpdatePublicDashboardConfig(ctx, cmd)
|
return dto.PublicDashboard.Uid, pd.store.UpdatePublicDashboardConfig(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO *PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, skipCache bool, queryDto PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
||||||
if err := validation.ValidateQueryPublicDashboardRequest(reqDTO); err != nil {
|
|
||||||
return nil, ErrPublicDashboardBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
publicDashboard, dashboard, err := pd.GetPublicDashboard(ctx, accessToken)
|
publicDashboard, dashboard, err := pd.GetPublicDashboard(ctx, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
metricReqDTO, err := pd.buildPublicDashboardMetricRequest(
|
metricReq, err := pd.GetMetricRequest(ctx, dashboard, publicDashboard, panelId, queryDto)
|
||||||
ctx,
|
|
||||||
dashboard,
|
|
||||||
publicDashboard,
|
|
||||||
panelId,
|
|
||||||
reqDTO,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -216,12 +206,31 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReqDTO, true)
|
return pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReq, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPublicDashboardMetricRequest merges public dashboard parameters with
|
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||||
|
if err := validation.ValidateQueryPublicDashboardRequest(queryDto); err != nil {
|
||||||
|
return dtos.MetricRequest{}, ErrPublicDashboardBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
metricReqDTO, err := pd.buildMetricRequest(
|
||||||
|
ctx,
|
||||||
|
dashboard,
|
||||||
|
publicDashboard,
|
||||||
|
panelId,
|
||||||
|
queryDto,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return dtos.MetricRequest{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return metricReqDTO, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildMetricRequest merges public dashboard parameters with
|
||||||
// dashboard and returns a metrics request to be sent to query backend
|
// dashboard and returns a metrics request to be sent to query backend
|
||||||
func (pd *PublicDashboardServiceImpl) buildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO *PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
func (pd *PublicDashboardServiceImpl) buildMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||||
// group queries by panel
|
// group queries by panel
|
||||||
queriesByPanel := queryModels.GroupQueriesByPanelId(dashboard.Data)
|
queriesByPanel := queryModels.GroupQueriesByPanelId(dashboard.Data)
|
||||||
queries, ok := queriesByPanel[panelId]
|
queries, ok := queriesByPanel[panelId]
|
||||||
@@ -290,7 +299,7 @@ func GenerateAccessToken() (string, error) {
|
|||||||
// this is an additional validation, all data sources implements QueryData interface and should have proper validations
|
// this is an additional validation, all data sources implements QueryData interface and should have proper validations
|
||||||
// of these limits
|
// of these limits
|
||||||
// for the maxDataPoints we took a hard limit from prometheus which is 11000
|
// for the maxDataPoints we took a hard limit from prometheus which is 11000
|
||||||
func (pd *PublicDashboardServiceImpl) getSafeIntervalAndMaxDataPoints(reqDTO *PublicDashboardQueryDTO, ts *TimeSettings) (int64, int64) {
|
func (pd *PublicDashboardServiceImpl) getSafeIntervalAndMaxDataPoints(reqDTO PublicDashboardQueryDTO, ts TimeSettings) (int64, int64) {
|
||||||
// arbitrary max value for all data sources, it is actually a hard limit defined in prometheus
|
// arbitrary max value for all data sources, it is actually a hard limit defined in prometheus
|
||||||
safeResolution := int64(11000)
|
safeResolution := int64(11000)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/publicdashboards/internal"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||||
. "github.com/grafana/grafana/pkg/services/publicdashboards"
|
. "github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||||
database "github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||||
@@ -352,13 +353,57 @@ func TestBuildAnonymousUser(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
func TestGetMetricRequest(t *testing.T) {
|
||||||
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
|
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures())
|
||||||
|
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||||
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{})
|
||||||
|
publicDashboard := &PublicDashboard{
|
||||||
|
Uid: "1",
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
IsEnabled: true,
|
||||||
|
AccessToken: "abc123",
|
||||||
|
}
|
||||||
|
service := &PublicDashboardServiceImpl{
|
||||||
|
log: log.New("test.logger"),
|
||||||
|
store: publicdashboardStore,
|
||||||
|
intervalCalculator: intervalv2.NewCalculator(),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("will return an error when validation fails", func(t *testing.T) {
|
||||||
|
publicDashboardQueryDTO := PublicDashboardQueryDTO{
|
||||||
|
IntervalMs: int64(-1),
|
||||||
|
MaxDataPoints: int64(-1),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := service.GetMetricRequest(context.Background(), dashboard, publicDashboard, 1, publicDashboardQueryDTO)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("will not return an error when validation succeeds", func(t *testing.T) {
|
||||||
|
publicDashboardQueryDTO := PublicDashboardQueryDTO{
|
||||||
|
IntervalMs: int64(1),
|
||||||
|
MaxDataPoints: int64(1),
|
||||||
|
}
|
||||||
|
from, to := internal.GetTimeRangeFromDashboard(t, dashboard.Data)
|
||||||
|
|
||||||
|
metricReq, err := service.GetMetricRequest(context.Background(), dashboard, publicDashboard, 1, publicDashboardQueryDTO)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, from, metricReq.From)
|
||||||
|
require.Equal(t, to, metricReq.To)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMetricRequest(t *testing.T) {
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures())
|
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures())
|
||||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||||
|
|
||||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{})
|
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{})
|
||||||
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{})
|
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{})
|
||||||
|
from, to := internal.GetTimeRangeFromDashboard(t, publicDashboard.Data)
|
||||||
|
|
||||||
service := &PublicDashboardServiceImpl{
|
service := &PublicDashboardServiceImpl{
|
||||||
log: log.New("test.logger"),
|
log: log.New("test.logger"),
|
||||||
@@ -366,7 +411,7 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
intervalCalculator: intervalv2.NewCalculator(),
|
intervalCalculator: intervalv2.NewCalculator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
publicDashboardQueryDTO := &PublicDashboardQueryDTO{
|
publicDashboardQueryDTO := PublicDashboardQueryDTO{
|
||||||
IntervalMs: int64(10000000),
|
IntervalMs: int64(10000000),
|
||||||
MaxDataPoints: int64(200),
|
MaxDataPoints: int64(200),
|
||||||
}
|
}
|
||||||
@@ -400,7 +445,7 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
||||||
reqDTO, err := service.buildPublicDashboardMetricRequest(
|
reqDTO, err := service.buildMetricRequest(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
publicDashboard,
|
publicDashboard,
|
||||||
publicDashboardPD,
|
publicDashboardPD,
|
||||||
@@ -409,8 +454,8 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, timeSettings.Get("from").MustString(), reqDTO.From)
|
require.Equal(t, from, reqDTO.From)
|
||||||
require.Equal(t, timeSettings.Get("to").MustString(), reqDTO.To)
|
require.Equal(t, to, reqDTO.To)
|
||||||
|
|
||||||
for i := range reqDTO.Queries {
|
for i := range reqDTO.Queries {
|
||||||
require.Equal(t, publicDashboardQueryDTO.IntervalMs, reqDTO.Queries[i].Get("intervalMs").MustInt64())
|
require.Equal(t, publicDashboardQueryDTO.IntervalMs, reqDTO.Queries[i].Get("intervalMs").MustInt64())
|
||||||
@@ -449,7 +494,7 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns an error when panel missing", func(t *testing.T) {
|
t.Run("returns an error when panel missing", func(t *testing.T) {
|
||||||
_, err := service.buildPublicDashboardMetricRequest(
|
_, err := service.buildMetricRequest(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
publicDashboard,
|
publicDashboard,
|
||||||
publicDashboardPD,
|
publicDashboardPD,
|
||||||
@@ -514,6 +559,10 @@ func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardSto
|
|||||||
"templating": map[string]interface{}{
|
"templating": map[string]interface{}{
|
||||||
"list": templateVars,
|
"list": templateVars,
|
||||||
},
|
},
|
||||||
|
"time": map[string]interface{}{
|
||||||
|
"from": "2022-09-01T00:00:00.000Z",
|
||||||
|
"to": "2022-09-01T12:00:00.000Z",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
dash, err := dashboardStore.SaveDashboard(cmd)
|
dash, err := dashboardStore.SaveDashboard(cmd)
|
||||||
@@ -526,8 +575,8 @@ func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardSto
|
|||||||
|
|
||||||
func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T) {
|
func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
reqDTO *PublicDashboardQueryDTO
|
reqDTO PublicDashboardQueryDTO
|
||||||
ts *TimeSettings
|
ts TimeSettings
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -538,11 +587,11 @@ func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T
|
|||||||
{
|
{
|
||||||
name: "return original interval",
|
name: "return original interval",
|
||||||
args: args{
|
args: args{
|
||||||
reqDTO: &PublicDashboardQueryDTO{
|
reqDTO: PublicDashboardQueryDTO{
|
||||||
IntervalMs: 10000,
|
IntervalMs: 10000,
|
||||||
MaxDataPoints: 300,
|
MaxDataPoints: 300,
|
||||||
},
|
},
|
||||||
ts: &TimeSettings{
|
ts: TimeSettings{
|
||||||
From: "now-3h",
|
From: "now-3h",
|
||||||
To: "now",
|
To: "now",
|
||||||
},
|
},
|
||||||
@@ -553,11 +602,11 @@ func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T
|
|||||||
{
|
{
|
||||||
name: "return safe interval because of a small interval",
|
name: "return safe interval because of a small interval",
|
||||||
args: args{
|
args: args{
|
||||||
reqDTO: &PublicDashboardQueryDTO{
|
reqDTO: PublicDashboardQueryDTO{
|
||||||
IntervalMs: 1000,
|
IntervalMs: 1000,
|
||||||
MaxDataPoints: 300,
|
MaxDataPoints: 300,
|
||||||
},
|
},
|
||||||
ts: &TimeSettings{
|
ts: TimeSettings{
|
||||||
From: "now-6h",
|
From: "now-6h",
|
||||||
To: "now",
|
To: "now",
|
||||||
},
|
},
|
||||||
@@ -568,11 +617,11 @@ func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T
|
|||||||
{
|
{
|
||||||
name: "return safe interval for long time range",
|
name: "return safe interval for long time range",
|
||||||
args: args{
|
args: args{
|
||||||
reqDTO: &PublicDashboardQueryDTO{
|
reqDTO: PublicDashboardQueryDTO{
|
||||||
IntervalMs: 100,
|
IntervalMs: 100,
|
||||||
MaxDataPoints: 300,
|
MaxDataPoints: 300,
|
||||||
},
|
},
|
||||||
ts: &TimeSettings{
|
ts: TimeSettings{
|
||||||
From: "now-90d",
|
From: "now-90d",
|
||||||
To: "now",
|
To: "now",
|
||||||
},
|
},
|
||||||
@@ -583,8 +632,8 @@ func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T
|
|||||||
{
|
{
|
||||||
name: "return safe interval when reqDTO is empty",
|
name: "return safe interval when reqDTO is empty",
|
||||||
args: args{
|
args: args{
|
||||||
reqDTO: &PublicDashboardQueryDTO{},
|
reqDTO: PublicDashboardQueryDTO{},
|
||||||
ts: &TimeSettings{
|
ts: TimeSettings{
|
||||||
From: "now-90d",
|
From: "now-90d",
|
||||||
To: "now",
|
To: "now",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func hasTemplateVariables(dashboard *models.Dashboard) bool {
|
|||||||
return len(templateVariables) > 0
|
return len(templateVariables) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateQueryPublicDashboardRequest(req *publicDashboardModels.PublicDashboardQueryDTO) error {
|
func ValidateQueryPublicDashboardRequest(req publicDashboardModels.PublicDashboardQueryDTO) error {
|
||||||
if req.IntervalMs < 0 {
|
if req.IntervalMs < 0 {
|
||||||
return fmt.Errorf("intervalMS should be greater than 0")
|
return fmt.Errorf("intervalMS should be greater than 0")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user