PublicDashboards: Fetch dashboard as Grafana (#100344)
This commit is contained in:
@@ -75,12 +75,14 @@ func getWildcardPermissions(actions ...string) map[string][]string {
|
||||
// serviceIdentityPermissions is a list of wildcard permissions for provided actions.
|
||||
// We should add every action required "internally" here.
|
||||
var serviceIdentityPermissions = getWildcardPermissions(
|
||||
"annotations:read",
|
||||
"folders:read",
|
||||
"folders:write",
|
||||
"folders:create",
|
||||
"dashboards:read",
|
||||
"dashboards:write",
|
||||
"dashboards:create",
|
||||
"datasources:query",
|
||||
"datasources:read",
|
||||
"alert.provisioning:write",
|
||||
"alert.provisioning.secrets:read",
|
||||
|
||||
@@ -8,16 +8,13 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"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/featuremgmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -37,8 +34,8 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
||||
return nil, models.ErrInternalServerError.Errorf("FindAnnotations: failed to unmarshal dashboard annotations: %w", err)
|
||||
}
|
||||
|
||||
anonymousUser := buildAnonymousUser(ctx, dash, pd.features)
|
||||
|
||||
// We don't have a signed in user for public dashboards. We are using Grafana's Identity to query the annotations.
|
||||
svcCtx, svcIdent := identity.WithServiceIdentity(ctx, dash.OrgID)
|
||||
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
|
||||
@@ -51,7 +48,7 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
||||
OrgID: dash.OrgID,
|
||||
DashboardID: dash.ID,
|
||||
DashboardUID: dash.UID,
|
||||
SignedInUser: anonymousUser,
|
||||
SignedInUser: svcIdent,
|
||||
}
|
||||
|
||||
if anno.Target != nil {
|
||||
@@ -63,7 +60,7 @@ func (pd *PublicDashboardServiceImpl) FindAnnotations(ctx context.Context, reqDT
|
||||
}
|
||||
}
|
||||
|
||||
annotationItems, err := pd.AnnotationsRepo.Find(ctx, annoQuery)
|
||||
annotationItems, err := pd.AnnotationsRepo.Find(svcCtx, annoQuery)
|
||||
if err != nil {
|
||||
return nil, models.ErrInternalServerError.Errorf("FindAnnotations: failed to find annotations: %w", err)
|
||||
}
|
||||
@@ -139,8 +136,9 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context,
|
||||
return nil, models.ErrPanelQueriesNotFound.Errorf("GetQueryDataResponse: failed to extract queries from panel")
|
||||
}
|
||||
|
||||
anonymousUser := buildAnonymousUser(ctx, dashboard, pd.features)
|
||||
res, err := pd.QueryDataService.QueryData(ctx, anonymousUser, skipDSCache, metricReq)
|
||||
// We don't have a signed in user for public dashboards. We are using Grafana's Identity to query the datasource.
|
||||
svcCtx, svcIdent := identity.WithServiceIdentity(ctx, dashboard.OrgID)
|
||||
res, err := pd.QueryDataService.QueryData(svcCtx, svcIdent, skipDSCache, metricReq)
|
||||
|
||||
reqDatasources := metricReq.GetUniqueDatasourceTypes()
|
||||
if err != nil {
|
||||
@@ -180,92 +178,6 @@ func (pd *PublicDashboardServiceImpl) buildMetricRequest(dashboard *dashboards.D
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func buildAnonymousUser(ctx context.Context, dashboard *dashboards.Dashboard, features featuremgmt.FeatureToggles) *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[dashboards.ActionDashboardsRead] = dashboardScopes
|
||||
permissions[accesscontrol.ActionAnnotationsRead] = annotationScopes
|
||||
|
||||
if features.IsEnabled(ctx, featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
permissions[accesscontrol.ActionAnnotationsRead] = dashboardScopes
|
||||
}
|
||||
|
||||
anonymousUser.Permissions[dashboard.OrgID] = permissions
|
||||
|
||||
return anonymousUser
|
||||
}
|
||||
|
||||
func getUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string {
|
||||
var datasourceUids []string
|
||||
exists := map[string]bool{}
|
||||
|
||||
// collapsed rows contain panels in a nested structure, so we need to flatten them before calculate unique uids
|
||||
flattenedPanels := getFlattenedPanels(dashboard)
|
||||
|
||||
for _, panelObj := range flattenedPanels {
|
||||
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 _, targetObj := range panel.Get("targets").MustArray() {
|
||||
target := simplejson.NewFromAny(targetObj)
|
||||
datasourceUid := getDataSourceUidFromJson(target)
|
||||
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 getFlattenedPanels(dashboard *simplejson.Json) []any {
|
||||
var flatPanels []any
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
// if the panel is a row and it is collapsed, get the queries from the panels inside the row
|
||||
// if it is not collapsed, the row does not have any panels
|
||||
if panel.Get("type").MustString() == "row" {
|
||||
if panel.Get("collapsed").MustBool() {
|
||||
flatPanels = append(flatPanels, panel.Get("panels").MustArray()...)
|
||||
}
|
||||
} else {
|
||||
flatPanels = append(flatPanels, panelObj)
|
||||
}
|
||||
}
|
||||
return flatPanels
|
||||
}
|
||||
|
||||
func groupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.Json {
|
||||
result := make(map[int64][]*simplejson.Json)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
dashboard2 "github.com/grafana/grafana/pkg/kinds/dashboard"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@@ -110,161 +110,6 @@ const (
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"datasource": "6SOeCRrVk",
|
||||
"exemplar": true,
|
||||
"expr": "test{id=\"f0dd9b69-ad04-4342-8e79-ced8c245683b\", name=\"test\"}",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
@@ -460,218 +305,6 @@ const (
|
||||
],
|
||||
"schemaVersion": 35
|
||||
}`
|
||||
|
||||
dashboardWithCollapsedRows = `
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 12,
|
||||
"title": "Row title",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "qCbTUC37k"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "qCbTUC37k"
|
||||
},
|
||||
"editorMode": "builder",
|
||||
"expr": "access_evaluation_duration_bucket",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 10,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "P49A45DF074423DFB"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "9.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "influxdb",
|
||||
"uid": "P49A45DF074423DFB"
|
||||
},
|
||||
"query": "// v.bucket, v.timeRangeStart, and v.timeRange stop are all variables supported by the flux plugin and influxdb\nfrom(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_value\"] >= 10 and r[\"_value\"] <= 20)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Row title 1",
|
||||
"type": "row"
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestGetQueryDataResponse(t *testing.T) {
|
||||
@@ -731,8 +364,7 @@ func TestGetQueryDataResponse(t *testing.T) {
|
||||
func TestFindAnnotations(t *testing.T) {
|
||||
color := "red"
|
||||
name := "annoName"
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagAnnotationPermissionUpdate)
|
||||
t.Run("will build anonymous user with correct permissions to get annotations", func(t *testing.T) {
|
||||
t.Run("service identity has correct permissions to get annotations dashboards and query datasources", func(t *testing.T) {
|
||||
fakeStore := &FakePublicDashboardStore{}
|
||||
fakeStore.On("FindByAccessToken", mock.Anything, mock.AnythingOfType("string")).
|
||||
Return(&PublicDashboard{Uid: "uid1", IsEnabled: true}, nil)
|
||||
@@ -746,11 +378,14 @@ func TestFindAnnotations(t *testing.T) {
|
||||
}
|
||||
dash := dashboards.NewDashboard("testDashboard")
|
||||
|
||||
items, _ := service.FindAnnotations(context.Background(), reqDTO, "abc123")
|
||||
anonUser := buildAnonymousUser(context.Background(), dash, features)
|
||||
|
||||
assert.Equal(t, "dashboards:*", anonUser.Permissions[0]["dashboards:read"][0])
|
||||
items, err := service.FindAnnotations(context.Background(), reqDTO, "abc123")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, items, 0)
|
||||
|
||||
_, svcIdent := identity.WithServiceIdentity(context.Background(), dash.OrgID)
|
||||
require.Equal(t, "*", svcIdent.GetPermissions()["datasources:query"][0])
|
||||
require.Equal(t, "*", svcIdent.GetPermissions()["dashboards:read"][0])
|
||||
require.Equal(t, "*", svcIdent.GetPermissions()["annotations:read"][0])
|
||||
})
|
||||
|
||||
t.Run("Test events from tag queries overwrite built-in annotation queries and duplicate events are not returned", func(t *testing.T) {
|
||||
@@ -1121,47 +756,6 @@ func TestGetMetricRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
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, 3)
|
||||
require.Equal(t, "abc123", uids[0])
|
||||
require.Equal(t, "6SOeCRrVk", uids[1])
|
||||
require.Equal(t, "_yxMP8Ynk", uids[2])
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
t.Run("can get unique datasource ids from dashboard with rows", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithCollapsedRows))
|
||||
require.NoError(t, err)
|
||||
|
||||
uids := getUniqueDashboardDatasourceUids(json)
|
||||
require.Len(t, uids, 2)
|
||||
require.Equal(t, "qCbTUC37k", uids[0])
|
||||
require.Equal(t, "P49A45DF074423DFB", uids[1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildMetricRequest(t *testing.T) {
|
||||
fakeDashboardService := &dashboards.FakeDashboardService{}
|
||||
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
||||
@@ -1318,39 +912,6 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
|
||||
user := buildAnonymousUser(context.Background(), dashboard, features)
|
||||
|
||||
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 := buildAnonymousUser(context.Background(), dashboard, features)
|
||||
|
||||
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])
|
||||
})
|
||||
t.Run("will add dashboard and annotation permissions needed for getting annotations when FlagAnnotationPermissionUpdate is enabled", func(t *testing.T) {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagAnnotationPermissionUpdate)
|
||||
user := buildAnonymousUser(context.Background(), dashboard, features)
|
||||
|
||||
require.Equal(t, dashboard.OrgID, user.OrgID)
|
||||
require.Equal(t, "dashboards:*", user.Permissions[user.OrgID]["annotations:read"][0])
|
||||
require.Equal(t, "dashboards:*", user.Permissions[user.OrgID]["dashboards:read"][0])
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -136,7 +137,11 @@ func (pd *PublicDashboardServiceImpl) Find(ctx context.Context, uid string) (*Pu
|
||||
func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) {
|
||||
ctx, span := tracer.Start(ctx, "publicdashboards.FindDashboard")
|
||||
defer span.End()
|
||||
dash, err := pd.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId})
|
||||
|
||||
// We don't have a signed in user for public dashboards. We are using Grafana's Identity to query the dashboard.
|
||||
dash, err := identity.WithServiceIdentityFn(ctx, orgId, func(ctx context.Context) (*dashboards.Dashboard, error) {
|
||||
return pd.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId})
|
||||
})
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
|
||||
Reference in New Issue
Block a user