mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
2245a3d0d1
commit
3ee26df41e
@ -147,7 +147,7 @@ export interface BootData {
|
||||
* @internal
|
||||
*/
|
||||
export interface GrafanaConfig {
|
||||
isPublicDashboardView: boolean;
|
||||
publicDashboardAccessToken?: string;
|
||||
snapshotEnabled: boolean;
|
||||
datasources: { [str: string]: DataSourceInstanceSettings };
|
||||
panels: { [key: string]: PanelPluginMeta };
|
||||
|
@ -530,7 +530,6 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
|
||||
timeInfo?: string; // The query time description (blue text in the upper right)
|
||||
panelId?: number;
|
||||
dashboardUID?: string;
|
||||
publicDashboardAccessToken?: string;
|
||||
|
||||
// Request Timing
|
||||
startTime: number;
|
||||
|
@ -33,7 +33,7 @@ export type AppPluginConfig = {
|
||||
};
|
||||
|
||||
export class GrafanaBootConfig implements GrafanaConfig {
|
||||
isPublicDashboardView: boolean;
|
||||
publicDashboardAccessToken?: string;
|
||||
snapshotEnabled = true;
|
||||
datasources: { [str: string]: DataSourceInstanceSettings } = {};
|
||||
panels: { [key: string]: PanelPluginMeta } = {};
|
||||
@ -166,7 +166,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
this.bootData = options.bootData;
|
||||
this.isPublicDashboardView = options.bootData.settings.isPublicDashboardView;
|
||||
|
||||
const defaults = {
|
||||
datasources: {},
|
||||
|
@ -165,7 +165,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// anonymous view public dashboard
|
||||
r.Get("/public-dashboards/:accessToken",
|
||||
publicdashboardsapi.SetPublicDashboardFlag,
|
||||
publicdashboardsapi.SetPublicDashboardAccessToken,
|
||||
publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService),
|
||||
publicdashboardsapi.CountPublicDashboardRequest(),
|
||||
hs.Index,
|
||||
|
@ -7,34 +7,33 @@ import (
|
||||
)
|
||||
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanAdmin bool `json:"canAdmin"`
|
||||
CanStar bool `json:"canStar"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
Slug string `json:"slug"`
|
||||
Url string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version int `json:"version"`
|
||||
HasACL bool `json:"hasAcl" xorm:"has_acl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
||||
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||
PublicDashboardUID string `json:"publicDashboardUid"`
|
||||
PublicDashboardEnabled bool `json:"publicDashboardEnabled"`
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanAdmin bool `json:"canAdmin"`
|
||||
CanStar bool `json:"canStar"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
Slug string `json:"slug"`
|
||||
Url string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version int `json:"version"`
|
||||
HasACL bool `json:"hasAcl" xorm:"has_acl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
||||
PublicDashboardUID string `json:"publicDashboardUid,omitempty"`
|
||||
PublicDashboardEnabled bool `json:"publicDashboardEnabled,omitempty"`
|
||||
}
|
||||
type AnnotationPermission struct {
|
||||
Dashboard AnnotationActions `json:"dashboard"`
|
||||
|
@ -217,7 +217,7 @@ type FrontendSettingsDTO struct {
|
||||
GeomapDefaultBaseLayerConfig *map[string]interface{} `json:"geomapDefaultBaseLayerConfig,omitempty"`
|
||||
GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"`
|
||||
|
||||
IsPublicDashboardView bool `json:"isPublicDashboardView"`
|
||||
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||
|
||||
DateFormats setting.DateFormats `json:"dateFormats,omitempty"`
|
||||
|
||||
|
@ -77,8 +77,6 @@ type MetricRequest struct {
|
||||
Queries []*simplejson.Json `json:"queries"`
|
||||
// required: false
|
||||
Debug bool `json:"debug"`
|
||||
|
||||
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||
}
|
||||
|
||||
func (mr *MetricRequest) GetUniqueDatasourceTypes() []string {
|
||||
|
@ -152,6 +152,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
DateFormats: hs.Cfg.DateFormats,
|
||||
SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI,
|
||||
DisableFrontendSandboxForPlugins: hs.Cfg.DisableFrontendSandboxForPlugins,
|
||||
PublicDashboardAccessToken: c.PublicDashboardAccessToken,
|
||||
|
||||
Auth: dtos.FrontendSettingsAuthDTO{
|
||||
OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
||||
@ -282,7 +283,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
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
|
||||
orgDataSources = dataSources
|
||||
} else {
|
||||
|
@ -29,8 +29,6 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings.IsPublicDashboardView = c.IsPublicDashboardView
|
||||
|
||||
prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.UserID, OrgID: c.OrgID, Teams: c.Teams}
|
||||
prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery)
|
||||
if err != nil {
|
||||
|
@ -61,19 +61,19 @@ func CopyWithReqContext(ctx context.Context) context.Context {
|
||||
Resp: web.NewResponseWriter(origReqCtx.Req.Method, response.CreateNormalResponse(http.Header{}, []byte{}, 0)),
|
||||
}
|
||||
reqCtx := &contextmodel.ReqContext{
|
||||
Context: webCtx,
|
||||
SignedInUser: origReqCtx.SignedInUser,
|
||||
UserToken: origReqCtx.UserToken,
|
||||
IsSignedIn: origReqCtx.IsSignedIn,
|
||||
IsRenderCall: origReqCtx.IsRenderCall,
|
||||
AllowAnonymous: origReqCtx.AllowAnonymous,
|
||||
SkipDSCache: origReqCtx.SkipDSCache,
|
||||
SkipQueryCache: origReqCtx.SkipQueryCache,
|
||||
Logger: origReqCtx.Logger,
|
||||
Error: origReqCtx.Error,
|
||||
RequestNonce: origReqCtx.RequestNonce,
|
||||
IsPublicDashboardView: origReqCtx.IsPublicDashboardView,
|
||||
LookupTokenErr: origReqCtx.LookupTokenErr,
|
||||
Context: webCtx,
|
||||
SignedInUser: origReqCtx.SignedInUser,
|
||||
UserToken: origReqCtx.UserToken,
|
||||
IsSignedIn: origReqCtx.IsSignedIn,
|
||||
IsRenderCall: origReqCtx.IsRenderCall,
|
||||
AllowAnonymous: origReqCtx.AllowAnonymous,
|
||||
SkipDSCache: origReqCtx.SkipDSCache,
|
||||
SkipQueryCache: origReqCtx.SkipQueryCache,
|
||||
Logger: origReqCtx.Logger,
|
||||
Error: origReqCtx.Error,
|
||||
RequestNonce: origReqCtx.RequestNonce,
|
||||
PublicDashboardAccessToken: origReqCtx.PublicDashboardAccessToken,
|
||||
LookupTokenErr: origReqCtx.LookupTokenErr,
|
||||
}
|
||||
return context.WithValue(ctx, reqContextKey{}, reqCtx)
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ type ReqContext struct {
|
||||
Logger log.Logger
|
||||
Error error
|
||||
// RequestNonce is a cryptographic request identifier for use with Content Security Policy.
|
||||
RequestNonce string
|
||||
IsPublicDashboardView bool
|
||||
RequestNonce string
|
||||
PublicDashboardAccessToken string
|
||||
|
||||
PerfmonTimer prometheus.Summary
|
||||
LookupTokenErr error
|
||||
@ -60,6 +60,10 @@ func (ctx *ReqContext) IsApiRequest() bool {
|
||||
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) {
|
||||
resp := make(map[string]interface{})
|
||||
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
|
||||
|
@ -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.ActionDashboardsRead), ac.EvalPermission(dashboards.ActionDashboardsCreate)),
|
||||
) {
|
||||
|
@ -46,16 +46,16 @@ func TestAlertingProxy_createProxyContext(t *testing.T) {
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{},
|
||||
UserToken: &auth.UserToken{},
|
||||
IsSignedIn: rand.Int63()%2 == 1,
|
||||
IsRenderCall: rand.Int63()%2 == 1,
|
||||
AllowAnonymous: rand.Int63()%2 == 1,
|
||||
SkipDSCache: rand.Int63()%2 == 1,
|
||||
SkipQueryCache: rand.Int63()%2 == 1,
|
||||
Logger: log.New("test"),
|
||||
RequestNonce: util.GenerateShortUID(),
|
||||
IsPublicDashboardView: rand.Int63()%2 == 1,
|
||||
SignedInUser: &user.SignedInUser{},
|
||||
UserToken: &auth.UserToken{},
|
||||
IsSignedIn: rand.Int63()%2 == 1,
|
||||
IsRenderCall: rand.Int63()%2 == 1,
|
||||
AllowAnonymous: rand.Int63()%2 == 1,
|
||||
SkipDSCache: rand.Int63()%2 == 1,
|
||||
SkipQueryCache: rand.Int63()%2 == 1,
|
||||
Logger: log.New("test"),
|
||||
RequestNonce: util.GenerateShortUID(),
|
||||
PublicDashboardAccessToken: util.GenerateShortUID(),
|
||||
}
|
||||
|
||||
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.Logger, newCtx.Logger)
|
||||
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) {
|
||||
|
@ -36,7 +36,7 @@ var ResourceCachingRequestHistogram = prometheus.NewHistogramVec(prometheus.Hist
|
||||
}, []string{"plugin_id", "cache"})
|
||||
|
||||
func getQueryType(req *contextmodel.ReqContext) string {
|
||||
if req.IsPublicDashboardView {
|
||||
if req.IsPublicDashboardView() {
|
||||
return QueryPubdash
|
||||
}
|
||||
return QueryDashboard
|
||||
|
@ -28,9 +28,9 @@ func SetPublicDashboardOrgIdOnContext(publicDashboardService publicdashboards.Se
|
||||
}
|
||||
}
|
||||
|
||||
// SetPublicDashboardFlag Adds public dashboard flag on context
|
||||
func SetPublicDashboardFlag(c *contextmodel.ReqContext) {
|
||||
c.IsPublicDashboardView = true
|
||||
// SetPublicDashboardAccessToken Adds public dashboard flag on context
|
||||
func SetPublicDashboardAccessToken(c *contextmodel.ReqContext) {
|
||||
c.PublicDashboardAccessToken = web.Params(c.Req)[":accessToken"]
|
||||
}
|
||||
|
||||
// RequiresExistingAccessToken Middleware to enforce that a public dashboards exists before continuing to handler. This
|
||||
|
@ -145,10 +145,10 @@ func TestSetPublicDashboardOrgIdOnContext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetPublicDashboardFlag(t *testing.T) {
|
||||
t.Run("Adds context.IsPublicDashboardView=true to request", func(t *testing.T) {
|
||||
ctx := &contextmodel.ReqContext{}
|
||||
SetPublicDashboardFlag(ctx)
|
||||
assert.True(t, ctx.IsPublicDashboardView)
|
||||
t.Run("Adds context.PublicDashboardAccessToken to request", func(t *testing.T) {
|
||||
ctx := &contextmodel.ReqContext{Context: &web.Context{Req: web.SetURLParams(&http.Request{}, map[string]string{":accessToken": "asdfasdfasdfsadfasdfsfd"})}}
|
||||
SetPublicDashboardAccessToken(ctx)
|
||||
assert.NotEmpty(t, ctx.PublicDashboardAccessToken)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -30,21 +30,21 @@ func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Respons
|
||||
}
|
||||
|
||||
meta := dtos.DashboardMeta{
|
||||
Slug: dash.Slug,
|
||||
Type: dashboards.DashTypeDB,
|
||||
CanStar: false,
|
||||
CanSave: false,
|
||||
CanEdit: false,
|
||||
CanAdmin: false,
|
||||
CanDelete: false,
|
||||
Created: dash.Created,
|
||||
Updated: dash.Updated,
|
||||
Version: dash.Version,
|
||||
IsFolder: false,
|
||||
FolderId: dash.FolderID,
|
||||
PublicDashboardAccessToken: pubdash.AccessToken,
|
||||
PublicDashboardEnabled: pubdash.IsEnabled,
|
||||
Slug: dash.Slug,
|
||||
Type: dashboards.DashTypeDB,
|
||||
CanStar: false,
|
||||
CanSave: false,
|
||||
CanEdit: false,
|
||||
CanAdmin: false,
|
||||
CanDelete: false,
|
||||
Created: dash.Created,
|
||||
Updated: dash.Updated,
|
||||
Version: dash.Version,
|
||||
IsFolder: false,
|
||||
FolderId: dash.FolderID,
|
||||
PublicDashboardEnabled: pubdash.IsEnabled,
|
||||
}
|
||||
|
||||
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)
|
||||
|
||||
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
|
||||
|
@ -287,11 +287,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
queries := []*simplejson.Json{query1, query2}
|
||||
reqDTO := dtos.MetricRequest{
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
PublicDashboardAccessToken: "abc123",
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost:3000", nil)
|
||||
@ -351,11 +350,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
queries := []*simplejson.Json{query1, query2, query3}
|
||||
reqDTO := dtos.MetricRequest{
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
PublicDashboardAccessToken: "abc123",
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
// without query parameter
|
||||
@ -406,11 +404,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
|
||||
queries := []*simplejson.Json{query1, query2}
|
||||
|
||||
reqDTO := dtos.MetricRequest{
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
PublicDashboardAccessToken: "abc123",
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
res, err := tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO)
|
||||
@ -436,11 +433,10 @@ func TestQueryDataMultipleSources(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
queries := []*simplejson.Json{query1}
|
||||
reqDTO := dtos.MetricRequest{
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
PublicDashboardAccessToken: "abc123",
|
||||
From: "2022-01-01",
|
||||
To: "2022-01-02",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
_, err = tc.queryService.QueryData(context.Background(), tc.signedInUser, true, reqDTO)
|
||||
|
@ -200,7 +200,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
timeRange: this.range,
|
||||
maxDataPoints: panel.maxDataPoints || this.width,
|
||||
minInterval: panel.interval,
|
||||
publicDashboardAccessToken: this.dashboard.meta.publicDashboardAccessToken,
|
||||
scopedVars: panel.scopedVars,
|
||||
cacheTimeout: panel.cacheTimeout,
|
||||
queryCachingTTL: panel.queryCachingTTL,
|
||||
|
@ -57,7 +57,6 @@ export function executeAnnotationQuery(
|
||||
scopedVars,
|
||||
...interval,
|
||||
app: CoreApp.Dashboard,
|
||||
publicDashboardAccessToken: options.dashboard.meta.publicDashboardAccessToken,
|
||||
|
||||
timezone: options.dashboard.timezone,
|
||||
|
||||
|
@ -133,7 +133,7 @@ export const publicDashboardEventNames: AnnotationFieldInfo[] = [
|
||||
// Given legacy infrastructure, alert events are passed though the same annotation
|
||||
// pipeline, but include fields that should not be exposed generally
|
||||
const alertEventAndAnnotationFields: AnnotationFieldInfo[] = [
|
||||
...(config.isPublicDashboardView ? publicDashboardEventNames : []),
|
||||
...(config.publicDashboardAccessToken ? publicDashboardEventNames : []),
|
||||
...annotationEventNames,
|
||||
{ key: 'userId' },
|
||||
{ key: 'login' },
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { createEmptyQueryResponse } from '../../../explore/state/utils';
|
||||
import { PanelModel } from '../../state';
|
||||
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
|
||||
@ -17,9 +19,10 @@ let panelModel = new PanelModel({
|
||||
let panelData = createEmptyQueryResponse();
|
||||
|
||||
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', () => {
|
||||
window.history.pushState({}, 'Test Title', '/public-dashboards/abc123');
|
||||
config.publicDashboardAccessToken = 'abc123';
|
||||
|
||||
render(
|
||||
<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', () => {
|
||||
const dashboardModel = createDashboardModelFixture({}, { publicDashboardAccessToken: '' });
|
||||
const dashboardModel = createDashboardModelFixture({}, {});
|
||||
window.history.pushState({}, 'Test Title', '/d/abc/123');
|
||||
config.publicDashboardAccessToken = '';
|
||||
|
||||
render(
|
||||
<PanelHeader panel={panelModel} dashboard={dashboardModel} isViewing={false} isEditing={false} data={panelData} />
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
|
||||
import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
|
||||
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 { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
@ -65,7 +65,7 @@ export function PanelHeader({ panel, error, isViewing, isEditing, data, alertSta
|
||||
/>
|
||||
) : null}
|
||||
<h2 className={styles.titleText}>{title}</h2>
|
||||
{!dashboard.meta.publicDashboardAccessToken && (
|
||||
{!config.publicDashboardAccessToken && (
|
||||
<div data-testid="panel-dropdown">
|
||||
<Icon name="angle-down" className="panel-menu-toggle" />
|
||||
{panelMenuOpen ? <PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} /> : null}
|
||||
|
@ -353,7 +353,6 @@ export class PanelStateWrapper extends PureComponent<Props, State> {
|
||||
panel.runAllPanelQueries({
|
||||
dashboardUID: dashboard.uid,
|
||||
dashboardTimezone: dashboard.getTimezone(),
|
||||
publicDashboardAccessToken: dashboard.meta.publicDashboardAccessToken,
|
||||
timeData,
|
||||
width,
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { of } from 'rxjs';
|
||||
|
||||
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 { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||
|
||||
@ -45,41 +45,14 @@ describe('PublicDashboardDatasource', () => {
|
||||
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 () => {
|
||||
mockDatasourceRequest.mockReset();
|
||||
mockDatasourceRequest.mockReturnValue(Promise.resolve([]));
|
||||
|
||||
const ds = new PublicDashboardDataSource('public');
|
||||
const panelId = 1;
|
||||
const publicDashboardAccessToken = 'abc123';
|
||||
|
||||
config.publicDashboardAccessToken = 'abc123';
|
||||
|
||||
await ds.query({
|
||||
maxDataPoints: 10,
|
||||
@ -92,14 +65,13 @@ describe('PublicDashboardDatasource', () => {
|
||||
},
|
||||
],
|
||||
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(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', () => {
|
||||
@ -108,7 +80,7 @@ describe('PublicDashboardDatasource', () => {
|
||||
|
||||
const ds = new PublicDashboardDataSource('public');
|
||||
const panelId = 1;
|
||||
const publicDashboardAccessToken = 'abc123';
|
||||
config.publicDashboardAccessToken = 'abc123';
|
||||
|
||||
ds.query({
|
||||
maxDataPoints: 10,
|
||||
@ -123,15 +95,12 @@ describe('PublicDashboardDatasource', () => {
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
publicDashboardAccessToken,
|
||||
} as DataQueryRequest);
|
||||
|
||||
const mock = mockDatasourceRequest.mock;
|
||||
|
||||
expect(mock.calls.length).toBe(1);
|
||||
expect(mock.lastCall[0].url).toEqual(
|
||||
`/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`
|
||||
);
|
||||
expect(mock.lastCall[0].url).toEqual(`/api/public/dashboards/abc123/panels/${panelId}/query`);
|
||||
});
|
||||
|
||||
test('returns public datasource uid when datasource passed in is null', () => {
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
DataSourceRef,
|
||||
toDataFrame,
|
||||
} 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 { MIXED_DATASOURCE_NAME } from '../../../plugins/datasource/mixed/MixedDataSource';
|
||||
@ -85,7 +85,6 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
|
||||
intervalMs,
|
||||
maxDataPoints,
|
||||
requestId,
|
||||
publicDashboardAccessToken,
|
||||
panelId,
|
||||
queryCachingTTL,
|
||||
range: { from: fromRange, to: toRange },
|
||||
@ -120,7 +119,7 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
|
||||
|
||||
return getBackendSrv()
|
||||
.fetch<BackendDataSourceResponse>({
|
||||
url: `/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`,
|
||||
url: `/api/public/dashboards/${config.publicDashboardAccessToken!}/panels/${panelId}/query`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
requestId,
|
||||
@ -138,7 +137,6 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
|
||||
|
||||
async getAnnotations(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> {
|
||||
const {
|
||||
publicDashboardAccessToken: accessToken,
|
||||
range: { to, from },
|
||||
} = request;
|
||||
|
||||
@ -147,9 +145,10 @@ export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSour
|
||||
to: to.valueOf(),
|
||||
};
|
||||
|
||||
const annotations = accessToken
|
||||
? await getBackendSrv().get(`/api/public/dashboards/${accessToken}/annotations`, params)
|
||||
: [];
|
||||
const annotations = await getBackendSrv().get(
|
||||
`/api/public/dashboards/${config.publicDashboardAccessToken!}/annotations`,
|
||||
params
|
||||
);
|
||||
|
||||
return { data: [toDataFrame(annotations)] };
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ describe('timeSrv', () => {
|
||||
};
|
||||
|
||||
locationService.push('/d/id?from=now-24h&to=now');
|
||||
config.isPublicDashboardView = true;
|
||||
config.publicDashboardAccessToken = 'abc123';
|
||||
timeSrv = new TimeSrv(new ContextSrvStub());
|
||||
});
|
||||
|
||||
|
@ -149,7 +149,7 @@ export class TimeSrv {
|
||||
}
|
||||
|
||||
private initTimeFromUrl() {
|
||||
if (config.isPublicDashboardView && this.timeModel?.timepicker?.hidden) {
|
||||
if (config.publicDashboardAccessToken && this.timeModel?.timepicker?.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -359,19 +359,12 @@ export class PanelModel implements DataConfigSource, IPanelModel {
|
||||
this.render();
|
||||
}
|
||||
|
||||
runAllPanelQueries({
|
||||
dashboardUID,
|
||||
dashboardTimezone,
|
||||
timeData,
|
||||
width,
|
||||
publicDashboardAccessToken,
|
||||
}: RunPanelQueryOptions) {
|
||||
runAllPanelQueries({ dashboardUID, dashboardTimezone, timeData, width }: RunPanelQueryOptions) {
|
||||
this.getQueryRunner().run({
|
||||
datasource: this.datasource,
|
||||
queries: this.targets,
|
||||
panelId: this.id,
|
||||
dashboardUID: dashboardUID,
|
||||
publicDashboardAccessToken,
|
||||
timezone: dashboardTimezone,
|
||||
timeRange: timeData.timeRange,
|
||||
timeInfo: timeData.timeInfo,
|
||||
|
@ -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', () => {
|
||||
it('then it should return false', () => {
|
||||
const dashboard = { annotations: { list: [] } } as unknown as DashboardModel;
|
||||
|
@ -3,8 +3,7 @@ import { from, merge, Observable, of } from 'rxjs';
|
||||
import { catchError, filter, finalize, map, mergeAll, mergeMap, reduce, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { AnnotationQuery, DataSourceApi } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events';
|
||||
import { PUBLIC_DATASOURCE, PublicDashboardDataSource } from '../../../dashboard/services/PublicDashboardDataSource';
|
||||
@ -41,14 +40,16 @@ export class AnnotationsWorker implements DashboardQueryRunnerWorker {
|
||||
|
||||
const { dashboard, range } = options;
|
||||
let annotations = dashboard.annotations.list.filter(AnnotationsWorker.getAnnotationsToProcessFilter);
|
||||
|
||||
// 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]];
|
||||
}
|
||||
|
||||
const observables = annotations.map((annotation) => {
|
||||
let datasourceObservable;
|
||||
|
||||
if (getConfig().isPublicDashboardView) {
|
||||
if (config.publicDashboardAccessToken) {
|
||||
const pubdashDatasource = new PublicDashboardDataSource(PUBLIC_DATASOURCE);
|
||||
datasourceObservable = of(pubdashDatasource).pipe(catchError(handleDatasourceSrvError));
|
||||
} else {
|
||||
@ -78,7 +79,7 @@ export class AnnotationsWorker implements DashboardQueryRunnerWorker {
|
||||
annotation.snapshotData = cloneDeep(results);
|
||||
}
|
||||
// translate result
|
||||
if (dashboard.meta.publicDashboardAccessToken) {
|
||||
if (config.publicDashboardAccessToken) {
|
||||
return results;
|
||||
} else {
|
||||
return translateQueryResult(annotation, results);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { AlertState, getDefaultTimeRange, TimeRange } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { disableRBAC, enableRBAC, grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
||||
import { Annotation } from 'app/features/alerting/unified/utils/constants';
|
||||
@ -25,9 +26,7 @@ function getDefaultOptions(): DashboardQueryRunnerOptions {
|
||||
id: 12345,
|
||||
uid: 'a uid',
|
||||
},
|
||||
{
|
||||
publicDashboardAccessToken: '',
|
||||
}
|
||||
{}
|
||||
);
|
||||
const range = getDefaultTimeRange();
|
||||
|
||||
@ -46,6 +45,10 @@ function getTestContext() {
|
||||
describe('UnifiedAlertStatesWorker', () => {
|
||||
const worker = new UnifiedAlertStatesWorker();
|
||||
|
||||
beforeEach(() => {
|
||||
config.publicDashboardAccessToken = '';
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
disableRBAC();
|
||||
});
|
||||
@ -61,7 +64,7 @@ describe('UnifiedAlertStatesWorker', () => {
|
||||
describe('when canWork is called on a public dashboard view', () => {
|
||||
it('then it should return false', () => {
|
||||
const options = getDefaultOptions();
|
||||
options.dashboard.meta.publicDashboardAccessToken = 'abc123';
|
||||
config.publicDashboardAccessToken = 'abc123';
|
||||
|
||||
expect(worker.canWork(options)).toBe(false);
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { from, Observable } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
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 { Annotation } from 'app/features/alerting/unified/utils/constants';
|
||||
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
|
||||
if (dashboard.meta.publicDashboardAccessToken) {
|
||||
if (config.publicDashboardAccessToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -55,9 +55,7 @@ export function getDefaultOptions(): DashboardQueryRunnerOptions {
|
||||
publish: jest.fn(),
|
||||
},
|
||||
panels: [{ alert: {} } as any],
|
||||
meta: {
|
||||
publicDashboardAccessToken: '',
|
||||
},
|
||||
meta: {},
|
||||
};
|
||||
const range = getDefaultTimeRange();
|
||||
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
toDataFrame,
|
||||
transformDataFrame,
|
||||
} 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 { updatePanelDataWithASHFromLoki } from 'app/features/alerting/unified/components/rules/state-history/common';
|
||||
import { isStreamingDataFrame } from 'app/features/live/data/utils';
|
||||
@ -51,7 +51,6 @@ export interface QueryRunnerOptions<
|
||||
queries: TQuery[];
|
||||
panelId?: number;
|
||||
dashboardUID?: string;
|
||||
publicDashboardAccessToken?: string;
|
||||
timezone: TimeZone;
|
||||
timeRange: TimeRange;
|
||||
timeInfo?: string; // String description of time range for display
|
||||
@ -241,7 +240,6 @@ export class PanelQueryRunner {
|
||||
datasource,
|
||||
panelId,
|
||||
dashboardUID,
|
||||
publicDashboardAccessToken,
|
||||
timeRange,
|
||||
timeInfo,
|
||||
cacheTimeout,
|
||||
@ -263,7 +261,6 @@ export class PanelQueryRunner {
|
||||
timezone,
|
||||
panelId,
|
||||
dashboardUID,
|
||||
publicDashboardAccessToken,
|
||||
range: timeRange,
|
||||
timeInfo,
|
||||
interval: '',
|
||||
@ -278,7 +275,7 @@ export class PanelQueryRunner {
|
||||
};
|
||||
|
||||
try {
|
||||
const ds = await getDataSource(datasource, request.scopedVars, publicDashboardAccessToken);
|
||||
const ds = await getDataSource(datasource, request.scopedVars);
|
||||
const isMixedDS = ds.meta?.mixed;
|
||||
|
||||
// Attach the data source to each query
|
||||
@ -416,15 +413,14 @@ export class PanelQueryRunner {
|
||||
|
||||
async function getDataSource(
|
||||
datasource: DataSourceRef | string | DataSourceApi | null,
|
||||
scopedVars: ScopedVars,
|
||||
publicDashboardAccessToken?: string
|
||||
scopedVars: ScopedVars
|
||||
): Promise<DataSourceApi> {
|
||||
if (!publicDashboardAccessToken && datasource && typeof datasource === 'object' && 'query' in datasource) {
|
||||
if (!config.publicDashboardAccessToken && datasource && typeof datasource === 'object' && 'query' in datasource) {
|
||||
return datasource;
|
||||
}
|
||||
|
||||
const ds = await getDatasourceSrv().get(datasource, scopedVars);
|
||||
if (publicDashboardAccessToken) {
|
||||
if (config.publicDashboardAccessToken) {
|
||||
return new PublicDashboardDataSource(ds);
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,6 @@ describe('opentsdb', () => {
|
||||
timezone: 'browser',
|
||||
panelId: 2,
|
||||
dashboardUID: 'tyzmfPIVz',
|
||||
publicDashboardAccessToken: '',
|
||||
range: {
|
||||
from: dateTime('2022-10-19T08:55:18.430Z'),
|
||||
to: dateTime('2022-10-19T14:55:18.431Z'),
|
||||
|
@ -49,7 +49,6 @@ export interface DashboardMeta {
|
||||
fromFile?: boolean;
|
||||
hasUnsavedFolderChange?: boolean;
|
||||
annotationsPermissions?: AnnotationsPermissions;
|
||||
publicDashboardAccessToken?: string;
|
||||
publicDashboardUid?: string;
|
||||
publicDashboardEnabled?: boolean;
|
||||
dashboardNotFound?: boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user