Public Dashboards: Query Caching (#51403)
* passes id and uid to PublicDashboardDatasource * betterer results * If for a public dashboard, return the PublicDashboardDataSource first or else getDatasourceSrv.get() will fail bc of no authed user. Added some unit tests for resolving the uid from the many possible datasource types. * updates betterer * Exports DashboardService. Adds method to DashboardService to build anonymous user for use with public dashboards where there is no authed user. Adds method on dashboard_queries to get all dashboard uids from a dashboard. * refactors to get unique datasource uids * Adds tests for getting all unique datasource uids off a dashboard * adds test for building anonymous user with read and query actions that are scoped to each datasource uid in the dashboard * updates casing of DashboardService * updates test case to have additional panel with a different datasource * gives default interval to public dashboard data source
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
// DashboardService is a service for operating on dashboards.
|
||||
type DashboardService interface {
|
||||
BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error)
|
||||
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error)
|
||||
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
|
||||
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
||||
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
||||
|
||||
@@ -18,6 +18,29 @@ type FakeDashboardService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// BuildAnonymousUser provides a mock function with given fields: ctx, dashboard
|
||||
func (_m *FakeDashboardService) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
ret := _m.Called(ctx, dashboard)
|
||||
|
||||
var r0 *models.SignedInUser
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard) *models.SignedInUser); ok {
|
||||
r0 = rf(ctx, dashboard)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard) error); ok {
|
||||
r1 = rf(ctx, dashboard)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId
|
||||
func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||
ret := _m.Called(ctx, dashboard, publicDashboard, panelId)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
// Gets public dashboard via access token
|
||||
@@ -124,7 +125,7 @@ func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Co
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data)
|
||||
queriesByPanel := models.GroupQueriesByPanelId(dashboard.Data)
|
||||
|
||||
if _, ok := queriesByPanel[panelId]; !ok {
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardPanelNotFound
|
||||
@@ -139,6 +140,26 @@ func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Co
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func (dr *DashboardServiceImpl) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
datasourceUids := models.GetUniqueDashboardDatasourceUids(dashboard.Data)
|
||||
|
||||
// Create a temp user with read-only datasource permissions
|
||||
anonymousUser := &models.SignedInUser{OrgId: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
||||
permissions := make(map[string][]string)
|
||||
queryScopes := make([]string, 0)
|
||||
readScopes := make([]string, 0)
|
||||
for _, uid := range datasourceUids {
|
||||
queryScopes = append(queryScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
readScopes = append(readScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
}
|
||||
permissions[datasources.ActionQuery] = queryScopes
|
||||
permissions[datasources.ActionRead] = readScopes
|
||||
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||
|
||||
return anonymousUser, nil
|
||||
}
|
||||
|
||||
// generates a uuid formatted without dashes to use as access token
|
||||
func GenerateAccessToken() (string, error) {
|
||||
token, err := uuid.NewRandom()
|
||||
|
||||
@@ -314,6 +314,26 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
|
||||
user, err := service.BuildAnonymousUser(context.Background(), dashboard)
|
||||
require.NoError(t, err)
|
||||
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])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
@@ -425,6 +445,9 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
@@ -444,6 +467,9 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds3",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
|
||||
Reference in New Issue
Block a user