PublicDashboards: Variables refactor (#73476)

Co-authored-by: Juan Cabanas <juan.cabanas@grafana.com>
Co-authored-by: Ezequiel Victorero <ezequiel.victorero@grafana.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Torkel Ödegaard 2023-08-25 20:56:02 +02:00 committed by GitHub
parent 2245a3d0d1
commit 3ee26df41e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 147 additions and 204 deletions

View File

@ -147,7 +147,7 @@ export interface BootData {
* @internal * @internal
*/ */
export interface GrafanaConfig { export interface GrafanaConfig {
isPublicDashboardView: boolean; publicDashboardAccessToken?: string;
snapshotEnabled: boolean; snapshotEnabled: boolean;
datasources: { [str: string]: DataSourceInstanceSettings }; datasources: { [str: string]: DataSourceInstanceSettings };
panels: { [key: string]: PanelPluginMeta }; panels: { [key: string]: PanelPluginMeta };

View File

@ -530,7 +530,6 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
timeInfo?: string; // The query time description (blue text in the upper right) timeInfo?: string; // The query time description (blue text in the upper right)
panelId?: number; panelId?: number;
dashboardUID?: string; dashboardUID?: string;
publicDashboardAccessToken?: string;
// Request Timing // Request Timing
startTime: number; startTime: number;

View File

@ -33,7 +33,7 @@ export type AppPluginConfig = {
}; };
export class GrafanaBootConfig implements GrafanaConfig { export class GrafanaBootConfig implements GrafanaConfig {
isPublicDashboardView: boolean; publicDashboardAccessToken?: string;
snapshotEnabled = true; snapshotEnabled = true;
datasources: { [str: string]: DataSourceInstanceSettings } = {}; datasources: { [str: string]: DataSourceInstanceSettings } = {};
panels: { [key: string]: PanelPluginMeta } = {}; panels: { [key: string]: PanelPluginMeta } = {};
@ -166,7 +166,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
constructor(options: GrafanaBootConfig) { constructor(options: GrafanaBootConfig) {
this.bootData = options.bootData; this.bootData = options.bootData;
this.isPublicDashboardView = options.bootData.settings.isPublicDashboardView;
const defaults = { const defaults = {
datasources: {}, datasources: {},

View File

@ -165,7 +165,7 @@ func (hs *HTTPServer) registerRoutes() {
// anonymous view public dashboard // anonymous view public dashboard
r.Get("/public-dashboards/:accessToken", r.Get("/public-dashboards/:accessToken",
publicdashboardsapi.SetPublicDashboardFlag, publicdashboardsapi.SetPublicDashboardAccessToken,
publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService), publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService),
publicdashboardsapi.CountPublicDashboardRequest(), publicdashboardsapi.CountPublicDashboardRequest(),
hs.Index, hs.Index,

View File

@ -7,34 +7,33 @@ import (
) )
type DashboardMeta struct { type DashboardMeta struct {
IsStarred bool `json:"isStarred,omitempty"` IsStarred bool `json:"isStarred,omitempty"`
IsSnapshot bool `json:"isSnapshot,omitempty"` IsSnapshot bool `json:"isSnapshot,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
CanSave bool `json:"canSave"` CanSave bool `json:"canSave"`
CanEdit bool `json:"canEdit"` CanEdit bool `json:"canEdit"`
CanAdmin bool `json:"canAdmin"` CanAdmin bool `json:"canAdmin"`
CanStar bool `json:"canStar"` CanStar bool `json:"canStar"`
CanDelete bool `json:"canDelete"` CanDelete bool `json:"canDelete"`
Slug string `json:"slug"` Slug string `json:"slug"`
Url string `json:"url"` Url string `json:"url"`
Expires time.Time `json:"expires"` Expires time.Time `json:"expires"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
UpdatedBy string `json:"updatedBy"` UpdatedBy string `json:"updatedBy"`
CreatedBy string `json:"createdBy"` CreatedBy string `json:"createdBy"`
Version int `json:"version"` Version int `json:"version"`
HasACL bool `json:"hasAcl" xorm:"has_acl"` HasACL bool `json:"hasAcl" xorm:"has_acl"`
IsFolder bool `json:"isFolder"` IsFolder bool `json:"isFolder"`
FolderId int64 `json:"folderId"` FolderId int64 `json:"folderId"`
FolderUid string `json:"folderUid"` FolderUid string `json:"folderUid"`
FolderTitle string `json:"folderTitle"` FolderTitle string `json:"folderTitle"`
FolderUrl string `json:"folderUrl"` FolderUrl string `json:"folderUrl"`
Provisioned bool `json:"provisioned"` Provisioned bool `json:"provisioned"`
ProvisionedExternalId string `json:"provisionedExternalId"` ProvisionedExternalId string `json:"provisionedExternalId"`
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"` AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"` PublicDashboardUID string `json:"publicDashboardUid,omitempty"`
PublicDashboardUID string `json:"publicDashboardUid"` PublicDashboardEnabled bool `json:"publicDashboardEnabled,omitempty"`
PublicDashboardEnabled bool `json:"publicDashboardEnabled"`
} }
type AnnotationPermission struct { type AnnotationPermission struct {
Dashboard AnnotationActions `json:"dashboard"` Dashboard AnnotationActions `json:"dashboard"`

View File

@ -217,7 +217,7 @@ type FrontendSettingsDTO struct {
GeomapDefaultBaseLayerConfig *map[string]interface{} `json:"geomapDefaultBaseLayerConfig,omitempty"` GeomapDefaultBaseLayerConfig *map[string]interface{} `json:"geomapDefaultBaseLayerConfig,omitempty"`
GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"` GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"`
IsPublicDashboardView bool `json:"isPublicDashboardView"` PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
DateFormats setting.DateFormats `json:"dateFormats,omitempty"` DateFormats setting.DateFormats `json:"dateFormats,omitempty"`

View File

@ -77,8 +77,6 @@ type MetricRequest struct {
Queries []*simplejson.Json `json:"queries"` Queries []*simplejson.Json `json:"queries"`
// required: false // required: false
Debug bool `json:"debug"` Debug bool `json:"debug"`
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
} }
func (mr *MetricRequest) GetUniqueDatasourceTypes() []string { func (mr *MetricRequest) GetUniqueDatasourceTypes() []string {

View File

@ -152,6 +152,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
DateFormats: hs.Cfg.DateFormats, DateFormats: hs.Cfg.DateFormats,
SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI, SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI,
DisableFrontendSandboxForPlugins: hs.Cfg.DisableFrontendSandboxForPlugins, DisableFrontendSandboxForPlugins: hs.Cfg.DisableFrontendSandboxForPlugins,
PublicDashboardAccessToken: c.PublicDashboardAccessToken,
Auth: dtos.FrontendSettingsAuthDTO{ Auth: dtos.FrontendSettingsAuthDTO{
OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync, OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync,
@ -282,7 +283,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
return nil, err return nil, err
} }
if c.IsPublicDashboardView { if c.IsPublicDashboardView() {
// If RBAC is enabled, it will filter out all datasources for a public user, so we need to skip it // If RBAC is enabled, it will filter out all datasources for a public user, so we need to skip it
orgDataSources = dataSources orgDataSources = dataSources
} else { } else {

View File

@ -29,8 +29,6 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
return nil, err return nil, err
} }
settings.IsPublicDashboardView = c.IsPublicDashboardView
prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.UserID, OrgID: c.OrgID, Teams: c.Teams} prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.UserID, OrgID: c.OrgID, Teams: c.Teams}
prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery)
if err != nil { if err != nil {

View File

@ -61,19 +61,19 @@ func CopyWithReqContext(ctx context.Context) context.Context {
Resp: web.NewResponseWriter(origReqCtx.Req.Method, response.CreateNormalResponse(http.Header{}, []byte{}, 0)), Resp: web.NewResponseWriter(origReqCtx.Req.Method, response.CreateNormalResponse(http.Header{}, []byte{}, 0)),
} }
reqCtx := &contextmodel.ReqContext{ reqCtx := &contextmodel.ReqContext{
Context: webCtx, Context: webCtx,
SignedInUser: origReqCtx.SignedInUser, SignedInUser: origReqCtx.SignedInUser,
UserToken: origReqCtx.UserToken, UserToken: origReqCtx.UserToken,
IsSignedIn: origReqCtx.IsSignedIn, IsSignedIn: origReqCtx.IsSignedIn,
IsRenderCall: origReqCtx.IsRenderCall, IsRenderCall: origReqCtx.IsRenderCall,
AllowAnonymous: origReqCtx.AllowAnonymous, AllowAnonymous: origReqCtx.AllowAnonymous,
SkipDSCache: origReqCtx.SkipDSCache, SkipDSCache: origReqCtx.SkipDSCache,
SkipQueryCache: origReqCtx.SkipQueryCache, SkipQueryCache: origReqCtx.SkipQueryCache,
Logger: origReqCtx.Logger, Logger: origReqCtx.Logger,
Error: origReqCtx.Error, Error: origReqCtx.Error,
RequestNonce: origReqCtx.RequestNonce, RequestNonce: origReqCtx.RequestNonce,
IsPublicDashboardView: origReqCtx.IsPublicDashboardView, PublicDashboardAccessToken: origReqCtx.PublicDashboardAccessToken,
LookupTokenErr: origReqCtx.LookupTokenErr, LookupTokenErr: origReqCtx.LookupTokenErr,
} }
return context.WithValue(ctx, reqContextKey{}, reqCtx) return context.WithValue(ctx, reqContextKey{}, reqCtx)
} }

View File

@ -30,8 +30,8 @@ type ReqContext struct {
Logger log.Logger Logger log.Logger
Error error Error error
// RequestNonce is a cryptographic request identifier for use with Content Security Policy. // RequestNonce is a cryptographic request identifier for use with Content Security Policy.
RequestNonce string RequestNonce string
IsPublicDashboardView bool PublicDashboardAccessToken string
PerfmonTimer prometheus.Summary PerfmonTimer prometheus.Summary
LookupTokenErr error LookupTokenErr error
@ -60,6 +60,10 @@ func (ctx *ReqContext) IsApiRequest() bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api") return strings.HasPrefix(ctx.Req.URL.Path, "/api")
} }
func (ctx *ReqContext) IsPublicDashboardView() bool {
return ctx.PublicDashboardAccessToken != ""
}
func (ctx *ReqContext) JsonApiErr(status int, message string, err error) { func (ctx *ReqContext) JsonApiErr(status int, message string, err error) {
resp := make(map[string]interface{}) resp := make(map[string]interface{})
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false) traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)

View File

@ -95,7 +95,7 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
}) })
} }
if c.IsPublicDashboardView || hasAccess(ac.EvalAny( if c.IsPublicDashboardView() || hasAccess(ac.EvalAny(
ac.EvalPermission(dashboards.ActionFoldersRead), ac.EvalPermission(dashboards.ActionFoldersCreate), ac.EvalPermission(dashboards.ActionFoldersRead), ac.EvalPermission(dashboards.ActionFoldersCreate),
ac.EvalPermission(dashboards.ActionDashboardsRead), ac.EvalPermission(dashboards.ActionDashboardsCreate)), ac.EvalPermission(dashboards.ActionDashboardsRead), ac.EvalPermission(dashboards.ActionDashboardsCreate)),
) { ) {

View File

@ -46,16 +46,16 @@ func TestAlertingProxy_createProxyContext(t *testing.T) {
Context: &web.Context{ Context: &web.Context{
Req: &http.Request{}, Req: &http.Request{},
}, },
SignedInUser: &user.SignedInUser{}, SignedInUser: &user.SignedInUser{},
UserToken: &auth.UserToken{}, UserToken: &auth.UserToken{},
IsSignedIn: rand.Int63()%2 == 1, IsSignedIn: rand.Int63()%2 == 1,
IsRenderCall: rand.Int63()%2 == 1, IsRenderCall: rand.Int63()%2 == 1,
AllowAnonymous: rand.Int63()%2 == 1, AllowAnonymous: rand.Int63()%2 == 1,
SkipDSCache: rand.Int63()%2 == 1, SkipDSCache: rand.Int63()%2 == 1,
SkipQueryCache: rand.Int63()%2 == 1, SkipQueryCache: rand.Int63()%2 == 1,
Logger: log.New("test"), Logger: log.New("test"),
RequestNonce: util.GenerateShortUID(), RequestNonce: util.GenerateShortUID(),
IsPublicDashboardView: rand.Int63()%2 == 1, PublicDashboardAccessToken: util.GenerateShortUID(),
} }
t.Run("should create a copy of request context", func(t *testing.T) { t.Run("should create a copy of request context", func(t *testing.T) {
@ -81,7 +81,7 @@ func TestAlertingProxy_createProxyContext(t *testing.T) {
require.Equal(t, ctx.SkipQueryCache, newCtx.SkipQueryCache) require.Equal(t, ctx.SkipQueryCache, newCtx.SkipQueryCache)
require.Equal(t, ctx.Logger, newCtx.Logger) require.Equal(t, ctx.Logger, newCtx.Logger)
require.Equal(t, ctx.RequestNonce, newCtx.RequestNonce) require.Equal(t, ctx.RequestNonce, newCtx.RequestNonce)
require.Equal(t, ctx.IsPublicDashboardView, newCtx.IsPublicDashboardView) require.Equal(t, ctx.PublicDashboardAccessToken, newCtx.PublicDashboardAccessToken)
} }
}) })
t.Run("should overwrite response writer", func(t *testing.T) { t.Run("should overwrite response writer", func(t *testing.T) {

View File

@ -36,7 +36,7 @@ var ResourceCachingRequestHistogram = prometheus.NewHistogramVec(prometheus.Hist
}, []string{"plugin_id", "cache"}) }, []string{"plugin_id", "cache"})
func getQueryType(req *contextmodel.ReqContext) string { func getQueryType(req *contextmodel.ReqContext) string {
if req.IsPublicDashboardView { if req.IsPublicDashboardView() {
return QueryPubdash return QueryPubdash
} }
return QueryDashboard return QueryDashboard

View File

@ -28,9 +28,9 @@ func SetPublicDashboardOrgIdOnContext(publicDashboardService publicdashboards.Se
} }
} }
// SetPublicDashboardFlag Adds public dashboard flag on context // SetPublicDashboardAccessToken Adds public dashboard flag on context
func SetPublicDashboardFlag(c *contextmodel.ReqContext) { func SetPublicDashboardAccessToken(c *contextmodel.ReqContext) {
c.IsPublicDashboardView = true c.PublicDashboardAccessToken = web.Params(c.Req)[":accessToken"]
} }
// RequiresExistingAccessToken Middleware to enforce that a public dashboards exists before continuing to handler. This // RequiresExistingAccessToken Middleware to enforce that a public dashboards exists before continuing to handler. This

View File

@ -145,10 +145,10 @@ func TestSetPublicDashboardOrgIdOnContext(t *testing.T) {
} }
func TestSetPublicDashboardFlag(t *testing.T) { func TestSetPublicDashboardFlag(t *testing.T) {
t.Run("Adds context.IsPublicDashboardView=true to request", func(t *testing.T) { t.Run("Adds context.PublicDashboardAccessToken to request", func(t *testing.T) {
ctx := &contextmodel.ReqContext{} ctx := &contextmodel.ReqContext{Context: &web.Context{Req: web.SetURLParams(&http.Request{}, map[string]string{":accessToken": "asdfasdfasdfsadfasdfsfd"})}}
SetPublicDashboardFlag(ctx) SetPublicDashboardAccessToken(ctx)
assert.True(t, ctx.IsPublicDashboardView) assert.NotEmpty(t, ctx.PublicDashboardAccessToken)
}) })
} }

