mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Public Dashboards: Query Caching (#51403)
* passes id and uid to PublicDashboardDatasource * betterer results * If for a public dashboard, return the PublicDashboardDataSource first or else getDatasourceSrv.get() will fail bc of no authed user. Added some unit tests for resolving the uid from the many possible datasource types. * updates betterer * Exports DashboardService. Adds method to DashboardService to build anonymous user for use with public dashboards where there is no authed user. Adds method on dashboard_queries to get all dashboard uids from a dashboard. * refactors to get unique datasource uids * Adds tests for getting all unique datasource uids off a dashboard * adds test for building anonymous user with read and query actions that are scoped to each datasource uid in the dashboard * updates casing of DashboardService * updates test case to have additional panel with a different datasource * gives default interval to public dashboard data source
This commit is contained in:
parent
9941e06e22
commit
0b4af38bfa
@ -131,6 +131,37 @@ exports[`better eslint`] = {
|
||||
"e2e/dashboards-suite/dashboard-templating.spec.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"packages/grafana-data/src/types/datasource.ts:1730680024": [
|
||||
[24, 85, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[27, 73, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[46, 28, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[51, 26, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[56, 47, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[99, 46, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[109, 48, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[151, 14, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[152, 25, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[153, 24, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[172, 72, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[246, 37, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[260, 41, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[260, 57, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[270, 26, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[270, 41, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[275, 24, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[280, 25, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[317, 21, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[319, 32, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[382, 14, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[408, 14, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[414, 58, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[608, 13, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[618, 37, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[618, 42, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[619, 43, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[619, 59, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[625, 46, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[626, 22, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
"e2e/dashboards-suite/textbox-variables.spec.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
@ -693,6 +724,11 @@ exports[`better eslint`] = {
|
||||
"packages/grafana-data/src/types/flot.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts:2092121707": [
|
||||
[14, 19, 123, "Do not use any type assertions.", "3028355264"],
|
||||
[14, 19, 109, "Do not use any type assertions.", "4248357345"],
|
||||
[39, 13, 206, "Do not use any type assertions.", "1200376833"],
|
||||
[72, 49, 54, "Do not use any type assertions.", "114713672"]
|
||||
"packages/grafana-data/src/types/graph.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
@ -4333,6 +4369,13 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
|
||||
],
|
||||
"public/app/features/dashboard/services/PublicDashboardDataSource.ts:102072381": [
|
||||
[14, 61, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[20, 12, 16, "Do not use any type assertions.", "1747412709"],
|
||||
[43, 34, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[61, 16, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[78, 45, 22, "Do not use any type assertions.", "1838499175"],
|
||||
[86, 28, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
"public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
@ -5639,6 +5682,17 @@ exports[`better eslint`] = {
|
||||
"public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/query/state/PanelQueryRunner.ts:3311920590": [
|
||||
[110, 39, 118, "Do not use any type assertions.", "2873233307"],
|
||||
[189, 46, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[238, 5, 14, "Do not use any type assertions.", "4095749936"],
|
||||
[238, 16, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[285, 20, 46, "Do not use any type assertions.", "1712789723"],
|
||||
[285, 20, 32, "Do not use any type assertions.", "2220885232"],
|
||||
[367, 21, 17, "Do not use any type assertions.", "1733699692"],
|
||||
[367, 35, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[368, 11, 27, "Do not use any type assertions.", "2133479311"],
|
||||
[371, 38, 20, "Do not use any type assertions.", "340150831"]
|
||||
"public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
|
@ -483,7 +483,6 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
|
||||
timeInfo?: string; // The query time description (blue text in the upper right)
|
||||
panelId?: number;
|
||||
dashboardId?: number;
|
||||
// Temporary prop for public dashboards, to be replaced by publicAccessKey
|
||||
publicDashboardAccessToken?: string;
|
||||
|
||||
// Request Timing
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { of } from 'rxjs';
|
||||
import { BackendSrv, BackendSrvRequest } from 'src/services';
|
||||
|
||||
import { DataQueryRequest, DataSourceRef } from '@grafana/data';
|
||||
import { DataQueryRequest, DataSourceInstanceSettings, DataSourceRef } from '@grafana/data';
|
||||
|
||||
import { PublicDashboardDataSource } from '../../../../public/app/features/dashboard/services/PublicDashboardDataSource';
|
||||
import {
|
||||
PUBLIC_DATASOURCE,
|
||||
PublicDashboardDataSource,
|
||||
} from '../../../../public/app/features/dashboard/services/PublicDashboardDataSource';
|
||||
|
||||
import { DataSourceWithBackend } from './DataSourceWithBackend';
|
||||
|
||||
const mockDatasourceRequest = jest.fn();
|
||||
|
||||
@ -28,7 +33,7 @@ describe('PublicDashboardDatasource', () => {
|
||||
mockDatasourceRequest.mockReset();
|
||||
mockDatasourceRequest.mockReturnValue(Promise.resolve({}));
|
||||
|
||||
const ds = new PublicDashboardDataSource();
|
||||
const ds = new PublicDashboardDataSource('public');
|
||||
const panelId = 1;
|
||||
const publicDashboardAccessToken = 'abc123';
|
||||
|
||||
@ -47,4 +52,27 @@ describe('PublicDashboardDatasource', () => {
|
||||
`/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`
|
||||
);
|
||||
});
|
||||
|
||||
test('returns public datasource uid when datasource passed in is null', () => {
|
||||
let ds = new PublicDashboardDataSource(null);
|
||||
expect(ds.uid).toBe(PUBLIC_DATASOURCE);
|
||||
});
|
||||
|
||||
test('returns datasource when datasource passed in is a string', () => {
|
||||
let ds = new PublicDashboardDataSource('theDatasourceUid');
|
||||
expect(ds.uid).toBe('theDatasourceUid');
|
||||
});
|
||||
|
||||
test('returns datasource uid when datasource passed in is a DataSourceRef implementation', () => {
|
||||
const datasource = { type: 'datasource', uid: 'abc123' };
|
||||
let ds = new PublicDashboardDataSource(datasource);
|
||||
expect(ds.uid).toBe('abc123');
|
||||
});
|
||||
|
||||
test('returns datasource uid when datasource passed in is a DatasourceApi instance', () => {
|
||||
const settings: DataSourceInstanceSettings = { id: 1, uid: 'abc123' } as DataSourceInstanceSettings;
|
||||
const datasource = new DataSourceWithBackend(settings);
|
||||
let ds = new PublicDashboardDataSource(datasource);
|
||||
expect(ds.uid).toBe('abc123');
|
||||
});
|
||||
});
|
||||
|
@ -53,7 +53,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response {
|
||||
item.DashboardUID = val
|
||||
} else {
|
||||
query := models.GetDashboardQuery{Id: item.DashboardId, OrgId: c.OrgId}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
if err == nil && query.Result != nil {
|
||||
item.DashboardUID = &query.Result.Uid
|
||||
dashboardCache[item.DashboardId] = &query.Result.Uid
|
||||
@ -82,7 +82,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response {
|
||||
// overwrite dashboardId when dashboardUID is not empty
|
||||
if cmd.DashboardUID != "" {
|
||||
query := models.GetDashboardQuery{OrgId: c.OrgId, Uid: cmd.DashboardUID}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
if err == nil {
|
||||
cmd.DashboardId = query.Result.Id
|
||||
}
|
||||
@ -291,7 +291,7 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo
|
||||
|
||||
if cmd.DashboardUID != "" {
|
||||
query := models.GetDashboardQuery{OrgId: c.OrgId, Uid: cmd.DashboardUID}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
if err == nil {
|
||||
cmd.DashboardId = query.Result.Id
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
hs := setupSimpleHTTPServer(nil)
|
||||
hs.SQLStore = store
|
||||
hs.dashboardService = dashSvc
|
||||
hs.DashboardService = dashSvc
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||
@ -428,7 +428,7 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
hs := setupSimpleHTTPServer(nil)
|
||||
hs.SQLStore = store
|
||||
hs.dashboardService = dashSvc
|
||||
hs.DashboardService = dashSvc
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||
|
@ -28,7 +28,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
|
||||
reqEditorRole := middleware.ReqEditorRole
|
||||
reqOrgAdmin := middleware.ReqOrgAdmin
|
||||
reqOrgAdminDashOrFolderAdminOrTeamAdmin := middleware.OrgAdminDashOrFolderAdminOrTeamAdmin(hs.SQLStore, hs.dashboardService)
|
||||
reqOrgAdminDashOrFolderAdminOrTeamAdmin := middleware.OrgAdminDashOrFolderAdminOrTeamAdmin(hs.SQLStore, hs.DashboardService)
|
||||
reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin)
|
||||
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
|
||||
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
|
||||
|
@ -396,7 +396,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
|
||||
AccessControl: ac,
|
||||
teamPermissionsService: teamPermissionService,
|
||||
searchUsersService: searchusers.ProvideUsersService(db, filters.ProvideOSSSearchUserFilter()),
|
||||
dashboardService: dashboardservice.ProvideDashboardService(
|
||||
DashboardService: dashboardservice.ProvideDashboardService(
|
||||
cfg, dashboardsStore, nil, features,
|
||||
accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), ac,
|
||||
),
|
||||
|
@ -144,7 +144,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
// lookup folder title
|
||||
if dash.FolderId > 0 {
|
||||
query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
|
||||
if err := hs.dashboardService.GetDashboard(c.Req.Context(), &query); err != nil {
|
||||
if err := hs.DashboardService.GetDashboard(c.Req.Context(), &query); err != nil {
|
||||
if errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
return response.Error(404, "Folder not found", err)
|
||||
}
|
||||
@ -235,7 +235,7 @@ func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id in
|
||||
query = models.GetDashboardQuery{Id: id, OrgId: orgID}
|
||||
}
|
||||
|
||||
if err := hs.dashboardService.GetDashboard(ctx, &query); err != nil {
|
||||
if err := hs.DashboardService.GetDashboard(ctx, &query); err != nil {
|
||||
return nil, response.Error(404, "Dashboard not found", err)
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
|
||||
hs.log.Error("Failed to disconnect library elements", "dashboard", dash.Id, "user", c.SignedInUser.UserId, "error", err)
|
||||
}
|
||||
|
||||
err = hs.dashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId)
|
||||
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId)
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
@ -387,7 +387,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
|
||||
Overwrite: cmd.Overwrite,
|
||||
}
|
||||
|
||||
dashboard, err := hs.dashboardService.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate)
|
||||
dashboard, err := hs.DashboardService.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate)
|
||||
|
||||
if dashboard != nil && hs.entityEventsService != nil {
|
||||
if err := hs.entityEventsService.SaveEvent(ctx, store.SaveEventCmd{
|
||||
@ -460,7 +460,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response {
|
||||
|
||||
if preference.HomeDashboardID != 0 {
|
||||
slugQuery := models.GetDashboardRefByIdQuery{Id: preference.HomeDashboardID}
|
||||
err := hs.dashboardService.GetDashboardUIDById(c.Req.Context(), &slugQuery)
|
||||
err := hs.DashboardService.GetDashboardUIDById(c.Req.Context(), &slugQuery)
|
||||
if err == nil {
|
||||
url := models.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
|
||||
dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
|
||||
@ -546,7 +546,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *models.ReqContext) response.Respon
|
||||
OrgId: c.SignedInUser.OrgId,
|
||||
Uid: dashUID,
|
||||
}
|
||||
if err := hs.dashboardService.GetDashboard(c.Req.Context(), &q); err != nil {
|
||||
if err := hs.DashboardService.GetDashboard(c.Req.Context(), &q); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "failed to get dashboard by UID", err)
|
||||
}
|
||||
dashID = q.Result.Id
|
||||
@ -605,7 +605,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *models.ReqContext) response.Respons
|
||||
OrgId: c.SignedInUser.OrgId,
|
||||
Uid: dashUID,
|
||||
}
|
||||
if err := hs.dashboardService.GetDashboard(c.Req.Context(), &q); err != nil {
|
||||
if err := hs.DashboardService.GetDashboard(c.Req.Context(), &q); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "failed to get dashboard by UID", err)
|
||||
}
|
||||
dashID = q.Result.Id
|
||||
@ -782,7 +782,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext) response.Res
|
||||
|
||||
func (hs *HTTPServer) GetDashboardTags(c *models.ReqContext) {
|
||||
query := models.GetDashboardTagsQuery{OrgId: c.OrgId}
|
||||
err := hs.dashboardService.GetDashboardTags(c.Req.Context(), &query)
|
||||
err := hs.DashboardService.GetDashboardTags(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get tags from database", err)
|
||||
return
|
||||
@ -803,7 +803,7 @@ func (hs *HTTPServer) GetDashboardUIDs(c *models.ReqContext) {
|
||||
continue
|
||||
}
|
||||
q.Id = id
|
||||
err = hs.dashboardService.GetDashboardUIDById(c.Req.Context(), q)
|
||||
err = hs.DashboardService.GetDashboardUIDById(c.Req.Context(), q)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.
|
||||
return response.Success("Dashboard permissions updated")
|
||||
}
|
||||
|
||||
if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), dashID, items); err != nil {
|
||||
if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), dashID, items); err != nil {
|
||||
if errors.Is(err, models.ErrDashboardAclInfoMissing) ||
|
||||
errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) {
|
||||
return response.Error(409, err.Error(), err)
|
||||
|
@ -39,7 +39,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
Cfg: settings,
|
||||
SQLStore: mockSQLStore,
|
||||
Features: features,
|
||||
dashboardService: dashboardservice.ProvideDashboardService(
|
||||
DashboardService: dashboardservice.ProvideDashboardService(
|
||||
settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac,
|
||||
),
|
||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response {
|
||||
accessToken := web.Params(c.Req)[":accessToken"]
|
||||
|
||||
dash, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), accessToken)
|
||||
dash, err := hs.DashboardService.GetPublicDashboard(c.Req.Context(), accessToken)
|
||||
if err != nil {
|
||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err)
|
||||
}
|
||||
@ -47,7 +47,7 @@ func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response
|
||||
|
||||
// gets public dashboard configuration for dashboard
|
||||
func (hs *HTTPServer) GetPublicDashboardConfig(c *models.ReqContext) response.Response {
|
||||
pdc, err := hs.dashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgId, web.Params(c.Req)[":uid"])
|
||||
pdc, err := hs.DashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgId, web.Params(c.Req)[":uid"])
|
||||
if err != nil {
|
||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard config", err)
|
||||
}
|
||||
@ -71,7 +71,7 @@ func (hs *HTTPServer) SavePublicDashboardConfig(c *models.ReqContext) response.R
|
||||
PublicDashboard: pubdash,
|
||||
}
|
||||
|
||||
pubdash, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
|
||||
pubdash, err := hs.DashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
|
||||
if err != nil {
|
||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err)
|
||||
}
|
||||
@ -87,17 +87,17 @@ func (hs *HTTPServer) QueryPublicDashboard(c *models.ReqContext) response.Respon
|
||||
return response.Error(http.StatusBadRequest, "invalid panel ID", err)
|
||||
}
|
||||
|
||||
dashboard, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), web.Params(c.Req)[":accessToken"])
|
||||
dashboard, err := hs.DashboardService.GetPublicDashboard(c.Req.Context(), web.Params(c.Req)[":accessToken"])
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "could not fetch dashboard", err)
|
||||
}
|
||||
|
||||
publicDashboard, err := hs.dashboardService.GetPublicDashboardConfig(c.Req.Context(), dashboard.OrgId, dashboard.Uid)
|
||||
publicDashboard, err := hs.DashboardService.GetPublicDashboardConfig(c.Req.Context(), dashboard.OrgId, dashboard.Uid)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "could not fetch public dashboard", err)
|
||||
}
|
||||
|
||||
reqDTO, err := hs.dashboardService.BuildPublicDashboardMetricRequest(
|
||||
reqDTO, err := hs.DashboardService.BuildPublicDashboardMetricRequest(
|
||||
c.Req.Context(),
|
||||
dashboard,
|
||||
publicDashboard,
|
||||
|
@ -38,7 +38,7 @@ func TestAPIGetPublicDashboard(t *testing.T) {
|
||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||
dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).
|
||||
Return(&models.Dashboard{}, nil).Maybe()
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
response := callAPI(
|
||||
@ -97,7 +97,7 @@ func TestAPIGetPublicDashboard(t *testing.T) {
|
||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||
dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).
|
||||
Return(test.publicDashboardResult, test.publicDashboardErr)
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
response := callAPI(
|
||||
@ -170,7 +170,7 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
|
||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||
dashSvc.On("GetPublicDashboardConfig", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
|
||||
Return(test.PublicDashboardResult, test.PublicDashboardError)
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
response := callAPI(
|
||||
@ -229,7 +229,7 @@ func TestApiSavePublicDashboardConfig(t *testing.T) {
|
||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||
dashSvc.On("SavePublicDashboardConfig", mock.Anything, mock.AnythingOfType("*dashboards.SavePublicDashboardConfigDTO")).
|
||||
Return(&models.PublicDashboard{IsEnabled: true}, test.saveDashboardError)
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
response := callAPI(
|
||||
@ -298,7 +298,7 @@ func TestAPIQueryPublicDashboard(t *testing.T) {
|
||||
return SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.queryDataService = qds
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards, enabled)
|
||||
hs.dashboardService = fakeDashboardService
|
||||
hs.DashboardService = fakeDashboardService
|
||||
}), fakeDashboardService
|
||||
}
|
||||
|
||||
@ -593,7 +593,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
},
|
||||
}
|
||||
|
||||
pubdash, err := scenario.hs.dashboardService.SavePublicDashboardConfig(context.Background(), savePubDashboardCmd)
|
||||
pubdash, err := scenario.hs.DashboardService.SavePublicDashboardConfig(context.Background(), savePubDashboardCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
response := callAPI(
|
||||
|
@ -139,7 +139,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
dashboardVersionService: fakeDashboardVersionService,
|
||||
}
|
||||
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
|
||||
@ -259,7 +259,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
dashboardVersionService: fakeDashboardVersionService,
|
||||
}
|
||||
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
|
||||
@ -900,7 +900,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
dashboardProvisioningService: mockDashboardProvisioningService{},
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
}
|
||||
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
|
||||
hs.callGetDashboard(sc)
|
||||
@ -954,7 +954,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
cfg, dashboardStore, nil, features,
|
||||
folderPermissions, dashboardPermissions, ac,
|
||||
),
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
}
|
||||
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
|
||||
|
||||
@ -986,7 +986,7 @@ func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) {
|
||||
|
||||
func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T,
|
||||
sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) {
|
||||
hs.dashboardService = mockDashboard
|
||||
hs.DashboardService = mockDashboard
|
||||
sc.handlerFunc = hs.DeleteDashboardByUID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
@ -1018,7 +1018,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
|
||||
pluginStore: &fakePluginStore{},
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
folderService: folderService,
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
}
|
||||
@ -1088,7 +1088,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
|
||||
QuotaService: "a.QuotaService{Cfg: cfg},
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
dashboardService: mock,
|
||||
DashboardService: mock,
|
||||
SQLStore: sqlStore,
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
dashboardVersionService: fakeDashboardVersionService,
|
||||
|
@ -70,6 +70,8 @@ type MetricRequest struct {
|
||||
// required: false
|
||||
Debug bool `json:"debug"`
|
||||
|
||||
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
|
||||
return response.Success("Dashboard permissions updated")
|
||||
}
|
||||
|
||||
if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil {
|
||||
if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil {
|
||||
if errors.Is(err, models.ErrDashboardAclInfoMissing) {
|
||||
err = models.ErrFolderAclInfoMissing
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
folderService: folderService,
|
||||
folderPermissionsService: folderPermissions,
|
||||
dashboardPermissionsService: dashboardPermissions,
|
||||
dashboardService: service.ProvideDashboardService(
|
||||
DashboardService: service.ProvideDashboardService(
|
||||
settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac,
|
||||
),
|
||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||
|
@ -148,7 +148,7 @@ type HTTPServer struct {
|
||||
authenticator loginpkg.Authenticator
|
||||
teamPermissionsService accesscontrol.TeamPermissionsService
|
||||
NotificationService *notifications.NotificationService
|
||||
dashboardService dashboards.DashboardService
|
||||
DashboardService dashboards.DashboardService
|
||||
dashboardProvisioningService dashboards.DashboardProvisioningService
|
||||
folderService dashboards.FolderService
|
||||
DatasourcePermissionsService permissions.DatasourcePermissionsService
|
||||
@ -267,7 +267,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
authInfoService: authInfoService,
|
||||
authenticator: authenticator,
|
||||
NotificationService: notificationService,
|
||||
dashboardService: dashboardService,
|
||||
DashboardService: dashboardService,
|
||||
dashboardProvisioningService: dashboardProvisioningService,
|
||||
folderService: folderService,
|
||||
DatasourcePermissionsService: datasourcePermissionsService,
|
||||
|
@ -407,7 +407,7 @@ func (hs *HTTPServer) buildStarredItemsNavLinks(c *models.ReqContext, prefs *pre
|
||||
Id: dashboardId,
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), query)
|
||||
if err == nil {
|
||||
starredDashboards = append(starredDashboards, query.Result)
|
||||
}
|
||||
@ -674,7 +674,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
|
||||
|
||||
func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool {
|
||||
hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
|
||||
if err := hs.dashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
|
||||
if err := hs.DashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
|
||||
return false
|
||||
}
|
||||
return hasEditPermissionInFoldersQuery.Result
|
||||
|
@ -16,7 +16,7 @@ func (hs *HTTPServer) populateDashboardsByID(ctx context.Context, dashboardByIDs
|
||||
|
||||
if len(dashboardByIDs) > 0 {
|
||||
dashboardQuery := models.GetDashboardsQuery{DashboardIds: dashboardByIDs}
|
||||
if err := hs.dashboardService.GetDashboards(ctx, &dashboardQuery); err != nil {
|
||||
if err := hs.DashboardService.GetDashboards(ctx, &dashboardQuery); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ func (hs *HTTPServer) SetHomeDashboard(c *models.ReqContext) response.Response {
|
||||
dashboardID := cmd.HomeDashboardID
|
||||
if cmd.HomeDashboardUID != nil {
|
||||
query := models.GetDashboardQuery{Uid: *cmd.HomeDashboardUID}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
return response.Error(404, "Dashboard not found", err)
|
||||
}
|
||||
@ -65,7 +65,7 @@ func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, team
|
||||
// when homedashboardID is 0, that means it is the default home dashboard, no UID would be returned in the response
|
||||
if preference.HomeDashboardID != 0 {
|
||||
query := models.GetDashboardQuery{Id: preference.HomeDashboardID, OrgId: orgID}
|
||||
err = hs.dashboardService.GetDashboard(ctx, &query)
|
||||
err = hs.DashboardService.GetDashboard(ctx, &query)
|
||||
if err == nil {
|
||||
dashboardUID = query.Result.Uid
|
||||
}
|
||||
@ -105,7 +105,7 @@ func (hs *HTTPServer) updatePreferencesFor(ctx context.Context, orgID, userID, t
|
||||
dashboardID := dtoCmd.HomeDashboardID
|
||||
if dtoCmd.HomeDashboardUID != nil {
|
||||
query := models.GetDashboardQuery{Uid: *dtoCmd.HomeDashboardUID, OrgId: orgID}
|
||||
err := hs.dashboardService.GetDashboard(ctx, &query)
|
||||
err := hs.DashboardService.GetDashboard(ctx, &query)
|
||||
if err != nil {
|
||||
return response.Error(404, "Dashboard not found", err)
|
||||
}
|
||||
@ -151,7 +151,7 @@ func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, te
|
||||
dashboardID := dtoCmd.HomeDashboardID
|
||||
if dtoCmd.HomeDashboardUID != nil {
|
||||
query := models.GetDashboardQuery{Uid: *dtoCmd.HomeDashboardUID, OrgId: orgID}
|
||||
err := hs.dashboardService.GetDashboard(ctx, &query)
|
||||
err := hs.DashboardService.GetDashboard(ctx, &query)
|
||||
if err != nil {
|
||||
return response.Error(404, "Dashboard not found", err)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T)
|
||||
q.Result = &models.Dashboard{Uid: "home", Id: 1}
|
||||
}).Return(nil)
|
||||
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
|
||||
prefService := preftest.NewPreferenceServiceFake()
|
||||
prefService.ExpectedPreference = &pref.Preference{HomeDashboardID: 1, Theme: "dark"}
|
||||
@ -169,7 +169,7 @@ func TestAPIEndpoint_PatchUserPreferences(t *testing.T) {
|
||||
q := args.Get(1).(*models.GetDashboardQuery)
|
||||
q.Result = &models.Dashboard{Uid: "home", Id: 1}
|
||||
}).Return(nil)
|
||||
sc.hs.dashboardService = dashSvc
|
||||
sc.hs.DashboardService = dashSvc
|
||||
t.Run("Returns 200 on success", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPatch, patchUserPreferencesUrl, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
|
@ -26,7 +26,7 @@ func (hs *HTTPServer) GetStars(c *models.ReqContext) response.Response {
|
||||
Id: dashboardId,
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
err := hs.dashboardService.GetDashboard(c.Req.Context(), query)
|
||||
err := hs.DashboardService.GetDashboard(c.Req.Context(), query)
|
||||
|
||||
// Grafana admin users may have starred dashboards in multiple orgs. This will avoid returning errors when the dashboard is in another org
|
||||
if err == nil {
|
||||
|
@ -4,7 +4,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
)
|
||||
|
||||
func GetQueriesFromDashboard(dashboard *simplejson.Json) map[int64][]*simplejson.Json {
|
||||
func GetUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string {
|
||||
var datasourceUids []string
|
||||
exists := map[string]bool{}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return datasourceUids
|
||||
}
|
||||
|
||||
func GroupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.Json {
|
||||
result := make(map[int64][]*simplejson.Json)
|
||||
|
||||
for _, panelObj := range dashboard.Get("panels").MustArray() {
|
||||
|
@ -56,6 +56,79 @@ const (
|
||||
"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": [
|
||||
@ -79,12 +152,32 @@ const (
|
||||
}`
|
||||
)
|
||||
|
||||
func TestGetQueriesFromDashboard(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 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)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupQueriesByPanelId(t *testing.T) {
|
||||
t.Run("can extract no queries from empty dashboard", func(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(`{"panels": {}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GetQueriesFromDashboard(json)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 0)
|
||||
})
|
||||
|
||||
@ -92,7 +185,7 @@ func TestGetQueriesFromDashboard(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithNoQueries))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GetQueriesFromDashboard(json)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 0)
|
||||
@ -102,7 +195,7 @@ func TestGetQueriesFromDashboard(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(dashboardWithQueries))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GetQueriesFromDashboard(json)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 2)
|
||||
@ -138,7 +231,7 @@ func TestGetQueriesFromDashboard(t *testing.T) {
|
||||
json, err := simplejson.NewJson([]byte(oldStyleDashboard))
|
||||
require.NoError(t, err)
|
||||
|
||||
queries := GetQueriesFromDashboard(json)
|
||||
queries := GroupQueriesByPanelId(json)
|
||||
require.Len(t, queries, 1)
|
||||
require.Contains(t, queries, int64(2))
|
||||
require.Len(t, queries[2], 1)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
// DashboardService is a service for operating on dashboards.
|
||||
type DashboardService interface {
|
||||
BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error)
|
||||
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error)
|
||||
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
|
||||
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
||||
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
||||
|
@ -18,6 +18,29 @@ type FakeDashboardService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// BuildAnonymousUser provides a mock function with given fields: ctx, dashboard
|
||||
func (_m *FakeDashboardService) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
ret := _m.Called(ctx, dashboard)
|
||||
|
||||
var r0 *models.SignedInUser
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard) *models.SignedInUser); ok {
|
||||
r0 = rf(ctx, dashboard)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard) error); ok {
|
||||
r1 = rf(ctx, dashboard)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId
|
||||
func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||
ret := _m.Called(ctx, dashboard, publicDashboard, panelId)
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
// Gets public dashboard via access token
|
||||
@ -124,7 +125,7 @@ func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Co
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data)
|
||||
queriesByPanel := models.GroupQueriesByPanelId(dashboard.Data)
|
||||
|
||||
if _, ok := queriesByPanel[panelId]; !ok {
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardPanelNotFound
|
||||
@ -139,6 +140,26 @@ func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Co
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func (dr *DashboardServiceImpl) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
datasourceUids := models.GetUniqueDashboardDatasourceUids(dashboard.Data)
|
||||
|
||||
// Create a temp user with read-only datasource permissions
|
||||
anonymousUser := &models.SignedInUser{OrgId: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
||||
permissions := make(map[string][]string)
|
||||
queryScopes := make([]string, 0)
|
||||
readScopes := make([]string, 0)
|
||||
for _, uid := range datasourceUids {
|
||||
queryScopes = append(queryScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
readScopes = append(readScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
}
|
||||
permissions[datasources.ActionQuery] = queryScopes
|
||||
permissions[datasources.ActionRead] = readScopes
|
||||
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||
|
||||
return anonymousUser, nil
|
||||
}
|
||||
|
||||
// generates a uuid formatted without dashes to use as access token
|
||||
func GenerateAccessToken() (string, error) {
|
||||
token, err := uuid.NewRandom()
|
||||
|
@ -314,6 +314,26 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
|
||||
user, err := service.BuildAnonymousUser(context.Background(), dashboard)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dashboard.OrgId, user.OrgId)
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgId]["datasources:query"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgId]["datasources:query"][1])
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgId]["datasources:read"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgId]["datasources:read"][1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
@ -425,6 +445,9 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
@ -444,6 +467,9 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds3",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
|
@ -1,19 +1,41 @@
|
||||
import { catchError, Observable, of, switchMap } from 'rxjs';
|
||||
|
||||
import { DataQuery, DataQueryRequest, DataQueryResponse, DataSourceApi, PluginMeta } from '@grafana/data';
|
||||
import {
|
||||
DataQuery,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceRef,
|
||||
PluginMeta,
|
||||
} from '@grafana/data';
|
||||
import { BackendDataSourceResponse, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||
|
||||
export const PUBLIC_DATASOURCE = '-- Public --';
|
||||
|
||||
export class PublicDashboardDataSource extends DataSourceApi<any> {
|
||||
constructor() {
|
||||
constructor(datasource: DataSourceRef | string | DataSourceApi | null) {
|
||||
super({
|
||||
name: 'public-ds',
|
||||
id: 1,
|
||||
id: 0,
|
||||
type: 'public-ds',
|
||||
meta: {} as PluginMeta,
|
||||
uid: '1',
|
||||
uid: PublicDashboardDataSource.resolveUid(datasource),
|
||||
jsonData: {},
|
||||
access: 'proxy',
|
||||
});
|
||||
|
||||
this.interval = '1min';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the datasource uid based on the many types a datasource can be.
|
||||
*/
|
||||
private static resolveUid(datasource: DataSourceRef | string | DataSourceApi | null): string {
|
||||
if (typeof datasource === 'string') {
|
||||
return datasource;
|
||||
}
|
||||
|
||||
return datasource?.uid ?? PUBLIC_DATASOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,7 +362,7 @@ async function getDataSource(
|
||||
publicDashboardAccessToken?: string
|
||||
): Promise<DataSourceApi> {
|
||||
if (publicDashboardAccessToken) {
|
||||
return new PublicDashboardDataSource();
|
||||
return new PublicDashboardDataSource(datasource);
|
||||
}
|
||||
|
||||
if (datasource && (datasource as any).query) {
|
||||
|
Loading…
Reference in New Issue
Block a user