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:
owensmallwood
2022-07-06 12:42:39 -06:00
committed by GitHub
parent 9941e06e22
commit 0b4af38bfa
30 changed files with 353 additions and 68 deletions
+1
View File
@@ -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{}{