View File

@ -30,21 +30,21 @@ func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Respons
} }
meta := dtos.DashboardMeta{ meta := dtos.DashboardMeta{
Slug: dash.Slug, Slug: dash.Slug,
Type: dashboards.DashTypeDB, Type: dashboards.DashTypeDB,
CanStar: false, CanStar: false,
CanSave: false, CanSave: false,
CanEdit: false, CanEdit: false,
CanAdmin: false, CanAdmin: false,
CanDelete: false, CanDelete: false,
Created: dash.Created, Created: dash.Created,
Updated: dash.Updated, Updated: dash.Updated,
Version: dash.Version, Version: dash.Version,
IsFolder: false, IsFolder: false,
FolderId: dash.FolderID, FolderId: dash.FolderID,
PublicDashboardAccessToken: pubdash.AccessToken, PublicDashboardEnabled: pubdash.IsEnabled,
PublicDashboardEnabled: pubdash.IsEnabled,
} }
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled) dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data} dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}

View File

@ -287,11 +287,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
queries := []*simplejson.Json{query1, query2} queries := []*simplejson.Json{query1, query2}
reqDTO := dtos.MetricRequest{ reqDTO := dtos.MetricRequest{
From: "2022-01-01", From: "2022-01-01",
To: "2022-01-02", To: "2022-01-02",
Queries: queries, Queries: queries,
Debug: false, Debug: false,
PublicDashboardAccessToken: "abc123",
} }
req, err := http.NewRequest("POST", "http://localhost:3000", nil) req, err := http.NewRequest("POST", "http://localhost:3000", nil)
@ -351,11 +350,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
queries := []*simplejson.Json{query1, query2, query3} queries := []*simplejson.Json{query1, query2, query3}
reqDTO := dtos.MetricRequest{ reqDTO := dtos.MetricRequest{
From: "2022-01-01", From: "2022-01-01",
To: "2022-01-02", To: "2022-01-02",
Queries: queries, Queries: queries,
Debug: false, Debug: false,
PublicDashboardAccessToken: "abc123",
} }
// without query parameter // without query parameter
@ -406,11 +404,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
queries := []*simplejson.Json{query1, query2} queries := []*simplejson.Json{query1, query2}
reqDTO := dtos.MetricRequest{ reqDTO := dtos.MetricRequest{
From: "2022-01-01", From: "2022-01-01",
To: "2022-01-02", To: "2022-01-02",
Queries: queries, Queries: queries,
Debug: false, Debug: false,
PublicDashboardAccessToken: "abc123",
} }
res, err := tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO) res, err := tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO)
@ -436,11 +433,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
queries := []*simplejson.Json{query1} queries := []*simplejson.Json{query1}
reqDTO := dtos.MetricRequest{ reqDTO := dtos.MetricRequest{
From: "2022-01-01", From: "2022-01-01",
To: "2022-01-02", To: "2022-01-02",
Queries: queries, Queries: queries,
Debug: false, Debug: false,
PublicDashboardAccessToken: "abc123",
} }
_, err = tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO) _, err = tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO)

