mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: support time range selection on the backend (#60203)
This commit is contained in:
parent
6928ad2949
commit
8d5b19bc61
@ -19,4 +19,5 @@ var (
|
|||||||
ErrPublicDashboardHasTemplateVariables = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.hasTemplateVariables", errutil.WithPublicMessage("Public dashboard has template variables"))
|
ErrPublicDashboardHasTemplateVariables = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.hasTemplateVariables", errutil.WithPublicMessage("Public dashboard has template variables"))
|
||||||
ErrInvalidInterval = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidInterval", errutil.WithPublicMessage("intervalMS should be greater than 0"))
|
ErrInvalidInterval = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidInterval", errutil.WithPublicMessage("intervalMS should be greater than 0"))
|
||||||
ErrInvalidMaxDataPoints = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.maxDataPoints", errutil.WithPublicMessage("maxDataPoints should be greater than 0"))
|
ErrInvalidMaxDataPoints = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.maxDataPoints", errutil.WithPublicMessage("maxDataPoints should be greater than 0"))
|
||||||
|
ErrInvalidTimeRange = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidTimeRange", errutil.WithPublicMessage("Invalid time range"))
|
||||||
)
|
)
|
||||||
|
@ -94,24 +94,23 @@ func (ts *TimeSettings) ToDB() ([]byte, error) {
|
|||||||
return json.Marshal(ts)
|
return json.Marshal(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// build time settings object from json on public dashboard. If empty, use
|
// BuildTimeSettings build time settings object using selected values if enabled and are valid or dashboard default values
|
||||||
// defaults on the dashboard
|
func (pd PublicDashboard) BuildTimeSettings(dashboard *models.Dashboard, reqDTO PublicDashboardQueryDTO) TimeSettings {
|
||||||
func (pd PublicDashboard) BuildTimeSettings(dashboard *models.Dashboard) 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()
|
||||||
|
|
||||||
|
if pd.TimeSelectionEnabled {
|
||||||
|
from = reqDTO.TimeRange.From
|
||||||
|
to = reqDTO.TimeRange.To
|
||||||
|
}
|
||||||
|
|
||||||
timeRange := legacydata.NewDataTimeRange(from, to)
|
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.
|
// 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{
|
return TimeSettings{
|
||||||
From: strconv.FormatInt(timeRange.GetFromAsMsEpoch(), 10),
|
From: strconv.FormatInt(timeRange.GetFromAsMsEpoch(), 10),
|
||||||
To: strconv.FormatInt(timeRange.GetToAsMsEpoch(), 10),
|
To: strconv.FormatInt(timeRange.GetToAsMsEpoch(), 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
if pd.TimeSettings == nil {
|
|
||||||
return ts
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTO for transforming user input in the api
|
// DTO for transforming user input in the api
|
||||||
@ -125,6 +124,7 @@ type SavePublicDashboardDTO struct {
|
|||||||
type PublicDashboardQueryDTO struct {
|
type PublicDashboardQueryDTO struct {
|
||||||
IntervalMs int64
|
IntervalMs int64
|
||||||
MaxDataPoints int64
|
MaxDataPoints int64
|
||||||
|
TimeRange TimeSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnnotationsQueryDTO struct {
|
type AnnotationsQueryDTO struct {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"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"
|
||||||
@ -15,36 +17,58 @@ 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": "2022-09-01T00:00:00.000Z", "to": "2022-09-01T12:00:00.000Z"}})
|
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)
|
defaultFromMs, defaultToMs := internal.GetTimeRangeFromDashboard(t, dashboardData)
|
||||||
|
|
||||||
|
selectionFromMs := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||||
|
selectionToMs := strconv.FormatInt(time.Now().Add(time.Hour).UnixMilli(), 10)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
dashboard *models.Dashboard
|
dashboard *models.Dashboard
|
||||||
pubdash *PublicDashboard
|
pubdash *PublicDashboard
|
||||||
timeResult TimeSettings
|
timeResult TimeSettings
|
||||||
|
reqDTO PublicDashboardQueryDTO
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
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{TimeSelectionEnabled: false},
|
||||||
timeResult: TimeSettings{
|
timeResult: TimeSettings{
|
||||||
From: fromMs,
|
From: defaultFromMs,
|
||||||
To: toMs,
|
To: defaultToMs,
|
||||||
},
|
},
|
||||||
|
reqDTO: PublicDashboardQueryDTO{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "should use dashboard time even if pubdash time exists",
|
name: "should use dashboard time when time selection is disabled",
|
||||||
dashboard: &models.Dashboard{Data: dashboardData},
|
dashboard: &models.Dashboard{Data: dashboardData},
|
||||||
pubdash: &PublicDashboard{TimeSettings: &TimeSettings{From: "now-12", To: "now"}},
|
pubdash: &PublicDashboard{TimeSelectionEnabled: false, TimeSettings: &TimeSettings{From: "now-12", To: "now"}},
|
||||||
timeResult: TimeSettings{
|
timeResult: TimeSettings{
|
||||||
From: fromMs,
|
From: defaultFromMs,
|
||||||
To: toMs,
|
To: defaultToMs,
|
||||||
|
},
|
||||||
|
reqDTO: PublicDashboardQueryDTO{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should use selected values if time selection is enabled",
|
||||||
|
dashboard: &models.Dashboard{Data: dashboardData},
|
||||||
|
pubdash: &PublicDashboard{TimeSelectionEnabled: true, TimeSettings: &TimeSettings{From: "now-12", To: "now"}},
|
||||||
|
reqDTO: PublicDashboardQueryDTO{
|
||||||
|
TimeRange: TimeSettings{
|
||||||
|
From: selectionFromMs,
|
||||||
|
To: selectionToMs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeResult: TimeSettings{
|
||||||
|
From: selectionFromMs,
|
||||||
|
To: selectionToMs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.timeResult, test.pubdash.BuildTimeSettings(test.dashboard))
|
assert.Equal(t, test.timeResult, test.pubdash.BuildTimeSettings(test.dashboard, test.reqDTO))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
|||||||
|
|
||||||
// GetMetricRequest returns a metric request for the given panel and query
|
// GetMetricRequest returns a metric request for the given panel and query
|
||||||
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *dashmodels.Dashboard, publicDashboard *models.PublicDashboard, panelId int64, queryDto models.PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *dashmodels.Dashboard, publicDashboard *models.PublicDashboard, panelId int64, queryDto models.PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||||
err := validation.ValidateQueryPublicDashboardRequest(queryDto)
|
err := validation.ValidateQueryPublicDashboardRequest(queryDto, publicDashboard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dtos.MetricRequest{}, err
|
return dtos.MetricRequest{}, err
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ func (pd *PublicDashboardServiceImpl) buildMetricRequest(ctx context.Context, da
|
|||||||
return dtos.MetricRequest{}, models.ErrPanelNotFound.Errorf("buildMetricRequest: public dashboard panel not found")
|
return dtos.MetricRequest{}, models.ErrPanelNotFound.Errorf("buildMetricRequest: public dashboard panel not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := publicDashboard.BuildTimeSettings(dashboard)
|
ts := publicDashboard.BuildTimeSettings(dashboard, reqDTO)
|
||||||
|
|
||||||
// determine safe resolution to query data at
|
// determine safe resolution to query data at
|
||||||
safeInterval, safeResolution := pd.getSafeIntervalAndMaxDataPoints(reqDTO, ts)
|
safeInterval, safeResolution := pd.getSafeIntervalAndMaxDataPoints(reqDTO, ts)
|
||||||
|
@ -3,6 +3,7 @@ package validation
|
|||||||
import (
|
import (
|
||||||
"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/tsdb/legacydata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidatePublicDashboard(dto *SavePublicDashboardDTO, dashboard *models.Dashboard) error {
|
func ValidatePublicDashboard(dto *SavePublicDashboardDTO, dashboard *models.Dashboard) error {
|
||||||
@ -19,7 +20,7 @@ func hasTemplateVariables(dashboard *models.Dashboard) bool {
|
|||||||
return len(templateVariables) > 0
|
return len(templateVariables) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateQueryPublicDashboardRequest(req PublicDashboardQueryDTO) error {
|
func ValidateQueryPublicDashboardRequest(req PublicDashboardQueryDTO, pd *PublicDashboard) error {
|
||||||
if req.IntervalMs < 0 {
|
if req.IntervalMs < 0 {
|
||||||
return ErrInvalidInterval.Errorf("ValidateQueryPublicDashboardRequest: intervalMS should be greater than 0")
|
return ErrInvalidInterval.Errorf("ValidateQueryPublicDashboardRequest: intervalMS should be greater than 0")
|
||||||
}
|
}
|
||||||
@ -28,5 +29,18 @@ func ValidateQueryPublicDashboardRequest(req PublicDashboardQueryDTO) error {
|
|||||||
return ErrInvalidMaxDataPoints.Errorf("ValidateQueryPublicDashboardRequest: maxDataPoints should be greater than 0")
|
return ErrInvalidMaxDataPoints.Errorf("ValidateQueryPublicDashboardRequest: maxDataPoints should be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pd.TimeSelectionEnabled {
|
||||||
|
timeRange := legacydata.NewDataTimeRange(req.TimeRange.From, req.TimeRange.To)
|
||||||
|
|
||||||
|
_, err := timeRange.ParseFrom()
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidTimeRange.Errorf("ValidateQueryPublicDashboardRequest: time range from is invalid")
|
||||||
|
}
|
||||||
|
_, err = timeRange.ParseTo()
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidTimeRange.Errorf("ValidateQueryPublicDashboardRequest: time range to is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -42,3 +42,118 @@ func TestValidatePublicDashboard(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateQueryPublicDashboardRequest(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
req PublicDashboardQueryDTO
|
||||||
|
pd *PublicDashboard
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Returns no error when input is valid",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
IntervalMs: 1000,
|
||||||
|
MaxDataPoints: 1000,
|
||||||
|
TimeRange: TimeSettings{
|
||||||
|
From: "now-1h",
|
||||||
|
To: "now",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{
|
||||||
|
TimeSelectionEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns no error when input is valid and time selection is disabled",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
IntervalMs: 1000,
|
||||||
|
MaxDataPoints: 1000,
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{
|
||||||
|
TimeSelectionEnabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns validation error when intervalMs is less than 0",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
IntervalMs: -1,
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns validation error when maxDataPoints is less than 0",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
MaxDataPoints: -1,
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns validation error when time range from is invalid",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
TimeRange: TimeSettings{
|
||||||
|
From: "invalid",
|
||||||
|
To: "1622560000000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{
|
||||||
|
TimeSelectionEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns validation error when time range to is invalid",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
TimeRange: TimeSettings{
|
||||||
|
From: "1622560000000",
|
||||||
|
To: "invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{
|
||||||
|
TimeSelectionEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Returns validation error when time range from or to is blank",
|
||||||
|
args: args{
|
||||||
|
req: PublicDashboardQueryDTO{
|
||||||
|
TimeRange: TimeSettings{
|
||||||
|
From: "",
|
||||||
|
To: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pd: &PublicDashboard{
|
||||||
|
TimeSelectionEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := ValidateQueryPublicDashboardRequest(tt.args.req, tt.args.pd); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ValidateQueryPublicDashboardRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user