From 2aff83d4e13616c9132f855ff9f9a47cc0cbd69a Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Thu, 7 Jul 2022 15:58:11 -0600 Subject: [PATCH] Public Dashboards Bug: Permissions error on panel when using mixed datasource with Enterprise (#51930) Fixes pubdash bug for enterprise when pubdash panel uses a mixed datasource --- .../utils/PublicDashboardDataSource.test.ts | 23 +++++ pkg/models/dashboard_queries.go | 19 ++++- pkg/models/dashboard_queries_test.go | 83 +++++++++++++++++++ .../services/PublicDashboardDataSource.ts | 19 ++++- 4 files changed, 139 insertions(+), 5 deletions(-) diff --git a/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts b/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts index f72460483f8..a64884b769c 100644 --- a/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts +++ b/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts @@ -7,6 +7,7 @@ import { PUBLIC_DATASOURCE, PublicDashboardDataSource, } from '../../../../public/app/features/dashboard/services/PublicDashboardDataSource'; +import { MIXED_DATASOURCE_NAME } from '../../../../public/app/plugins/datasource/mixed/MixedDataSource'; import { DataSourceWithBackend } from './DataSourceWithBackend'; @@ -75,4 +76,26 @@ describe('PublicDashboardDatasource', () => { let ds = new PublicDashboardDataSource(datasource); expect(ds.uid).toBe('abc123'); }); + + test('isMixedDatasource returns true when datasource is mixed', () => { + const datasource = new DataSourceWithBackend({ id: 1, uid: MIXED_DATASOURCE_NAME } as DataSourceInstanceSettings); + let ds = new PublicDashboardDataSource(datasource); + expect(ds.meta.mixed).toBeTruthy(); + }); + + test('isMixedDatasource returns false when datasource is not mixed', () => { + const datasource = new DataSourceWithBackend({ id: 1, uid: 'abc123' } as DataSourceInstanceSettings); + let ds = new PublicDashboardDataSource(datasource); + expect(ds.meta.mixed).toBeFalsy(); + }); + + test('isMixedDatasource returns false when datasource is a string', () => { + let ds = new PublicDashboardDataSource('abc123'); + expect(ds.meta.mixed).toBeFalsy(); + }); + + test('isMixedDatasource returns false when datasource is null', () => { + let ds = new PublicDashboardDataSource(null); + expect(ds.meta.mixed).toBeFalsy(); + }); }); diff --git a/pkg/models/dashboard_queries.go b/pkg/models/dashboard_queries.go index 7f27011e73c..f0b470acb19 100644 --- a/pkg/models/dashboard_queries.go +++ b/pkg/models/dashboard_queries.go @@ -11,9 +11,22 @@ func GetUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string { for _, panelObj := range dashboard.Get("panels").MustArray() { panel := simplejson.NewFromAny(panelObj) uid := panel.Get("datasource").Get("uid").MustString() - if _, ok := exists[uid]; !ok { - datasourceUids = append(datasourceUids, uid) - exists[uid] = true + + // 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 + } } } diff --git a/pkg/models/dashboard_queries_test.go b/pkg/models/dashboard_queries_test.go index ed02d11c88b..afa160515ab 100644 --- a/pkg/models/dashboard_queries_test.go +++ b/pkg/models/dashboard_queries_test.go @@ -56,6 +56,79 @@ 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" + } + ], + "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": [ @@ -163,6 +236,16 @@ func TestGetUniqueDashboardDatasourceUids(t *testing.T) { 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) diff --git a/public/app/features/dashboard/services/PublicDashboardDataSource.ts b/public/app/features/dashboard/services/PublicDashboardDataSource.ts index 52e89d55fba..f3a595e3383 100644 --- a/public/app/features/dashboard/services/PublicDashboardDataSource.ts +++ b/public/app/features/dashboard/services/PublicDashboardDataSource.ts @@ -5,20 +5,27 @@ import { DataQueryRequest, DataQueryResponse, DataSourceApi, + DataSourcePluginMeta, DataSourceRef, - PluginMeta, } from '@grafana/data'; import { BackendDataSourceResponse, getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; +import { MIXED_DATASOURCE_NAME } from '../../../plugins/datasource/mixed/MixedDataSource'; + export const PUBLIC_DATASOURCE = '-- Public --'; export class PublicDashboardDataSource extends DataSourceApi { constructor(datasource: DataSourceRef | string | DataSourceApi | null) { + let meta = {} as DataSourcePluginMeta; + if (PublicDashboardDataSource.isMixedDatasource(datasource)) { + meta.mixed = true; + } + super({ name: 'public-ds', id: 0, type: 'public-ds', - meta: {} as PluginMeta, + meta, uid: PublicDashboardDataSource.resolveUid(datasource), jsonData: {}, access: 'proxy', @@ -38,6 +45,14 @@ export class PublicDashboardDataSource extends DataSourceApi { return datasource?.uid ?? PUBLIC_DATASOURCE; } + private static isMixedDatasource(datasource: DataSourceRef | string | DataSourceApi | null): boolean { + if (typeof datasource === 'string' || datasource === null) { + return false; + } + + return datasource?.uid === MIXED_DATASOURCE_NAME; + } + /** * Ideally final -- any other implementation may not work as expected */