View File

@ -200,7 +200,6 @@ class MetricsPanelCtrl extends PanelCtrl {
timeRange: this.range, timeRange: this.range,
maxDataPoints: panel.maxDataPoints || this.width, maxDataPoints: panel.maxDataPoints || this.width,
minInterval: panel.interval, minInterval: panel.interval,
publicDashboardAccessToken: this.dashboard.meta.publicDashboardAccessToken,
scopedVars: panel.scopedVars, scopedVars: panel.scopedVars,
cacheTimeout: panel.cacheTimeout, cacheTimeout: panel.cacheTimeout,
queryCachingTTL: panel.queryCachingTTL, queryCachingTTL: panel.queryCachingTTL,

View File

@ -57,7 +57,6 @@ export function executeAnnotationQuery(
scopedVars, scopedVars,
...interval, ...interval,
app: CoreApp.Dashboard, app: CoreApp.Dashboard,
publicDashboardAccessToken: options.dashboard.meta.publicDashboardAccessToken,
timezone: options.dashboard.timezone, timezone: options.dashboard.timezone,

View File

@ -133,7 +133,7 @@ export const publicDashboardEventNames: AnnotationFieldInfo[] = [
// Given legacy infrastructure, alert events are passed though the same annotation // Given legacy infrastructure, alert events are passed though the same annotation
// pipeline, but include fields that should not be exposed generally // pipeline, but include fields that should not be exposed generally
const alertEventAndAnnotationFields: AnnotationFieldInfo[] = [ const alertEventAndAnnotationFields: AnnotationFieldInfo[] = [
...(config.isPublicDashboardView ? publicDashboardEventNames : []), ...(config.publicDashboardAccessToken ? publicDashboardEventNames : []),
...annotationEventNames, ...annotationEventNames,
{ key: 'userId' }, { key: 'userId' },
{ key: 'login' }, { key: 'login' },

View File

@ -1,6 +1,8 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { config } from '@grafana/runtime';
import { createEmptyQueryResponse } from '../../../explore/state/utils'; import { createEmptyQueryResponse } from '../../../explore/state/utils';
import { PanelModel } from '../../state'; import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures'; import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
@ -17,9 +19,10 @@ let panelModel = new PanelModel({
let panelData = createEmptyQueryResponse(); let panelData = createEmptyQueryResponse();
describe('Panel Header', () => { describe('Panel Header', () => {
const dashboardModel = createDashboardModelFixture({}, { publicDashboardAccessToken: 'abc123' }); const dashboardModel = createDashboardModelFixture({}, {});
it('will render header title but not render dropdown icon when dashboard is being viewed publicly', () => { it('will render header title but not render dropdown icon when dashboard is being viewed publicly', () => {
window.history.pushState({}, 'Test Title', '/public-dashboards/abc123'); window.history.pushState({}, 'Test Title', '/public-dashboards/abc123');
config.publicDashboardAccessToken = 'abc123';
render( render(
<PanelHeader panel={panelModel} dashboard={dashboardModel} isViewing={false} isEditing={false} data={panelData} /> <PanelHeader panel={panelModel} dashboard={dashboardModel} isViewing={false} isEditing={false} data={panelData} />
@ -30,8 +33,9 @@ describe('Panel Header', () => {
}); });
it('will render header title and dropdown icon when dashboard is not being viewed publicly', () => { it('will render header title and dropdown icon when dashboard is not being viewed publicly', () => {
const dashboardModel = createDashboardModelFixture({}, { publicDashboardAccessToken: '' }); const dashboardModel = createDashboardModelFixture({}, {});
window.history.pushState({}, 'Test Title', '/d/abc/123'); window.history.pushState({}, 'Test Title', '/d/abc/123');
config.publicDashboardAccessToken = '';
render( render(
<PanelHeader panel={panelModel} dashboard={dashboardModel} isViewing={false} isEditing={false} data={panelData} /> <PanelHeader panel={panelModel} dashboard={dashboardModel} isViewing={false} isEditing={false} data={panelData} />

View File

@ -3,7 +3,7 @@ import React from 'react';
import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data'; import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime'; import { config, reportInteraction } from '@grafana/runtime';
import { Icon, useStyles2, ClickOutsideWrapper } from '@grafana/ui'; import { Icon, useStyles2, ClickOutsideWrapper } from '@grafana/ui';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel';
@ -65,7 +65,7 @@ export function PanelHeader({ panel, error, isViewing, isEditing, data, alertSta
/> />
) : null} ) : null}
<h2 className={styles.titleText}>{title}</h2> <h2 className={styles.titleText}>{title}</h2>
{!dashboard.meta.publicDashboardAccessToken && ( {!config.publicDashboardAccessToken && (
<div data-testid="panel-dropdown"> <div data-testid="panel-dropdown">
<Icon name="angle-down" className="panel-menu-toggle" /> <Icon name="angle-down" className="panel-menu-toggle" />
{panelMenuOpen ? <PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} /> : null} {panelMenuOpen ? <PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} /> : null}

View File

@ -353,7 +353,6 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
panel.runAllPanelQueries({ panel.runAllPanelQueries({
dashboardUID: dashboard.uid, dashboardUID: dashboard.uid,
dashboardTimezone: dashboard.getTimezone(), dashboardTimezone: dashboard.getTimezone(),
publicDashboardAccessToken: dashboard.meta.publicDashboardAccessToken,
timeData, timeData,
width, width,
}); });

View File

@ -1,7 +1,7 @@
import { of } from 'rxjs'; import { of } from 'rxjs';
import { DataQueryRequest, DataSourceInstanceSettings, DataSourceRef, dateTime, TimeRange } from '@grafana/data'; import { DataQueryRequest, DataSourceInstanceSettings, DataSourceRef, dateTime, TimeRange } from '@grafana/data';
import { BackendSrvRequest, BackendSrv, DataSourceWithBackend } from '@grafana/runtime'; import { BackendSrvRequest, BackendSrv, DataSourceWithBackend, config } from '@grafana/runtime';
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
@ -45,41 +45,14 @@ describe('PublicDashboardDatasource', () => {
expect(annotation?.queryType).toEqual(GrafanaQueryType.Annotations); expect(annotation?.queryType).toEqual(GrafanaQueryType.Annotations);
}); });
test('will not fetch annotations when access token is falsey', async () => {
mockDatasourceRequest.mockReset();
mockDatasourceRequest.mockReturnValue(Promise.resolve([]));
const ds = new PublicDashboardDataSource('public');
const panelId = 1;
const publicDashboardAccessToken = undefined;
await ds.query({
maxDataPoints: 10,
intervalMs: 5000,
targets: [
{
refId: 'A',
datasource: { uid: GRAFANA_DATASOURCE_NAME, type: 'sample' },
queryType: GrafanaQueryType.Annotations,
},
],
panelId,
publicDashboardAccessToken,
range: { from: new Date().toLocaleString(), to: new Date().toLocaleString() } as unknown as TimeRange,
} as DataQueryRequest);
const mock = mockDatasourceRequest.mock;
expect(mock.calls.length).toBe(0);
});
test('fetches results from the pubdash annotations endpoint when it is an annotation query', async () => { test('fetches results from the pubdash annotations endpoint when it is an annotation query', async () => {
mockDatasourceRequest.mockReset(); mockDatasourceRequest.mockReset();
mockDatasourceRequest.mockReturnValue(Promise.resolve([])); mockDatasourceRequest.mockReturnValue(Promise.resolve([]));
const ds = new PublicDashboardDataSource('public'); const ds = new PublicDashboardDataSource('public');
const panelId = 1; const panelId = 1;
const publicDashboardAccessToken = 'abc123';
config.publicDashboardAccessToken = 'abc123';
await ds.query({ await ds.query({
maxDataPoints: 10, maxDataPoints: 10,
@ -92,14 +65,13 @@ describe('PublicDashboardDatasource', () => {
}, },
], ],
panelId, panelId,
publicDashboardAccessToken,
range: { from: new Date().toLocaleString(), to: new Date().toLocaleString() } as unknown as TimeRange, range: { from: new Date().toLocaleString(), to: new Date().toLocaleString() } as unknown as TimeRange,
} as DataQueryRequest); } as DataQueryRequest);
const mock = mockDatasourceRequest.mock; const mock = mockDatasourceRequest.mock;
expect(mock.calls.length).toBe(1); expect(mock.calls.length).toBe(1);
expect(mock.lastCall[0]).toEqual(`/api/public/dashboards/${publicDashboardAccessToken}/annotations`); expect(mock.lastCall[0]).toEqual(`/api/public/dashboards/abc123/annotations`);
}); });
test('fetches results from the pubdash query endpoint when not annotation query', () => { test('fetches results from the pubdash query endpoint when not annotation query', () => {
@ -108,7 +80,7 @@ describe('PublicDashboardDatasource', () => {
const ds = new PublicDashboardDataSource('public'); const ds = new PublicDashboardDataSource('public');
const panelId = 1; const panelId = 1;
const publicDashboardAccessToken = 'abc123'; config.publicDashboardAccessToken = 'abc123';
ds.query({ ds.query({
maxDataPoints: 10, maxDataPoints: 10,
@ -123,15 +95,12 @@ describe('PublicDashboardDatasource', () => {
to: 'now', to: 'now',
}, },
}, },
publicDashboardAccessToken,
} as DataQueryRequest); } as DataQueryRequest);
const mock = mockDatasourceRequest.mock; const mock = mockDatasourceRequest.mock;
expect(mock.calls.length).toBe(1); expect(mock.calls.length).toBe(1);
expect(mock.lastCall[0].url).toEqual( expect(mock.lastCall[0].url).toEqual(`/api/public/dashboards/abc123/panels/${panelId}/query`);
`/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`
);
}); });
test('returns public datasource uid when datasource passed in is null', () => { test('returns public datasource uid when datasource passed in is null', () => {

View File

@ -12,7 +12,7 @@ import {
DataSourceRef, DataSourceRef,
toDataFrame, toDataFrame,
} from '@grafana/data'; } from '@grafana/data';
import { BackendDataSourceResponse, getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; import { BackendDataSourceResponse, config, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
import { GrafanaQueryType } from '../../../plugins/datasource/grafana/types'; import { GrafanaQueryType } from '../../../plugins/datasource/grafana/types';
import { MIXED_DATASOURCE_NAME } from '../../../plugins/datasource/mixed/MixedDataSource'; import { MIXED_DATASOURCE_NAME } from '../../../plugins/datasource/mixed/MixedDataSource';
@ -85,7 +85,6 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
intervalMs, intervalMs,
maxDataPoints, maxDataPoints,
requestId, requestId,
publicDashboardAccessToken,
panelId, panelId,
queryCachingTTL, queryCachingTTL,
range: { from: fromRange, to: toRange }, range: { from: fromRange, to: toRange },
@ -120,7 +119,7 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
return getBackendSrv() return getBackendSrv()
.fetch<BackendDataSourceResponse>({ .fetch<BackendDataSourceResponse>({
url: `/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`, url: `/api/public/dashboards/${config.publicDashboardAccessToken!}/panels/${panelId}/query`,
method: 'POST', method: 'POST',
data: body, data: body,
requestId, requestId,
@ -138,7 +137,6 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
async getAnnotations(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> { async getAnnotations(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> {
const { const {
publicDashboardAccessToken: accessToken,
range: { to, from }, range: { to, from },
} = request; } = request;
@ -147,9 +145,10 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
to: to.valueOf(), to: to.valueOf(),
}; };
const annotations = accessToken const annotations = await getBackendSrv().get(
? await getBackendSrv().get(`/api/public/dashboards/${accessToken}/annotations`, params) `/api/public/dashboards/${config.publicDashboardAccessToken!}/annotations`,
: []; params
);
return { data: [toDataFrame(annotations)] }; return { data: [toDataFrame(annotations)] };
} }

View File

@ -99,7 +99,7 @@ describe('timeSrv', () => {
}; };
locationService.push('/d/id?from=now-24h&to=now'); locationService.push('/d/id?from=now-24h&to=now');
config.isPublicDashboardView = true; config.publicDashboardAccessToken = 'abc123';
timeSrv = new TimeSrv(new ContextSrvStub()); timeSrv = new TimeSrv(new ContextSrvStub());
}); });

View File

@ -149,7 +149,7 @@ export class TimeSrv {
} }
private initTimeFromUrl() { private initTimeFromUrl() {
if (config.isPublicDashboardView && this.timeModel?.timepicker?.hidden) { if (config.publicDashboardAccessToken && this.timeModel?.timepicker?.hidden) {
return; return;
} }

View File

@ -359,19 +359,12 @@ export class PanelModel implements DataConfigSource, IPanelModel {
this.render(); this.render();
} }
runAllPanelQueries({ runAllPanelQueries({ dashboardUID, dashboardTimezone, timeData, width }: RunPanelQueryOptions) {
dashboardUID,
dashboardTimezone,
timeData,
width,
publicDashboardAccessToken,
}: RunPanelQueryOptions) {
this.getQueryRunner().run({ this.getQueryRunner().run({
datasource: this.datasource, datasource: this.datasource,
queries: this.targets, queries: this.targets,
panelId: this.id, panelId: this.id,
dashboardUID: dashboardUID, dashboardUID: dashboardUID,
publicDashboardAccessToken,
timezone: dashboardTimezone, timezone: dashboardTimezone,
timeRange: timeData.timeRange, timeRange: timeData.timeRange,
timeInfo: timeData.timeInfo, timeInfo: timeData.timeInfo,

View File

@ -91,15 +91,6 @@ describe('AnnotationsWorker', () => {
}); });
}); });
describe('when canWork is called with correct props for a public dashboard with public view', () => {
it('then it should return true', () => {
const options = getDefaultOptions();
options.dashboard.meta.publicDashboardAccessToken = 'accessTokenString';
expect(worker.canWork(options)).toBe(true);
});
});
describe('when canWork is called with incorrect props', () => { describe('when canWork is called with incorrect props', () => {
it('then it should return false', () => { it('then it should return false', () => {
const dashboard = { annotations: { list: [] } } as unknown as DashboardModel; const dashboard = { annotations: { list: [] } } as unknown as DashboardModel;

View File

@ -3,8 +3,7 @@ import { from, merge, Observable, of } from 'rxjs';
import { catchError, filter, finalize, map, mergeAll, mergeMap, reduce, takeUntil } from 'rxjs/operators'; import { catchError, filter, finalize, map, mergeAll, mergeMap, reduce, takeUntil } from 'rxjs/operators';
import { AnnotationQuery, DataSourceApi } from '@grafana/data'; import { AnnotationQuery, DataSourceApi } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime'; import { config, getDataSourceSrv } from '@grafana/runtime';
import { getConfig } from 'app/core/config';
import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events'; import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events';
import { PUBLIC_DATASOURCE, PublicDashboardDataSource } from '../../../dashboard/services/PublicDashboardDataSource'; import { PUBLIC_DATASOURCE, PublicDashboardDataSource } from '../../../dashboard/services/PublicDashboardDataSource';
@ -41,14 +40,16 @@ export class AnnotationsWorker implements DashboardQueryRunnerWorker {
const { dashboard, range } = options; const { dashboard, range } = options;
let annotations = dashboard.annotations.list.filter(AnnotationsWorker.getAnnotationsToProcessFilter); let annotations = dashboard.annotations.list.filter(AnnotationsWorker.getAnnotationsToProcessFilter);
// We only want to create a single PublicDashboardDatasource. This will get all annotations in one request. // We only want to create a single PublicDashboardDatasource. This will get all annotations in one request.
if (dashboard.meta.publicDashboardAccessToken && annotations.length > 0) { if (config.publicDashboardAccessToken && annotations.length > 0) {
annotations = [annotations[0]]; annotations = [annotations[0]];
} }
const observables = annotations.map((annotation) => { const observables = annotations.map((annotation) => {
let datasourceObservable; let datasourceObservable;
if (getConfig().isPublicDashboardView) { if (config.publicDashboardAccessToken) {
const pubdashDatasource = new PublicDashboardDataSource(PUBLIC_DATASOURCE); const pubdashDatasource = new PublicDashboardDataSource(PUBLIC_DATASOURCE);
datasourceObservable = of(pubdashDatasource).pipe(catchError(handleDatasourceSrvError)); datasourceObservable = of(pubdashDatasource).pipe(catchError(handleDatasourceSrvError));
} else { } else {
@ -78,7 +79,7 @@ export class AnnotationsWorker implements DashboardQueryRunnerWorker {
annotation.snapshotData = cloneDeep(results); annotation.snapshotData = cloneDeep(results);
} }
// translate result // translate result
if (dashboard.meta.publicDashboardAccessToken) { if (config.publicDashboardAccessToken) {
return results; return results;
} else { } else {
return translateQueryResult(annotation, results); return translateQueryResult(annotation, results);

View File

@ -1,6 +1,7 @@
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { AlertState, getDefaultTimeRange, TimeRange } from '@grafana/data'; import { AlertState, getDefaultTimeRange, TimeRange } from '@grafana/data';
import { config } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; import { backendSrv } from 'app/core/services/backend_srv';
import { disableRBAC, enableRBAC, grantUserPermissions } from 'app/features/alerting/unified/mocks'; import { disableRBAC, enableRBAC, grantUserPermissions } from 'app/features/alerting/unified/mocks';
import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { Annotation } from 'app/features/alerting/unified/utils/constants';
@ -25,9 +26,7 @@ function getDefaultOptions(): DashboardQueryRunnerOptions {
id: 12345, id: 12345,
uid: 'a uid', uid: 'a uid',
}, },
{ {}
publicDashboardAccessToken: '',
}
); );
const range = getDefaultTimeRange(); const range = getDefaultTimeRange();
@ -46,6 +45,10 @@ function getTestContext() {
describe('UnifiedAlertStatesWorker', () => { describe('UnifiedAlertStatesWorker', () => {
const worker = new UnifiedAlertStatesWorker(); const worker = new UnifiedAlertStatesWorker();
beforeEach(() => {
config.publicDashboardAccessToken = '';
});
beforeAll(() => { beforeAll(() => {
disableRBAC(); disableRBAC();
}); });
@ -61,7 +64,7 @@ describe('UnifiedAlertStatesWorker', () => {
describe('when canWork is called on a public dashboard view', () => { describe('when canWork is called on a public dashboard view', () => {
it('then it should return false', () => { it('then it should return false', () => {
const options = getDefaultOptions(); const options = getDefaultOptions();
options.dashboard.meta.publicDashboardAccessToken = 'abc123'; config.publicDashboardAccessToken = 'abc123';
expect(worker.canWork(options)).toBe(false); expect(worker.canWork(options)).toBe(false);
}); });

View File

@ -2,7 +2,7 @@ import { from, Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { AlertState, AlertStateInfo } from '@grafana/data'; import { AlertState, AlertStateInfo } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { config, getBackendSrv } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { Annotation } from 'app/features/alerting/unified/utils/constants';
import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; import { isAlertingRule } from 'app/features/alerting/unified/utils/rules';
@ -24,7 +24,7 @@ export class UnifiedAlertStatesWorker implements DashboardQueryRunnerWorker {
} }
// Cannot fetch rules while on a public dashboard since it's unauthenticated // Cannot fetch rules while on a public dashboard since it's unauthenticated
if (dashboard.meta.publicDashboardAccessToken) { if (config.publicDashboardAccessToken) {
return false; return false;
} }

View File

@ -55,9 +55,7 @@ export function getDefaultOptions(): DashboardQueryRunnerOptions {
publish: jest.fn(), publish: jest.fn(),
}, },
panels: [{ alert: {} } as any], panels: [{ alert: {} } as any],
meta: { meta: {},
publicDashboardAccessToken: '',
},
}; };
const range = getDefaultTimeRange(); const range = getDefaultTimeRange();

View File

@ -29,7 +29,7 @@ import {
toDataFrame, toDataFrame,
transformDataFrame, transformDataFrame,
} from '@grafana/data'; } from '@grafana/data';
import { getTemplateSrv, toDataQueryError } from '@grafana/runtime'; import { config, getTemplateSrv, toDataQueryError } from '@grafana/runtime';
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
import { updatePanelDataWithASHFromLoki } from 'app/features/alerting/unified/components/rules/state-history/common'; import { updatePanelDataWithASHFromLoki } from 'app/features/alerting/unified/components/rules/state-history/common';
import { isStreamingDataFrame } from 'app/features/live/data/utils'; import { isStreamingDataFrame } from 'app/features/live/data/utils';
@ -51,7 +51,6 @@ export interface QueryRunnerOptions<
queries: TQuery[]; queries: TQuery[];
panelId?: number; panelId?: number;
dashboardUID?: string; dashboardUID?: string;
publicDashboardAccessToken?: string;
timezone: TimeZone; timezone: TimeZone;
timeRange: TimeRange; timeRange: TimeRange;
timeInfo?: string; // String description of time range for display timeInfo?: string; // String description of time range for display
@ -241,7 +240,6 @@ export class PanelQueryRunner {
datasource, datasource,
panelId, panelId,
dashboardUID, dashboardUID,
publicDashboardAccessToken,
timeRange, timeRange,
timeInfo, timeInfo,
cacheTimeout, cacheTimeout,
@ -263,7 +261,6 @@ export class PanelQueryRunner {
timezone, timezone,
panelId, panelId,
dashboardUID, dashboardUID,
publicDashboardAccessToken,
range: timeRange, range: timeRange,
timeInfo, timeInfo,
interval: '', interval: '',
@ -278,7 +275,7 @@ export class PanelQueryRunner {
}; };
try { try {
const ds = await getDataSource(datasource, request.scopedVars, publicDashboardAccessToken); const ds = await getDataSource(datasource, request.scopedVars);
const isMixedDS = ds.meta?.mixed; const isMixedDS = ds.meta?.mixed;
// Attach the data source to each query // Attach the data source to each query
@ -416,15 +413,14 @@ export class PanelQueryRunner {
async function getDataSource( async function getDataSource(
datasource: DataSourceRef | string | DataSourceApi | null, datasource: DataSourceRef | string | DataSourceApi | null,
scopedVars: ScopedVars, scopedVars: ScopedVars
publicDashboardAccessToken?: string
): Promise<DataSourceApi> { ): Promise<DataSourceApi> {
if (!publicDashboardAccessToken && datasource && typeof datasource === 'object' && 'query' in datasource) { if (!config.publicDashboardAccessToken && datasource && typeof datasource === 'object' && 'query' in datasource) {
return datasource; return datasource;
} }
const ds = await getDatasourceSrv().get(datasource, scopedVars); const ds = await getDatasourceSrv().get(datasource, scopedVars);
if (publicDashboardAccessToken) { if (config.publicDashboardAccessToken) {
return new PublicDashboardDataSource(ds); return new PublicDashboardDataSource(ds);
} }

View File

@ -190,7 +190,6 @@ describe('opentsdb', () => {
timezone: 'browser', timezone: 'browser',
panelId: 2, panelId: 2,
dashboardUID: 'tyzmfPIVz', dashboardUID: 'tyzmfPIVz',
publicDashboardAccessToken: '',
range: { range: {
from: dateTime('2022-10-19T08:55:18.430Z'), from: dateTime('2022-10-19T08:55:18.430Z'),
to: dateTime('2022-10-19T14:55:18.431Z'), to: dateTime('2022-10-19T14:55:18.431Z'),

View File

@ -49,7 +49,6 @@ export interface DashboardMeta {
fromFile?: boolean; fromFile?: boolean;
hasUnsavedFolderChange?: boolean; hasUnsavedFolderChange?: boolean;
annotationsPermissions?: AnnotationsPermissions; annotationsPermissions?: AnnotationsPermissions;
publicDashboardAccessToken?: string;
publicDashboardUid?: string; publicDashboardUid?: string;
publicDashboardEnabled?: boolean; publicDashboardEnabled?: boolean;
dashboardNotFound?: boolean; dashboardNotFound?: boolean;