mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: refactor service (#57372)
This commit is contained in:
parent
f161c5407a
commit
5b9959014c
@ -50,7 +50,7 @@ func RequiresExistingAccessToken(publicDashboardService publicdashboards.Service
|
||||
}
|
||||
|
||||
// Check that the access token references an enabled public dashboard
|
||||
exists, err := publicDashboardService.AccessTokenExists(c.Req.Context(), accessToken)
|
||||
exists, err := publicDashboardService.PublicDashboardEnabledExistsByAccessToken(c.Req.Context(), accessToken)
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Failed to query access token", nil)
|
||||
return
|
||||
|
@ -74,7 +74,7 @@ func TestRequiresExistingAccessToken(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
publicdashboardService := &publicdashboards.FakePublicDashboardService{}
|
||||
publicdashboardService.On("AccessTokenExists", mock.Anything, mock.Anything).Return(tt.AccessTokenExists, tt.AccessTokenExistsErr)
|
||||
publicdashboardService.On("PublicDashboardEnabledExistsByAccessToken", mock.Anything, mock.Anything).Return(tt.AccessTokenExists, tt.AccessTokenExistsErr)
|
||||
params := map[string]string{":accessToken": tt.AccessToken}
|
||||
mw := RequiresExistingAccessToken(publicdashboardService)
|
||||
_, resp := runMw(t, nil, "GET", tt.Path, params, mw)
|
||||
|
@ -258,8 +258,8 @@ func (d *PublicDashboardStoreImpl) UpdatePublicDashboardConfig(ctx context.Conte
|
||||
return err
|
||||
}
|
||||
|
||||
// Responds true if public dashboard for a dashboard exists and isEnabled
|
||||
func (d *PublicDashboardStoreImpl) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
// EnabledPublicDashboardExistsByDashboardUid Responds true if there is an enabled public dashboard for a dashboard uid
|
||||
func (d *PublicDashboardStoreImpl) PublicDashboardEnabledExistsByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
hasPublicDashboard := false
|
||||
err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
sql := "SELECT COUNT(*) FROM dashboard_public WHERE dashboard_uid=? AND is_enabled=true"
|
||||
@ -276,9 +276,8 @@ func (d *PublicDashboardStoreImpl) PublicDashboardEnabled(ctx context.Context, d
|
||||
return hasPublicDashboard, err
|
||||
}
|
||||
|
||||
// Responds true if accessToken exists and isEnabled. May be renamed in the
|
||||
// future
|
||||
func (d *PublicDashboardStoreImpl) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
|
||||
// EnabledPublicDashboardExistsByAccessToken Responds true if accessToken exists and isEnabled
|
||||
func (d *PublicDashboardStoreImpl) PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error) {
|
||||
hasPublicDashboard := false
|
||||
err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
sql := "SELECT COUNT(*) FROM dashboard_public WHERE access_token=? AND is_enabled=true"
|
||||
|
@ -88,8 +88,7 @@ func TestIntegrationGetDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// AccessTokenExists
|
||||
func TestIntegrationAccessTokenExists(t *testing.T) {
|
||||
func TestIntegrationEnabledPublicDashboardExistsByAccessToken(t *testing.T) {
|
||||
var sqlStore db.DB
|
||||
var cfg *setting.Cfg
|
||||
var dashboardStore *dashboardsDB.DashboardStore
|
||||
@ -102,7 +101,7 @@ func TestIntegrationAccessTokenExists(t *testing.T) {
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
t.Run("AccessTokenExists will return true when at least one public dashboard has a matching access token", func(t *testing.T) {
|
||||
t.Run("PublicDashboardEnabledExistsByAccessToken will return true when at least one public dashboard has a matching access token", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
|
||||
@ -118,13 +117,13 @@ func TestIntegrationAccessTokenExists(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := publicdashboardStore.AccessTokenExists(context.Background(), "accessToken")
|
||||
res, err := publicdashboardStore.PublicDashboardEnabledExistsByAccessToken(context.Background(), "accessToken")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, res)
|
||||
})
|
||||
|
||||
t.Run("AccessTokenExists will return false when IsEnabled=false", func(t *testing.T) {
|
||||
t.Run("PublicDashboardEnabledExistsByAccessToken will return false when IsEnabled=false", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
|
||||
@ -140,24 +139,23 @@ func TestIntegrationAccessTokenExists(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := publicdashboardStore.AccessTokenExists(context.Background(), "accessToken")
|
||||
res, err := publicdashboardStore.PublicDashboardEnabledExistsByAccessToken(context.Background(), "accessToken")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, res)
|
||||
})
|
||||
|
||||
t.Run("AccessTokenExists will return false when no public dashboard has matching access token", func(t *testing.T) {
|
||||
t.Run("PublicDashboardEnabledExistsByAccessToken will return false when no public dashboard has matching access token", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
res, err := publicdashboardStore.AccessTokenExists(context.Background(), "accessToken")
|
||||
res, err := publicdashboardStore.PublicDashboardEnabledExistsByAccessToken(context.Background(), "accessToken")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.False(t, res)
|
||||
})
|
||||
}
|
||||
|
||||
// PublicDashboardEnabled
|
||||
func TestIntegrationPublicDashboardEnabled(t *testing.T) {
|
||||
func TestIntegrationEnabledPublicDashboardExistsByDashboardUid(t *testing.T) {
|
||||
var sqlStore db.DB
|
||||
var cfg *setting.Cfg
|
||||
var dashboardStore *dashboardsDB.DashboardStore
|
||||
@ -171,7 +169,7 @@ func TestIntegrationPublicDashboardEnabled(t *testing.T) {
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
t.Run("PublicDashboardEnabled Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) {
|
||||
t.Run("PublicDashboardEnabledExistsByDashboardUid Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
|
||||
@ -187,13 +185,13 @@ func TestIntegrationPublicDashboardEnabled(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := publicdashboardStore.PublicDashboardEnabled(context.Background(), savedDashboard.Uid)
|
||||
res, err := publicdashboardStore.PublicDashboardEnabledExistsByDashboardUid(context.Background(), savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, res)
|
||||
})
|
||||
|
||||
t.Run("PublicDashboardEnabled will return false when dashboard has public dashboards but they are not enabled", func(t *testing.T) {
|
||||
t.Run("PublicDashboardEnabledExistsByDashboardUid will return false when dashboard has public dashboards but they are not enabled", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
|
||||
@ -209,7 +207,7 @@ func TestIntegrationPublicDashboardEnabled(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := publicdashboardStore.PublicDashboardEnabled(context.Background(), savedDashboard.Uid)
|
||||
res, err := publicdashboardStore.PublicDashboardEnabledExistsByDashboardUid(context.Background(), savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, res)
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/grafana/grafana/pkg/models"
|
||||
models "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
|
||||
publicdashboardsmodels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
pkgmodels "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
user "github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
@ -24,7 +24,7 @@ type FakePublicDashboardService struct {
|
||||
}
|
||||
|
||||
// AccessTokenExists provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakePublicDashboardService) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
|
||||
func (_m *FakePublicDashboardService) PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
||||
var r0 bool
|
||||
@ -44,37 +44,21 @@ func (_m *FakePublicDashboardService) AccessTokenExists(ctx context.Context, acc
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BuildAnonymousUser provides a mock function with given fields: ctx, dashboard
|
||||
func (_m *FakePublicDashboardService) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) *user.SignedInUser {
|
||||
ret := _m.Called(ctx, dashboard)
|
||||
|
||||
var r0 *user.SignedInUser
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard) *user.SignedInUser); ok {
|
||||
r0 = rf(ctx, dashboard)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*user.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetAnnotations provides a mock function with given fields: ctx, reqDTO, accessToken
|
||||
func (_m *FakePublicDashboardService) GetAnnotations(ctx context.Context, reqDTO publicdashboardsmodels.AnnotationsQueryDTO, accessToken string) ([]publicdashboardsmodels.AnnotationEvent, error) {
|
||||
func (_m *FakePublicDashboardService) GetAnnotations(ctx context.Context, reqDTO models.AnnotationsQueryDTO, accessToken string) ([]models.AnnotationEvent, error) {
|
||||
ret := _m.Called(ctx, reqDTO, accessToken)
|
||||
|
||||
var r0 []publicdashboardsmodels.AnnotationEvent
|
||||
if rf, ok := ret.Get(0).(func(context.Context, publicdashboardsmodels.AnnotationsQueryDTO, string) []publicdashboardsmodels.AnnotationEvent); ok {
|
||||
var r0 []models.AnnotationEvent
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.AnnotationsQueryDTO, string) []models.AnnotationEvent); ok {
|
||||
r0 = rf(ctx, reqDTO, accessToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]publicdashboardsmodels.AnnotationEvent)
|
||||
r0 = ret.Get(0).([]models.AnnotationEvent)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, publicdashboardsmodels.AnnotationsQueryDTO, string) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, models.AnnotationsQueryDTO, string) error); ok {
|
||||
r1 = rf(ctx, reqDTO, accessToken)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@ -84,15 +68,15 @@ func (_m *FakePublicDashboardService) GetAnnotations(ctx context.Context, reqDTO
|
||||
}
|
||||
|
||||
// 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) (*pkgmodels.Dashboard, error) {
|
||||
ret := _m.Called(ctx, dashboardUid)
|
||||
|
||||
var r0 *models.Dashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Dashboard); ok {
|
||||
var r0 *pkgmodels.Dashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *pkgmodels.Dashboard); ok {
|
||||
r0 = rf(ctx, dashboardUid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Dashboard)
|
||||
r0 = ret.Get(0).(*pkgmodels.Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,18 +91,18 @@ func (_m *FakePublicDashboardService) GetDashboard(ctx context.Context, dashboar
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (_m *FakePublicDashboardService) GetMetricRequest(ctx context.Context, dashboard *pkgmodels.Dashboard, publicDashboard *models.PublicDashboard, panelId int64, reqDTO models.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 {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *pkgmodels.Dashboard, *models.PublicDashboard, int64, models.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 {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *pkgmodels.Dashboard, *models.PublicDashboard, int64, models.PublicDashboardQueryDTO) error); ok {
|
||||
r1 = rf(ctx, dashboard, publicDashboard, panelId, reqDTO)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@ -128,24 +112,24 @@ func (_m *FakePublicDashboardService) GetMetricRequest(ctx context.Context, dash
|
||||
}
|
||||
|
||||
// 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) (*models.PublicDashboard, *pkgmodels.Dashboard, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
||||
var r0 *publicdashboardsmodels.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *publicdashboardsmodels.PublicDashboard); ok {
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, accessToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*publicdashboardsmodels.PublicDashboard)
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *models.Dashboard
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) *models.Dashboard); ok {
|
||||
var r1 *pkgmodels.Dashboard
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) *pkgmodels.Dashboard); ok {
|
||||
r1 = rf(ctx, accessToken)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*models.Dashboard)
|
||||
r1 = ret.Get(1).(*pkgmodels.Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,15 +144,15 @@ func (_m *FakePublicDashboardService) GetPublicDashboard(ctx context.Context, ac
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
||||
func (_m *FakePublicDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*publicdashboardsmodels.PublicDashboard, error) {
|
||||
func (_m *FakePublicDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, orgId, dashboardUid)
|
||||
|
||||
var r0 *publicdashboardsmodels.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *publicdashboardsmodels.PublicDashboard); ok {
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, orgId, dashboardUid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*publicdashboardsmodels.PublicDashboard)
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,11 +188,11 @@ func (_m *FakePublicDashboardService) GetPublicDashboardOrgId(ctx context.Contex
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO models.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 {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, bool, models.PublicDashboardQueryDTO, int64, string) *backend.QueryDataResponse); ok {
|
||||
r0 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
@ -217,7 +201,7 @@ func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context,
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, bool, publicdashboardsmodels.PublicDashboardQueryDTO, int64, string) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, bool, models.PublicDashboardQueryDTO, int64, string) error); ok {
|
||||
r1 = rf(ctx, skipCache, reqDTO, panelId, accessToken)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@ -227,15 +211,15 @@ func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context,
|
||||
}
|
||||
|
||||
// ListPublicDashboards provides a mock function with given fields: ctx, u, orgId
|
||||
func (_m *FakePublicDashboardService) ListPublicDashboards(ctx context.Context, u *user.SignedInUser, orgId int64) ([]publicdashboardsmodels.PublicDashboardListResponse, error) {
|
||||
func (_m *FakePublicDashboardService) ListPublicDashboards(ctx context.Context, u *user.SignedInUser, orgId int64) ([]models.PublicDashboardListResponse, error) {
|
||||
ret := _m.Called(ctx, u, orgId)
|
||||
|
||||
var r0 []publicdashboardsmodels.PublicDashboardListResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, int64) []publicdashboardsmodels.PublicDashboardListResponse); ok {
|
||||
var r0 []models.PublicDashboardListResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, int64) []models.PublicDashboardListResponse); ok {
|
||||
r0 = rf(ctx, u, orgId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]publicdashboardsmodels.PublicDashboardListResponse)
|
||||
r0 = ret.Get(0).([]models.PublicDashboardListResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,20 +255,20 @@ func (_m *FakePublicDashboardService) PublicDashboardEnabled(ctx context.Context
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig provides a mock function with given fields: ctx, u, dto
|
||||
func (_m *FakePublicDashboardService) SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *publicdashboardsmodels.SavePublicDashboardConfigDTO) (*publicdashboardsmodels.PublicDashboard, error) {
|
||||
func (_m *FakePublicDashboardService) SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *models.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, u, dto)
|
||||
|
||||
var r0 *publicdashboardsmodels.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, *publicdashboardsmodels.SavePublicDashboardConfigDTO) *publicdashboardsmodels.PublicDashboard); ok {
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardConfigDTO) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, u, dto)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*publicdashboardsmodels.PublicDashboard)
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, *publicdashboardsmodels.SavePublicDashboardConfigDTO) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardConfigDTO) error); ok {
|
||||
r1 = rf(ctx, u, dto)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
|
@ -18,8 +18,8 @@ type FakePublicDashboardStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AccessTokenExists provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakePublicDashboardStore) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
|
||||
// EnabledPublicDashboardExistsByAccessToken provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakePublicDashboardStore) PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
||||
var r0 bool
|
||||
@ -227,7 +227,7 @@ func (_m *FakePublicDashboardStore) ListPublicDashboards(ctx context.Context, or
|
||||
}
|
||||
|
||||
// PublicDashboardEnabled provides a mock function with given fields: ctx, dashboardUid
|
||||
func (_m *FakePublicDashboardStore) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
func (_m *FakePublicDashboardStore) PublicDashboardEnabledExistsByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
ret := _m.Called(ctx, dashboardUid)
|
||||
|
||||
var r0 bool
|
||||
|
@ -14,8 +14,7 @@ import (
|
||||
|
||||
//go:generate mockery --name Service --structname FakePublicDashboardService --inpackage --filename public_dashboard_service_mock.go
|
||||
type Service interface {
|
||||
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
||||
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) *user.SignedInUser
|
||||
PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error)
|
||||
GetAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, 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)
|
||||
@ -30,7 +29,8 @@ type Service interface {
|
||||
|
||||
//go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go
|
||||
type Store interface {
|
||||
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
||||
PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error)
|
||||
PublicDashboardEnabledExistsByDashboardUid(ctx context.Context, dashboardUid string) (bool, error)
|
||||
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
||||
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
||||
GenerateNewPublicDashboardAccessToken(ctx context.Context) (string, error)
|
||||
@ -39,7 +39,6 @@ type Store interface {
|
||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||
GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error)
|
||||
ListPublicDashboards(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error)
|
||||
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
|
||||
SavePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
||||
UpdatePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
)
|
||||
|
||||
func GetUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string {
|
||||
var datasourceUids []string
|
||||
exists := map[string]bool{}
|
||||
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
uid := GetDataSourceUidFromJson(panel)
|
||||
|
||||
// if uid is for a mixed datasource, get the datasource uids from the targets
|
||||
if uid == "-- Mixed --" {
|
||||
for _, target := range panel.Get("targets").MustArray() {
|
||||
target := simplejson.NewFromAny(target)
|
||||
datasourceUid := target.Get("datasource").Get("uid").MustString()
|
||||
if _, ok := exists[datasourceUid]; !ok {
|
||||
datasourceUids = append(datasourceUids, datasourceUid)
|
||||
exists[datasourceUid] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := exists[uid]; !ok {
|
||||
datasourceUids = append(datasourceUids, uid)
|
||||
exists[uid] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datasourceUids
|
||||
}
|
||||
|
||||
func GroupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.Json {
|
||||
result := make(map[int64][]*simplejson.Json)
|
||||
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
|
||||
var panelQueries []*simplejson.Json
|
||||
|
||||
for _, queryObj := range panel.Get("targets").MustArray() {
|
||||
query := simplejson.NewFromAny(queryObj)
|
||||
|
||||
if hideAttr, exists := query.CheckGet("hide"); !exists || !hideAttr.MustBool() {
|
||||
// We dont support exemplars for public dashboards currently
|
||||
query.Del("exemplar")
|
||||
|
||||
// if query target has no datasource, set it to have the datasource on the panel
|
||||
if _, ok := query.CheckGet("datasource"); !ok {
|
||||
uid := GetDataSourceUidFromJson(panel)
|
||||
datasource := map[string]interface{}{"type": "public-ds", "uid": uid}
|
||||
query.Set("datasource", datasource)
|
||||
}
|
||||
|
||||
panelQueries = append(panelQueries, query)
|
||||
}
|
||||
}
|
||||
|
||||
result[panel.Get("id").MustInt64()] = panelQueries
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func HasExpressionQuery(queries []*simplejson.Json) bool {
|
||||
for _, query := range queries {
|
||||
uid := GetDataSourceUidFromJson(query)
|
||||
if expr.IsDataSource(uid) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetDataSourceUidFromJson(query *simplejson.Json) string {
|
||||
uid := query.Get("datasource").Get("uid").MustString()
|
||||
|
||||
// before 8.3 special types could be sent as datasource (expr)
|
||||
if uid == "" {
|
||||
uid = query.Get("datasource").MustString()
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
|
||||
func SanitizeMetadataFromQueryData(res *backend.QueryDataResponse) {
|
||||
for k := range res.Responses {
|
||||
frames := res.Responses[k].Frames
|
||||
for i := range frames {
|
||||
if frames[i].Meta != nil {
|
||||
frames[i].Meta.ExecutedQueryString = ""
|
||||
frames[i].Meta.Custom = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,678 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
dashboardWithNoQueries = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithTargetsWithNoDatasources = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"datasource": {
|
||||
"type": "postgres",
|
||||
"uid": "abc123"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithQueriesExemplarEnabled = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithQueriesAndExpression = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"name": "Expression",
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__"
|
||||
},
|
||||
"expression": "$A + 1",
|
||||
"hide": false,
|
||||
"refId": "EXPRESSION",
|
||||
"type": "math"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithMixedDatasource = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Mixed --"
|
||||
},
|
||||
"id": 1,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "abc123"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"id": 3,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithDuplicateDatasources = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "abc123"
|
||||
},
|
||||
"id": 1,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "abc123"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"id": 3,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
oldStyleDashboard = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "_yxMP8Ynk",
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 21
|
||||
}`
|
||||
|
||||
dashboardWithOneHiddenQuery = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A",
|
||||
"hide": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
dashboardWithAllHiddenQueries = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"id": 2,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A",
|
||||
"hide": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B",
|
||||
"hide": true
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
)
|
||||
|
||||
func TestGetUniqueDashboardDatasourceUids(t *testing.T) {
|
||||
t.Run("can get unique datasource ids from dashboard", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithDuplicateDatasources))
|
||||
require.NoError(t, err)
|
||||
|
||||
uids := GetUniqueDashboardDatasourceUids(json)
|
||||
require.Len(t, uids, 2)
|
||||
require.Equal(t, "abc123", uids[0])
|
||||
require.Equal(t, "_yxMP8Ynk", uids[1])
|
||||
})
|
||||
|
||||
t.Run("can get unique datasource ids from dashboard with a mixed datasource", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithMixedDatasource))
|
||||
require.NoError(t, err)
|
||||
|
||||
uids := GetUniqueDashboardDatasourceUids(json)
|
||||
require.Len(t, uids, 2)
|
||||
require.Equal(t, "abc123", uids[0])
|
||||
require.Equal(t, "_yxMP8Ynk", uids[1])
|
||||
})
|
||||
|
||||
t.Run("can get no datasource uids from empty dashboard", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(`{"panels": {}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
uids := GetUniqueDashboardDatasourceUids(json)
|
||||
require.Len(t, uids, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHasExpressionQuery(t *testing.T) {
|
||||
t.Run("will return true when expression query exists", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithQueriesAndExpression))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
panelId := int64(2)
|
||||
require.True(t, HasExpressionQuery(queries[panelId]))
|
||||
})
|
||||
t.Run("will return false when no expression query exists", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithMixedDatasource))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
panelId := int64(2)
|
||||
require.False(t, HasExpressionQuery(queries[panelId]))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupQueriesByPanelId(t *testing.T) {
|
||||
t.Run("can extract queries from dashboard with panel datasource string that has no datasource on panel targets", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(oldStyleDashboard))
|
||||
require.NoError(t, err)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
|
||||
panelId := int64(2)
|
||||
queriesByDatasource := groupQueriesByDataSource(t, queries[panelId])
|
||||
require.Len(t, queriesByDatasource[0], 1)
|
||||
})
|
||||
t.Run("will delete exemplar property from target if exists", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithQueriesExemplarEnabled))
|
||||
require.NoError(t, err)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
|
||||
panelId := int64(2)
|
||||
queriesByDatasource := groupQueriesByDataSource(t, queries[panelId])
|
||||
for _, query := range queriesByDatasource[0] {
|
||||
_, ok := query.CheckGet("exemplar")
|
||||
require.False(t, ok)
|
||||
}
|
||||
})
|
||||
t.Run("can extract queries from dashboard with panel json datasource that has no datasource on panel targets", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithTargetsWithNoDatasources))
|
||||
require.NoError(t, err)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
|
||||
panelId := int64(2)
|
||||
queriesByDatasource := groupQueriesByDataSource(t, queries[panelId])
|
||||
require.Len(t, queriesByDatasource[0], 2)
|
||||
})
|
||||
t.Run("can extract no queries from empty dashboard", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(`{"panels": {}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 0)
|
||||
})
|
||||
|
||||
t.Run("can extract no queries from empty panel", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithNoQueries))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 0)
|
||||
})
|
||||
|
||||
t.Run("can extract queries from panels", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithQueriesExemplarEnabled))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 2)
|
||||
query, err := queries[2][0].MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}`, string(query))
|
||||
query, err = queries[2][1].MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}`, string(query))
|
||||
})
|
||||
|
||||
t.Run("can extract queries from old-style panels", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(oldStyleDashboard))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 1)
|
||||
query, err := queries[2][0].MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{
|
||||
"datasource": {
|
||||
"uid": "_yxMP8Ynk",
|
||||
"type": "public-ds"
|
||||
},
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}`, string(query))
|
||||
})
|
||||
|
||||
t.Run("hidden query filtered", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithOneHiddenQuery))
|
||||
require.NoError(t, err)
|
||||
queries := GroupQueriesByPanelId(json)[2]
|
||||
|
||||
require.Len(t, queries, 1)
|
||||
for _, query := range queries {
|
||||
if hideAttr, exists := query.CheckGet("hide"); exists && hideAttr.MustBool() {
|
||||
require.Fail(t, "hidden queries should have been filtered")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hidden query filtered, so empty queries returned", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithAllHiddenQueries))
|
||||
require.NoError(t, err)
|
||||
queries := GroupQueriesByPanelId(json)[2]
|
||||
|
||||
require.Len(t, queries, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupQueriesByDataSource(t *testing.T) {
|
||||
t.Run("can divide queries by datasource", func(t *testing.T) {
|
||||
queries := []*simplejson.Json{
|
||||
simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}`)),
|
||||
simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}`)),
|
||||
}
|
||||
|
||||
queriesByDatasource := groupQueriesByDataSource(t, queries)
|
||||
require.Len(t, queriesByDatasource, 2)
|
||||
require.Contains(t, queriesByDatasource, []*simplejson.Json{simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "_yxMP8Ynk"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "go_goroutines{job=\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}`))})
|
||||
require.Contains(t, queriesByDatasource, []*simplejson.Json{simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "promds2"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "query2",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}`))})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSanitizeMetadataFromQueryData(t *testing.T) {
|
||||
t.Run("can remove metadata from query", func(t *testing.T) {
|
||||
fakeResponse := &backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": backend.DataResponse{
|
||||
Frames: data.Frames{
|
||||
&data.Frame{
|
||||
Name: "1",
|
||||
Meta: &data.FrameMeta{
|
||||
ExecutedQueryString: "Test1",
|
||||
Custom: map[string]string{
|
||||
"test1": "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
&data.Frame{
|
||||
Name: "2",
|
||||
Meta: &data.FrameMeta{
|
||||
ExecutedQueryString: "Test2",
|
||||
Custom: map[string]string{
|
||||
"test2": "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"B": backend.DataResponse{
|
||||
Frames: data.Frames{
|
||||
&data.Frame{
|
||||
Name: "3",
|
||||
Meta: &data.FrameMeta{
|
||||
ExecutedQueryString: "Test3",
|
||||
Custom: map[string]string{
|
||||
"test3": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
SanitizeMetadataFromQueryData(fakeResponse)
|
||||
for k := range fakeResponse.Responses {
|
||||
frames := fakeResponse.Responses[k].Frames
|
||||
for i := range frames {
|
||||
require.Empty(t, frames[i].Meta.ExecutedQueryString)
|
||||
require.Empty(t, frames[i].Meta.Custom)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func groupQueriesByDataSource(t *testing.T, queries []*simplejson.Json) (result [][]*simplejson.Json) {
|
||||
t.Helper()
|
||||
byDataSource := make(map[string][]*simplejson.Json)
|
||||
|
||||
for _, query := range queries {
|
||||
uid := GetDataSourceUidFromJson(query)
|
||||
byDataSource[uid] = append(byDataSource[uid], query)
|
||||
}
|
||||
|
||||
for _, queries := range byDataSource {
|
||||
result = append(result, queries)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
288
pkg/services/publicdashboards/service/query.go
Normal file
288
pkg/services/publicdashboards/service/query.go
Normal file
@ -0,0 +1,288 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
dashmodels "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
)
|
||||
|
||||
// GetAnnotations returns annotations for a public dashboard
|
||||
func (pd *PublicDashboardServiceImpl) GetAnnotations(ctx context.Context, reqDTO models.AnnotationsQueryDTO, accessToken string) ([]models.AnnotationEvent, error) {
|
||||
_, dash, err := pd.GetPublicDashboard(ctx, accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annoDto, err := UnmarshalDashboardAnnotations(dash.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anonymousUser := buildAnonymousUser(ctx, dash)
|
||||
|
||||
uniqueEvents := make(map[int64]models.AnnotationEvent, 0)
|
||||
for _, anno := range annoDto.Annotations.List {
|
||||
// skip annotations that are not enabled or are not a grafana datasource
|
||||
if !anno.Enable || (*anno.Datasource.Uid != grafanads.DatasourceUID && *anno.Datasource.Uid != grafanads.DatasourceName) {
|
||||
continue
|
||||
}
|
||||
annoQuery := &annotations.ItemQuery{
|
||||
From: reqDTO.From,
|
||||
To: reqDTO.To,
|
||||
OrgId: dash.OrgId,
|
||||
DashboardId: dash.Id,
|
||||
DashboardUid: dash.Uid,
|
||||
Limit: anno.Target.Limit,
|
||||
MatchAny: anno.Target.MatchAny,
|
||||
SignedInUser: anonymousUser,
|
||||
}
|
||||
|
||||
if anno.Target.Type == "tags" {
|
||||
annoQuery.DashboardId = 0
|
||||
annoQuery.Tags = anno.Target.Tags
|
||||
}
|
||||
|
||||
annotationItems, err := pd.AnnotationsRepo.Find(ctx, annoQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range annotationItems {
|
||||
event := models.AnnotationEvent{
|
||||
Id: item.Id,
|
||||
DashboardId: item.DashboardId,
|
||||
Tags: item.Tags,
|
||||
IsRegion: item.TimeEnd > 0 && item.Time != item.TimeEnd,
|
||||
Text: item.Text,
|
||||
Color: *anno.IconColor,
|
||||
Time: item.Time,
|
||||
TimeEnd: item.TimeEnd,
|
||||
Source: anno,
|
||||
}
|
||||
|
||||
// We want dashboard annotations to reference the panel they're for. If no panelId is provided, they'll show up on all panels
|
||||
// which is only intended for tag and org annotations.
|
||||
if anno.Type == "dashboard" {
|
||||
event.PanelId = item.PanelId
|
||||
}
|
||||
|
||||
// We want events from tag queries to overwrite existing events
|
||||
_, has := uniqueEvents[event.Id]
|
||||
if !has || (has && anno.Target.Type == "tags") {
|
||||
uniqueEvents[event.Id] = event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var results []models.AnnotationEvent
|
||||
for _, result := range uniqueEvents {
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
err := validation.ValidateQueryPublicDashboardRequest(queryDto)
|
||||
if err != nil {
|
||||
return dtos.MetricRequest{}, err
|
||||
}
|
||||
|
||||
metricReqDTO, err := pd.buildMetricRequest(
|
||||
ctx,
|
||||
dashboard,
|
||||
publicDashboard,
|
||||
panelId,
|
||||
queryDto,
|
||||
)
|
||||
if err != nil {
|
||||
return dtos.MetricRequest{}, err
|
||||
}
|
||||
|
||||
return metricReqDTO, nil
|
||||
}
|
||||
|
||||
// GetQueryDataResponse returns a query data response for the given panel and query
|
||||
func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, skipCache bool, queryDto models.PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
||||
publicDashboard, dashboard, err := pd.GetPublicDashboard(ctx, accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metricReq, err := pd.GetMetricRequest(ctx, dashboard, publicDashboard, panelId, queryDto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(metricReq.Queries) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
anonymousUser := buildAnonymousUser(ctx, dashboard)
|
||||
res, err := pd.QueryDataService.QueryData(ctx, anonymousUser, skipCache, metricReq)
|
||||
|
||||
reqDatasources := metricReq.GetUniqueDatasourceTypes()
|
||||
if err != nil {
|
||||
LogQueryFailure(reqDatasources, pd.log, err)
|
||||
return nil, err
|
||||
}
|
||||
LogQuerySuccess(reqDatasources, pd.log)
|
||||
|
||||
sanitizeMetadataFromQueryData(res)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// buildMetricRequest merges public dashboard parameters with dashboard and returns a metrics request to be sent to query backend
|
||||
func (pd *PublicDashboardServiceImpl) buildMetricRequest(ctx context.Context, dashboard *dashmodels.Dashboard, publicDashboard *models.PublicDashboard, panelId int64, reqDTO models.PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||
// group queries by panel
|
||||
queriesByPanel := groupQueriesByPanelId(dashboard.Data)
|
||||
queries, ok := queriesByPanel[panelId]
|
||||
if !ok {
|
||||
return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound
|
||||
}
|
||||
|
||||
ts := publicDashboard.BuildTimeSettings(dashboard)
|
||||
|
||||
// determine safe resolution to query data at
|
||||
safeInterval, safeResolution := pd.getSafeIntervalAndMaxDataPoints(reqDTO, ts)
|
||||
for i := range queries {
|
||||
queries[i].Set("intervalMs", safeInterval)
|
||||
queries[i].Set("maxDataPoints", safeResolution)
|
||||
}
|
||||
|
||||
return dtos.MetricRequest{
|
||||
From: ts.From,
|
||||
To: ts.To,
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func buildAnonymousUser(ctx context.Context, dashboard *dashmodels.Dashboard) *user.SignedInUser {
|
||||
datasourceUids := getUniqueDashboardDatasourceUids(dashboard.Data)
|
||||
|
||||
// Create a user with blank permissions
|
||||
anonymousUser := &user.SignedInUser{OrgID: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
||||
|
||||
// Scopes needed for Annotation queries
|
||||
annotationScopes := []string{accesscontrol.ScopeAnnotationsTypeDashboard}
|
||||
// Need to access all dashboards since tags annotations span across all dashboards
|
||||
dashboardScopes := []string{dashboards.ScopeDashboardsProvider.GetResourceAllScope()}
|
||||
|
||||
// Scopes needed for datasource queries
|
||||
queryScopes := make([]string, 0)
|
||||
readScopes := make([]string, 0)
|
||||
for _, uid := range datasourceUids {
|
||||
scope := datasources.ScopeProvider.GetResourceScopeUID(uid)
|
||||
queryScopes = append(queryScopes, scope)
|
||||
readScopes = append(readScopes, scope)
|
||||
}
|
||||
|
||||
// Apply all scopes to the actions we need the user to be able to perform
|
||||
permissions := make(map[string][]string)
|
||||
permissions[datasources.ActionQuery] = queryScopes
|
||||
permissions[datasources.ActionRead] = readScopes
|
||||
permissions[accesscontrol.ActionAnnotationsRead] = annotationScopes
|
||||
permissions[dashboards.ActionDashboardsRead] = dashboardScopes
|
||||
|
||||
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||
|
||||
return anonymousUser
|
||||
}
|
||||
|
||||
func getUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string {
|
||||
var datasourceUids []string
|
||||
exists := map[string]bool{}
|
||||
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
uid := getDataSourceUidFromJson(panel)
|
||||
|
||||
// if uid is for a mixed datasource, get the datasource uids from the targets
|
||||
if uid == "-- Mixed --" {
|
||||
for _, target := range panel.Get("targets").MustArray() {
|
||||
target := simplejson.NewFromAny(target)
|
||||
datasourceUid := target.Get("datasource").Get("uid").MustString()
|
||||
if _, ok := exists[datasourceUid]; !ok {
|
||||
datasourceUids = append(datasourceUids, datasourceUid)
|
||||
exists[datasourceUid] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := exists[uid]; !ok {
|
||||
datasourceUids = append(datasourceUids, uid)
|
||||
exists[uid] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datasourceUids
|
||||
}
|
||||
|
||||
func groupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.Json {
|
||||
result := make(map[int64][]*simplejson.Json)
|
||||
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
|
||||
var panelQueries []*simplejson.Json
|
||||
|
||||
for _, queryObj := range panel.Get("targets").MustArray() {
|
||||
query := simplejson.NewFromAny(queryObj)
|
||||
|
||||
if hideAttr, exists := query.CheckGet("hide"); !exists || !hideAttr.MustBool() {
|
||||
// We dont support exemplars for public dashboards currently
|
||||
query.Del("exemplar")
|
||||
|
||||
// if query target has no datasource, set it to have the datasource on the panel
|
||||
if _, ok := query.CheckGet("datasource"); !ok {
|
||||
uid := getDataSourceUidFromJson(panel)
|
||||
datasource := map[string]interface{}{"type": "public-ds", "uid": uid}
|
||||
query.Set("datasource", datasource)
|
||||
}
|
||||
|
||||
panelQueries = append(panelQueries, query)
|
||||
}
|
||||
}
|
||||
|
||||
result[panel.Get("id").MustInt64()] = panelQueries
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getDataSourceUidFromJson(query *simplejson.Json) string {
|
||||
uid := query.Get("datasource").Get("uid").MustString()
|
||||
|
||||
// before 8.3 special types could be sent as datasource (expr)
|
||||
if uid == "" {
|
||||
uid = query.Get("datasource").MustString()
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
|
||||
func sanitizeMetadataFromQueryData(res *backend.QueryDataResponse) {
|
||||
for k := range res.Responses {
|
||||
frames := res.Responses[k].Frames
|
||||
for i := range frames {
|
||||
if frames[i].Meta != nil {
|
||||
frames[i].Meta.ExecutedQueryString = ""
|
||||
frames[i].Meta.Custom = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1252
pkg/services/publicdashboards/service/query_test.go
Normal file
1252
pkg/services/publicdashboards/service/query_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,21 +6,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/queries"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
)
|
||||
@ -207,190 +203,6 @@ func (pd *PublicDashboardServiceImpl) updatePublicDashboardConfig(ctx context.Co
|
||||
return dto.PublicDashboard.Uid, pd.store.UpdatePublicDashboardConfig(ctx, cmd)
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, skipCache bool, queryDto PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
||||
publicDashboard, dashboard, err := pd.GetPublicDashboard(ctx, accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metricReq, err := pd.GetMetricRequest(ctx, dashboard, publicDashboard, panelId, queryDto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(metricReq.Queries) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
anonymousUser := pd.BuildAnonymousUser(ctx, dashboard)
|
||||
res, err := pd.QueryDataService.QueryData(ctx, anonymousUser, skipCache, metricReq)
|
||||
|
||||
reqDatasources := metricReq.GetUniqueDatasourceTypes()
|
||||
if err != nil {
|
||||
LogQueryFailure(reqDatasources, pd.log, err)
|
||||
return nil, err
|
||||
}
|
||||
LogQuerySuccess(reqDatasources, pd.log)
|
||||
|
||||
queries.SanitizeMetadataFromQueryData(res)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||
err := validation.ValidateQueryPublicDashboardRequest(queryDto)
|
||||
if err != nil {
|
||||
return dtos.MetricRequest{}, err
|
||||
}
|
||||
|
||||
metricReqDTO, err := pd.buildMetricRequest(
|
||||
ctx,
|
||||
dashboard,
|
||||
publicDashboard,
|
||||
panelId,
|
||||
queryDto,
|
||||
)
|
||||
if err != nil {
|
||||
return dtos.MetricRequest{}, err
|
||||
}
|
||||
|
||||
return metricReqDTO, nil
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) GetAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error) {
|
||||
_, dash, err := pd.GetPublicDashboard(ctx, accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annoDto, err := UnmarshalDashboardAnnotations(dash.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anonymousUser := pd.BuildAnonymousUser(ctx, dash)
|
||||
|
||||
uniqueEvents := make(map[int64]AnnotationEvent, 0)
|
||||
for _, anno := range annoDto.Annotations.List {
|
||||
// skip annotations that are not enabled or are not a grafana datasource
|
||||
if !anno.Enable || (*anno.Datasource.Uid != grafanads.DatasourceUID && *anno.Datasource.Uid != grafanads.DatasourceName) {
|
||||
continue
|
||||
}
|
||||
annoQuery := &annotations.ItemQuery{
|
||||
From: reqDTO.From,
|
||||
To: reqDTO.To,
|
||||
OrgId: dash.OrgId,
|
||||
DashboardId: dash.Id,
|
||||
DashboardUid: dash.Uid,
|
||||
Limit: anno.Target.Limit,
|
||||
MatchAny: anno.Target.MatchAny,
|
||||
SignedInUser: anonymousUser,
|
||||
}
|
||||
|
||||
if anno.Target.Type == "tags" {
|
||||
annoQuery.DashboardId = 0
|
||||
annoQuery.Tags = anno.Target.Tags
|
||||
}
|
||||
|
||||
annotationItems, err := pd.AnnotationsRepo.Find(ctx, annoQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range annotationItems {
|
||||
event := AnnotationEvent{
|
||||
Id: item.Id,
|
||||
DashboardId: item.DashboardId,
|
||||
Tags: item.Tags,
|
||||
IsRegion: item.TimeEnd > 0 && item.Time != item.TimeEnd,
|
||||
Text: item.Text,
|
||||
Color: *anno.IconColor,
|
||||
Time: item.Time,
|
||||
TimeEnd: item.TimeEnd,
|
||||
Source: anno,
|
||||
}
|
||||
|
||||
// We want dashboard annotations to reference the panel they're for. If no panelId is provided, they'll show up on all panels
|
||||
// which is only intended for tag and org annotations.
|
||||
if anno.Type == "dashboard" {
|
||||
event.PanelId = item.PanelId
|
||||
}
|
||||
|
||||
// We want events from tag queries to overwrite existing events
|
||||
_, has := uniqueEvents[event.Id]
|
||||
if !has || (has && anno.Target.Type == "tags") {
|
||||
uniqueEvents[event.Id] = event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var results []AnnotationEvent
|
||||
for _, result := range uniqueEvents {
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// buildMetricRequest merges public dashboard parameters with
|
||||
// dashboard and returns a metrics request to be sent to query backend
|
||||
func (pd *PublicDashboardServiceImpl) buildMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
||||
// group queries by panel
|
||||
queriesByPanel := queries.GroupQueriesByPanelId(dashboard.Data)
|
||||
queries, ok := queriesByPanel[panelId]
|
||||
if !ok {
|
||||
return dtos.MetricRequest{}, ErrPublicDashboardPanelNotFound
|
||||
}
|
||||
|
||||
ts := publicDashboard.BuildTimeSettings(dashboard)
|
||||
|
||||
// determine safe resolution to query data at
|
||||
safeInterval, safeResolution := pd.getSafeIntervalAndMaxDataPoints(reqDTO, ts)
|
||||
for i := range queries {
|
||||
queries[i].Set("intervalMs", safeInterval)
|
||||
queries[i].Set("maxDataPoints", safeResolution)
|
||||
}
|
||||
|
||||
return dtos.MetricRequest{
|
||||
From: ts.From,
|
||||
To: ts.To,
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func (pd *PublicDashboardServiceImpl) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) *user.SignedInUser {
|
||||
datasourceUids := queries.GetUniqueDashboardDatasourceUids(dashboard.Data)
|
||||
|
||||
// Create a user with blank permissions
|
||||
anonymousUser := &user.SignedInUser{OrgID: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
||||
|
||||
// Scopes needed for Annotation queries
|
||||
annotationScopes := []string{accesscontrol.ScopeAnnotationsTypeDashboard}
|
||||
// Need to access all dashboards since tags annotations span across all dashboards
|
||||
dashboardScopes := []string{dashboards.ScopeDashboardsProvider.GetResourceAllScope()}
|
||||
|
||||
// Scopes needed for datasource queries
|
||||
queryScopes := make([]string, 0)
|
||||
readScopes := make([]string, 0)
|
||||
for _, uid := range datasourceUids {
|
||||
scope := datasources.ScopeProvider.GetResourceScopeUID(uid)
|
||||
queryScopes = append(queryScopes, scope)
|
||||
readScopes = append(readScopes, scope)
|
||||
}
|
||||
|
||||
// Apply all scopes to the actions we need the user to be able to perform
|
||||
permissions := make(map[string][]string)
|
||||
permissions[datasources.ActionQuery] = queryScopes
|
||||
permissions[datasources.ActionRead] = readScopes
|
||||
permissions[accesscontrol.ActionAnnotationsRead] = annotationScopes
|
||||
permissions[dashboards.ActionDashboardsRead] = dashboardScopes
|
||||
|
||||
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||
|
||||
return anonymousUser
|
||||
}
|
||||
|
||||
// Gets a list of public dashboards by orgId
|
||||
func (pd *PublicDashboardServiceImpl) ListPublicDashboards(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error) {
|
||||
publicDashboards, err := pd.store.ListPublicDashboards(ctx, orgId)
|
||||
@ -402,11 +214,11 @@ func (pd *PublicDashboardServiceImpl) ListPublicDashboards(ctx context.Context,
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
return pd.store.PublicDashboardEnabled(ctx, dashboardUid)
|
||||
return pd.store.PublicDashboardEnabledExistsByDashboardUid(ctx, dashboardUid)
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
|
||||
return pd.store.AccessTokenExists(ctx, accessToken)
|
||||
func (pd *PublicDashboardServiceImpl) PublicDashboardEnabledExistsByAccessToken(ctx context.Context, accessToken string) (bool, error) {
|
||||
return pd.store.PublicDashboardEnabledExistsByAccessToken(ctx, accessToken)
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
|
||||
|
@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@ -14,25 +13,19 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
dashboard2 "github.com/grafana/grafana/pkg/coremodel/dashboard"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/internal"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
)
|
||||
|
||||
@ -45,289 +38,6 @@ func TestLogPrefix(t *testing.T) {
|
||||
assert.Equal(t, LogPrefix, "publicdashboards.service")
|
||||
}
|
||||
|
||||
func TestGetAnnotations(t *testing.T) {
|
||||
t.Run("will build anonymous user with correct permissions to get annotations", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
config := setting.NewCfg()
|
||||
tagService := tagimpl.ProvideService(sqlStore, sqlStore.Cfg)
|
||||
annotationsRepo := annotationsimpl.ProvideService(sqlStore, config, tagService)
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: annotationsRepo,
|
||||
}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).
|
||||
Return(&PublicDashboard{Uid: "uid1", IsEnabled: true}, models.NewDashboard("dash1"), nil)
|
||||
reqDTO := AnnotationsQueryDTO{
|
||||
From: 1,
|
||||
To: 2,
|
||||
}
|
||||
dash := models.NewDashboard("testDashboard")
|
||||
|
||||
items, _ := service.GetAnnotations(context.Background(), reqDTO, "abc123")
|
||||
anonUser := service.BuildAnonymousUser(context.Background(), dash)
|
||||
|
||||
assert.Equal(t, "dashboards:*", anonUser.Permissions[0]["dashboards:read"][0])
|
||||
assert.Len(t, items, 0)
|
||||
})
|
||||
|
||||
t.Run("Test events from tag queries overwrite built-in annotation queries and duplicate events are not returned", func(t *testing.T) {
|
||||
dash := models.NewDashboard("test")
|
||||
color := "red"
|
||||
name := "annoName"
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: nil,
|
||||
Type: "dashboard",
|
||||
},
|
||||
Type: "dashboard",
|
||||
}
|
||||
grafanaTagAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: []string{"tag1"},
|
||||
Type: "tags",
|
||||
},
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation, grafanaTagAnnotation}
|
||||
dashboard := AddAnnotationsToDashboard(t, dash, annos)
|
||||
|
||||
annotationsRepo := annotations.FakeAnnotationsRepo{}
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: &annotationsRepo,
|
||||
}
|
||||
pubdash := &PublicDashboard{Uid: "uid1", IsEnabled: true, OrgId: 1, DashboardUid: dashboard.Uid}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).Return(pubdash, dashboard, nil)
|
||||
annotationsRepo.On("Find", mock.Anything, mock.Anything).Return([]*annotations.ItemDTO{
|
||||
{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
Tags: []string{"tag1"},
|
||||
TimeEnd: 2,
|
||||
Time: 2,
|
||||
Text: "text",
|
||||
},
|
||||
}, nil).Maybe()
|
||||
|
||||
items, err := service.GetAnnotations(context.Background(), AnnotationsQueryDTO{}, "abc123")
|
||||
|
||||
expected := AnnotationEvent{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 0,
|
||||
Tags: []string{"tag1"},
|
||||
IsRegion: false,
|
||||
Text: "text",
|
||||
Color: color,
|
||||
Time: 2,
|
||||
TimeEnd: 2,
|
||||
Source: grafanaTagAnnotation,
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, items, 1)
|
||||
assert.Equal(t, expected, items[0])
|
||||
})
|
||||
|
||||
t.Run("Test panelId set to zero when annotation event is for a tags query", func(t *testing.T) {
|
||||
dash := models.NewDashboard("test")
|
||||
color := "red"
|
||||
name := "annoName"
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: []string{"tag1"},
|
||||
Type: "tags",
|
||||
},
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation}
|
||||
dashboard := AddAnnotationsToDashboard(t, dash, annos)
|
||||
|
||||
annotationsRepo := annotations.FakeAnnotationsRepo{}
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: &annotationsRepo,
|
||||
}
|
||||
pubdash := &PublicDashboard{Uid: "uid1", IsEnabled: true, OrgId: 1, DashboardUid: dashboard.Uid}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).Return(pubdash, dashboard, nil)
|
||||
annotationsRepo.On("Find", mock.Anything, mock.Anything).Return([]*annotations.ItemDTO{
|
||||
{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
Tags: []string{},
|
||||
TimeEnd: 1,
|
||||
Time: 2,
|
||||
Text: "text",
|
||||
},
|
||||
}, nil).Maybe()
|
||||
|
||||
items, err := service.GetAnnotations(context.Background(), AnnotationsQueryDTO{}, "abc123")
|
||||
|
||||
expected := AnnotationEvent{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 0,
|
||||
Tags: []string{},
|
||||
IsRegion: true,
|
||||
Text: "text",
|
||||
Color: color,
|
||||
Time: 2,
|
||||
TimeEnd: 1,
|
||||
Source: grafanaAnnotation,
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, items, 1)
|
||||
assert.Equal(t, expected, items[0])
|
||||
})
|
||||
|
||||
t.Run("Test can get grafana annotations and will skip annotation queries and disabled annotations", func(t *testing.T) {
|
||||
dash := models.NewDashboard("test")
|
||||
color := "red"
|
||||
name := "annoName"
|
||||
disabledGrafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: false,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
}
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: true,
|
||||
Tags: nil,
|
||||
Type: "dashboard",
|
||||
},
|
||||
Type: "dashboard",
|
||||
}
|
||||
queryAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("prometheus", "abc123"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation, queryAnnotation, disabledGrafanaAnnotation}
|
||||
dashboard := AddAnnotationsToDashboard(t, dash, annos)
|
||||
|
||||
annotationsRepo := annotations.FakeAnnotationsRepo{}
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: &annotationsRepo,
|
||||
}
|
||||
pubdash := &PublicDashboard{Uid: "uid1", IsEnabled: true, OrgId: 1, DashboardUid: dashboard.Uid}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).Return(pubdash, dashboard, nil)
|
||||
annotationsRepo.On("Find", mock.Anything, mock.Anything).Return([]*annotations.ItemDTO{
|
||||
{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
Tags: []string{},
|
||||
TimeEnd: 1,
|
||||
Time: 2,
|
||||
Text: "text",
|
||||
},
|
||||
}, nil).Maybe()
|
||||
|
||||
items, err := service.GetAnnotations(context.Background(), AnnotationsQueryDTO{}, "abc123")
|
||||
|
||||
expected := AnnotationEvent{
|
||||
Id: 1,
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
Tags: []string{},
|
||||
IsRegion: true,
|
||||
Text: "text",
|
||||
Color: color,
|
||||
Time: 2,
|
||||
TimeEnd: 1,
|
||||
Source: grafanaAnnotation,
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, items, 1)
|
||||
assert.Equal(t, expected, items[0])
|
||||
})
|
||||
|
||||
t.Run("test will return nothing when dashboard has no annotations", func(t *testing.T) {
|
||||
annotationsRepo := annotations.FakeAnnotationsRepo{}
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: &annotationsRepo,
|
||||
}
|
||||
dashboard := models.NewDashboard("dashWithNoAnnotations")
|
||||
pubdash := &PublicDashboard{Uid: "uid1", IsEnabled: true, OrgId: 1, DashboardUid: dashboard.Uid}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).Return(pubdash, dashboard, nil)
|
||||
|
||||
items, err := service.GetAnnotations(context.Background(), AnnotationsQueryDTO{}, "abc123")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, items)
|
||||
})
|
||||
|
||||
t.Run("test will error when annotations repo returns an error", func(t *testing.T) {
|
||||
annotationsRepo := annotations.FakeAnnotationsRepo{}
|
||||
fakeStore := FakePublicDashboardStore{}
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: &fakeStore,
|
||||
AnnotationsRepo: &annotationsRepo,
|
||||
}
|
||||
dash := models.NewDashboard("test")
|
||||
color := "red"
|
||||
name := "annoName"
|
||||
grafanaAnnotation := DashAnnotation{
|
||||
Datasource: CreateDatasource("grafana", "grafana"),
|
||||
Enable: true,
|
||||
Name: &name,
|
||||
IconColor: &color,
|
||||
Target: &dashboard2.AnnotationTarget{
|
||||
Limit: 100,
|
||||
MatchAny: false,
|
||||
Tags: []string{"tag1"},
|
||||
Type: "tags",
|
||||
},
|
||||
}
|
||||
annos := []DashAnnotation{grafanaAnnotation}
|
||||
dash = AddAnnotationsToDashboard(t, dash, annos)
|
||||
pubdash := &PublicDashboard{Uid: "uid1", IsEnabled: true, OrgId: 1, DashboardUid: dash.Uid}
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).Return(pubdash, dash, nil)
|
||||
annotationsRepo.On("Find", mock.Anything, mock.Anything).Return(nil, errors.New("failed")).Maybe()
|
||||
|
||||
items, err := service.GetAnnotations(context.Background(), AnnotationsQueryDTO{}, "abc123")
|
||||
|
||||
require.Error(t, err)
|
||||
require.Nil(t, items)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPublicDashboard(t *testing.T) {
|
||||
type storeResp struct {
|
||||
pd *PublicDashboard
|
||||
@ -655,342 +365,6 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: publicdashboardStore,
|
||||
}
|
||||
|
||||
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
|
||||
user := service.BuildAnonymousUser(context.Background(), dashboard)
|
||||
|
||||
require.Equal(t, dashboard.OrgId, user.OrgID)
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgID]["datasources:query"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgID]["datasources:query"][1])
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgID]["datasources:read"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgID]["datasources:read"][1])
|
||||
})
|
||||
t.Run("will add dashboard and annotation permissions needed for getting annotations", func(t *testing.T) {
|
||||
user := service.BuildAnonymousUser(context.Background(), dashboard)
|
||||
|
||||
require.Equal(t, dashboard.OrgId, user.OrgID)
|
||||
require.Equal(t, "annotations:type:dashboard", user.Permissions[user.OrgID]["annotations:read"][0])
|
||||
require.Equal(t, "dashboards:*", user.Permissions[user.OrgID]["dashboards:read"][0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
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 TestGetQueryDataResponse(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: publicdashboardStore,
|
||||
intervalCalculator: intervalv2.NewCalculator(),
|
||||
}
|
||||
|
||||
publicDashboardQueryDTO := PublicDashboardQueryDTO{
|
||||
IntervalMs: int64(1),
|
||||
MaxDataPoints: int64(1),
|
||||
}
|
||||
|
||||
t.Run("Returns nil when query is hidden", func(t *testing.T) {
|
||||
hiddenQuery := map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"hide": true,
|
||||
"refId": "A",
|
||||
}
|
||||
customPanels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{hiddenQuery},
|
||||
}}
|
||||
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels)
|
||||
dto := &SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &PublicDashboard{
|
||||
IsEnabled: true,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
pubdashDto, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, _ := service.GetQueryDataResponse(context.Background(), true, publicDashboardQueryDTO, 1, pubdashDto.AccessToken)
|
||||
require.Nil(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
from, to := internal.GetTimeRangeFromDashboard(t, publicDashboard.Data)
|
||||
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: publicdashboardStore,
|
||||
intervalCalculator: intervalv2.NewCalculator(),
|
||||
}
|
||||
|
||||
publicDashboardQueryDTO := PublicDashboardQueryDTO{
|
||||
IntervalMs: int64(10000000),
|
||||
MaxDataPoints: int64(200),
|
||||
}
|
||||
|
||||
dto := &SavePublicDashboardConfigDTO{
|
||||
DashboardUid: publicDashboard.Uid,
|
||||
OrgId: publicDashboard.OrgId,
|
||||
PublicDashboard: &PublicDashboard{
|
||||
IsEnabled: true,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
publicDashboardPD, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
nonPublicDto := &SavePublicDashboardConfigDTO{
|
||||
DashboardUid: nonPublicDashboard.Uid,
|
||||
OrgId: nonPublicDashboard.OrgId,
|
||||
PublicDashboard: &PublicDashboard{
|
||||
IsEnabled: false,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: defaultPubdashTimeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = service.SavePublicDashboardConfig(context.Background(), SignedInUser, nonPublicDto)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
||||
reqDTO, err := service.buildMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
1,
|
||||
publicDashboardQueryDTO,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, from, reqDTO.From)
|
||||
require.Equal(t, to, reqDTO.To)
|
||||
|
||||
for i := range reqDTO.Queries {
|
||||
require.Equal(t, publicDashboardQueryDTO.IntervalMs, reqDTO.Queries[i].Get("intervalMs").MustInt64())
|
||||
require.Equal(t, publicDashboardQueryDTO.MaxDataPoints, reqDTO.Queries[i].Get("maxDataPoints").MustInt64())
|
||||
}
|
||||
|
||||
require.Len(t, reqDTO.Queries, 2)
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
simplejson.NewFromAny(map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"intervalMs": int64(10000000),
|
||||
"maxDataPoints": int64(200),
|
||||
"refId": "A",
|
||||
}),
|
||||
reqDTO.Queries[0],
|
||||
)
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
simplejson.NewFromAny(map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"intervalMs": int64(10000000),
|
||||
"maxDataPoints": int64(200),
|
||||
"refId": "B",
|
||||
}),
|
||||
reqDTO.Queries[1],
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("returns an error when panel missing", func(t *testing.T) {
|
||||
_, err := service.buildMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
49,
|
||||
publicDashboardQueryDTO,
|
||||
)
|
||||
|
||||
require.ErrorContains(t, err, ErrPublicDashboardPanelNotFound.Reason)
|
||||
})
|
||||
|
||||
t.Run("metric request built without hidden query", func(t *testing.T) {
|
||||
hiddenQuery := map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"hide": true,
|
||||
"refId": "A",
|
||||
}
|
||||
nonHiddenQuery := map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"refId": "B",
|
||||
}
|
||||
|
||||
customPanels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{hiddenQuery, nonHiddenQuery},
|
||||
}}
|
||||
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels)
|
||||
|
||||
reqDTO, err := service.buildMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
1,
|
||||
publicDashboardQueryDTO,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, from, reqDTO.From)
|
||||
require.Equal(t, to, reqDTO.To)
|
||||
|
||||
for i := range reqDTO.Queries {
|
||||
require.Equal(t, publicDashboardQueryDTO.IntervalMs, reqDTO.Queries[i].Get("intervalMs").MustInt64())
|
||||
require.Equal(t, publicDashboardQueryDTO.MaxDataPoints, reqDTO.Queries[i].Get("maxDataPoints").MustInt64())
|
||||
}
|
||||
|
||||
require.Len(t, reqDTO.Queries, 1)
|
||||
|
||||
require.NotEqual(
|
||||
t,
|
||||
simplejson.NewFromAny(hiddenQuery),
|
||||
reqDTO.Queries[0],
|
||||
)
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
simplejson.NewFromAny(nonHiddenQuery),
|
||||
reqDTO.Queries[0],
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("metric request built with 0 queries len when all queries are hidden", func(t *testing.T) {
|
||||
customPanels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"hide": true,
|
||||
"refId": "A",
|
||||
}, map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"hide": true,
|
||||
"refId": "B",
|
||||
}},
|
||||
}}
|
||||
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithAllQueriesHidden", 1, 0, true, []map[string]interface{}{}, customPanels)
|
||||
|
||||
reqDTO, err := service.buildMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
1,
|
||||
publicDashboardQueryDTO,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, from, reqDTO.From)
|
||||
require.Equal(t, to, reqDTO.To)
|
||||
|
||||
require.Len(t, reqDTO.Queries, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardStore, title string, orgId int64,
|
||||
folderId int64, isFolder bool, templateVars []map[string]interface{}, customPanels []interface{}, tags ...interface{}) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
Loading…
Reference in New Issue
Block a user