mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
public dashboards: finalize db schema & v1 feature complete (#50467)
This PR completes public dashboards v1 functionality and simplifies public dashboard conventions. It exists as a large PR so that we are not making constant changes to the database schema. models.PublicDashboardConfig model replaced with models.PublicDashboard directly dashboard_public_config table renamed to dashboard_public models.Dashboard.IsPublic removed from the dashboard and replaced with models.PublicDashboard.isEnabled Routing now uses a uuid v4 as an access token for viewing a public dashboard anonymously, PublicDashboard.Uid only used as database identifier Frontend utilizes uuid for auth'd operations and access token for anonymous access Default to time range defined on dashboard when viewing public dashboard Add audit fields to public dashboard Co-authored-by: Owen Smallwood <owen.smallwood@grafana.com>, Ezequiel Victorero <ezequiel.victorero@grafana.com>, Jesse Weaver <jesse.weaver@grafana.com>
This commit is contained in:
@@ -484,7 +484,7 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
|
|||||||
panelId?: number;
|
panelId?: number;
|
||||||
dashboardId?: number;
|
dashboardId?: number;
|
||||||
// Temporary prop for public dashboards, to be replaced by publicAccessKey
|
// Temporary prop for public dashboards, to be replaced by publicAccessKey
|
||||||
publicDashboardUid?: string;
|
publicDashboardAccessToken?: string;
|
||||||
|
|
||||||
// Request Timing
|
// Request Timing
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
|||||||
@@ -30,19 +30,21 @@ describe('PublicDashboardDatasource', () => {
|
|||||||
|
|
||||||
const ds = new PublicDashboardDataSource();
|
const ds = new PublicDashboardDataSource();
|
||||||
const panelId = 1;
|
const panelId = 1;
|
||||||
const publicDashboardUid = 'abc123';
|
const publicDashboardAccessToken = 'abc123';
|
||||||
|
|
||||||
ds.query({
|
ds.query({
|
||||||
maxDataPoints: 10,
|
maxDataPoints: 10,
|
||||||
intervalMs: 5000,
|
intervalMs: 5000,
|
||||||
targets: [{ refId: 'A' }, { refId: 'B', datasource: { type: 'sample' } }],
|
targets: [{ refId: 'A' }, { refId: 'B', datasource: { type: 'sample' } }],
|
||||||
panelId,
|
panelId,
|
||||||
publicDashboardUid,
|
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(`/api/public/dashboards/${publicDashboardUid}/panels/${panelId}/query`);
|
expect(mock.lastCall[0].url).toEqual(
|
||||||
|
`/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,11 +89,6 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
r.Get("/a/:id/*", reqSignedIn, hs.Index) // App Root Page
|
r.Get("/a/:id/*", reqSignedIn, hs.Index) // App Root Page
|
||||||
r.Get("/a/:id", reqSignedIn, hs.Index)
|
r.Get("/a/:id", reqSignedIn, hs.Index)
|
||||||
|
|
||||||
//pubdash
|
|
||||||
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
|
|
||||||
r.Get("/public-dashboards/:uid", middleware.SetPublicDashboardFlag(), hs.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Get("/d/:uid/:slug", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
r.Get("/d/:uid/:slug", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
||||||
r.Get("/d/:uid", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
r.Get("/d/:uid", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
||||||
r.Get("/dashboard/script/*", reqSignedIn, hs.Index)
|
r.Get("/dashboard/script/*", reqSignedIn, hs.Index)
|
||||||
@@ -612,8 +607,9 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
|
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
|
||||||
r.Get("/api/public/dashboards/:uid", routing.Wrap(hs.GetPublicDashboard))
|
r.Get("/public-dashboards/:accessToken", middleware.SetPublicDashboardFlag(), hs.Index)
|
||||||
r.Post("/api/public/dashboards/:uid/panels/:panelId/query", routing.Wrap(hs.QueryPublicDashboard))
|
r.Get("/api/public/dashboards/:accessToken", routing.Wrap(hs.GetPublicDashboard))
|
||||||
|
r.Post("/api/public/dashboards/:accessToken/panels/:panelId/query", routing.Wrap(hs.QueryPublicDashboard))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontend logs
|
// Frontend logs
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
|||||||
Url: dash.GetUrl(),
|
Url: dash.GetUrl(),
|
||||||
FolderTitle: "General",
|
FolderTitle: "General",
|
||||||
AnnotationsPermissions: annotationPermissions,
|
AnnotationsPermissions: annotationPermissions,
|
||||||
IsPublic: dash.IsPublic,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup folder title
|
// lookup folder title
|
||||||
|
|||||||
@@ -2,21 +2,24 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// gets public dashboard
|
// gets public dashboard
|
||||||
func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response {
|
||||||
publicDashboardUid := web.Params(c.Req)[":uid"]
|
accessToken := web.Params(c.Req)[":accessToken"]
|
||||||
|
|
||||||
dash, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), publicDashboardUid)
|
dash, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err)
|
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err)
|
||||||
}
|
}
|
||||||
@@ -34,8 +37,7 @@ func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response
|
|||||||
Version: dash.Version,
|
Version: dash.Version,
|
||||||
IsFolder: false,
|
IsFolder: false,
|
||||||
FolderId: dash.FolderId,
|
FolderId: dash.FolderId,
|
||||||
IsPublic: dash.IsPublic,
|
PublicDashboardAccessToken: accessToken,
|
||||||
PublicDashboardUid: publicDashboardUid,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
|
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
|
||||||
@@ -54,43 +56,72 @@ func (hs *HTTPServer) GetPublicDashboardConfig(c *models.ReqContext) response.Re
|
|||||||
|
|
||||||
// sets public dashboard configuration for dashboard
|
// sets public dashboard configuration for dashboard
|
||||||
func (hs *HTTPServer) SavePublicDashboardConfig(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) SavePublicDashboardConfig(c *models.ReqContext) response.Response {
|
||||||
pdc := &models.PublicDashboardConfig{}
|
pubdash := &models.PublicDashboard{}
|
||||||
if err := web.Bind(c.Req, pdc); err != nil {
|
if err := web.Bind(c.Req, pubdash); err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always set the org id to the current auth session orgId
|
||||||
|
pubdash.OrgId = c.OrgId
|
||||||
|
|
||||||
dto := dashboards.SavePublicDashboardConfigDTO{
|
dto := dashboards.SavePublicDashboardConfigDTO{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
DashboardUid: web.Params(c.Req)[":uid"],
|
DashboardUid: web.Params(c.Req)[":uid"],
|
||||||
PublicDashboardConfig: pdc,
|
UserId: c.UserId,
|
||||||
|
PublicDashboard: pubdash,
|
||||||
}
|
}
|
||||||
|
|
||||||
pdc, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
|
pubdash, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err)
|
return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, pdc)
|
return response.JSON(http.StatusOK, pubdash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryPublicDashboard returns all results for a given panel on a public dashboard
|
// QueryPublicDashboard returns all results for a given panel on a public dashboard
|
||||||
// POST /api/public/dashboard/:uid/panels/:panelId/query
|
// POST /api/public/dashboard/:accessToken/panels/:panelId/query
|
||||||
func (hs *HTTPServer) QueryPublicDashboard(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) QueryPublicDashboard(c *models.ReqContext) response.Response {
|
||||||
panelId, err := strconv.ParseInt(web.Params(c.Req)[":panelId"], 10, 64)
|
panelId, err := strconv.ParseInt(web.Params(c.Req)[":panelId"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "invalid panel ID", err)
|
return response.Error(http.StatusBadRequest, "invalid panel ID", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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(),
|
c.Req.Context(),
|
||||||
web.Params(c.Req)[":uid"],
|
dashboard,
|
||||||
|
publicDashboard,
|
||||||
panelId,
|
panelId,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleDashboardErr(http.StatusInternalServerError, "Failed to get queries for public dashboard", err)
|
return handleDashboardErr(http.StatusInternalServerError, "Failed to get queries for public dashboard", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := hs.queryDataService.QueryDataMultipleSources(c.Req.Context(), nil, c.SkipCache, reqDTO, true)
|
// Get all needed datasource UIDs from queries
|
||||||
|
var uids []string
|
||||||
|
for _, query := range reqDTO.Queries {
|
||||||
|
uids = append(uids, query.Get("datasource").Get("uid").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
datasourceScope := fmt.Sprintf("datasources:uid:%s", strings.Join(uids, ","))
|
||||||
|
permissions[datasources.ActionQuery] = []string{datasourceScope}
|
||||||
|
permissions[datasources.ActionRead] = []string{datasourceScope}
|
||||||
|
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||||
|
|
||||||
|
resp, err := hs.queryDataService.QueryDataMultipleSources(c.Req.Context(), anonymousUser, c.SkipCache, reqDTO, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hs.handleQueryMetricsError(err)
|
return hs.handleQueryMetricsError(err)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -18,10 +19,14 @@ import (
|
|||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources/service"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/query"
|
"github.com/grafana/grafana/pkg/services/query"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/web/webtest"
|
"github.com/grafana/grafana/pkg/web/webtest"
|
||||||
|
|
||||||
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||||
@@ -54,39 +59,40 @@ func TestAPIGetPublicDashboard(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusNotFound, response.Code)
|
assert.Equal(t, http.StatusNotFound, response.Code)
|
||||||
})
|
})
|
||||||
|
|
||||||
dashboardUid := "dashboard-abcd1234"
|
DashboardUid := "dashboard-abcd1234"
|
||||||
pubdashUid := "pubdash-abcd1234"
|
token, err := uuid.NewV4()
|
||||||
|
require.NoError(t, err)
|
||||||
|
accessToken := fmt.Sprintf("%x", token)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
Name string
|
||||||
uid string
|
AccessToken string
|
||||||
expectedHttpResponse int
|
ExpectedHttpResponse int
|
||||||
publicDashboardResult *models.Dashboard
|
publicDashboardResult *models.Dashboard
|
||||||
publicDashboardErr error
|
publicDashboardErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "It gets a public dashboard",
|
Name: "It gets a public dashboard",
|
||||||
uid: pubdashUid,
|
AccessToken: accessToken,
|
||||||
expectedHttpResponse: http.StatusOK,
|
ExpectedHttpResponse: http.StatusOK,
|
||||||
publicDashboardResult: &models.Dashboard{
|
publicDashboardResult: &models.Dashboard{
|
||||||
Data: simplejson.NewFromAny(map[string]interface{}{
|
Data: simplejson.NewFromAny(map[string]interface{}{
|
||||||
"Uid": dashboardUid,
|
"Uid": DashboardUid,
|
||||||
}),
|
}),
|
||||||
IsPublic: true,
|
|
||||||
},
|
},
|
||||||
publicDashboardErr: nil,
|
publicDashboardErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "It should return 404 if isPublicDashboard is false",
|
Name: "It should return 404 if isPublicDashboard is false",
|
||||||
uid: pubdashUid,
|
AccessToken: accessToken,
|
||||||
expectedHttpResponse: http.StatusNotFound,
|
ExpectedHttpResponse: http.StatusNotFound,
|
||||||
publicDashboardResult: nil,
|
publicDashboardResult: nil,
|
||||||
publicDashboardErr: models.ErrPublicDashboardNotFound,
|
publicDashboardErr: models.ErrPublicDashboardNotFound,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
||||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||||
dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).
|
dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")).
|
||||||
@@ -97,20 +103,19 @@ func TestAPIGetPublicDashboard(t *testing.T) {
|
|||||||
response := callAPI(
|
response := callAPI(
|
||||||
sc.server,
|
sc.server,
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
fmt.Sprintf("/api/public/dashboards/%v", test.uid),
|
fmt.Sprintf("/api/public/dashboards/%s", test.AccessToken),
|
||||||
nil,
|
nil,
|
||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedHttpResponse, response.Code)
|
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||||
|
|
||||||
if test.publicDashboardErr == nil {
|
if test.publicDashboardErr == nil {
|
||||||
var dashResp dtos.DashboardFullWithMeta
|
var dashResp dtos.DashboardFullWithMeta
|
||||||
err := json.Unmarshal(response.Body.Bytes(), &dashResp)
|
err := json.Unmarshal(response.Body.Bytes(), &dashResp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dashboardUid, dashResp.Dashboard.Get("Uid").MustString())
|
assert.Equal(t, DashboardUid, dashResp.Dashboard.Get("Uid").MustString())
|
||||||
assert.Equal(t, true, dashResp.Meta.IsPublic)
|
|
||||||
assert.Equal(t, false, dashResp.Meta.CanEdit)
|
assert.Equal(t, false, dashResp.Meta.CanEdit)
|
||||||
assert.Equal(t, false, dashResp.Meta.CanDelete)
|
assert.Equal(t, false, dashResp.Meta.CanDelete)
|
||||||
assert.Equal(t, false, dashResp.Meta.CanSave)
|
assert.Equal(t, false, dashResp.Meta.CanSave)
|
||||||
@@ -127,44 +132,44 @@ func TestAPIGetPublicDashboard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGetPublicDashboardConfig(t *testing.T) {
|
func TestAPIGetPublicDashboardConfig(t *testing.T) {
|
||||||
pdc := &models.PublicDashboardConfig{IsPublic: true}
|
pubdash := &models.PublicDashboard{IsEnabled: true}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
Name string
|
||||||
dashboardUid string
|
DashboardUid string
|
||||||
expectedHttpResponse int
|
ExpectedHttpResponse int
|
||||||
publicDashboardConfigResult *models.PublicDashboardConfig
|
PublicDashboardResult *models.PublicDashboard
|
||||||
publicDashboardConfigError error
|
PublicDashboardError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "retrieves public dashboard config when dashboard is found",
|
Name: "retrieves public dashboard config when dashboard is found",
|
||||||
dashboardUid: "1",
|
DashboardUid: "1",
|
||||||
expectedHttpResponse: http.StatusOK,
|
ExpectedHttpResponse: http.StatusOK,
|
||||||
publicDashboardConfigResult: pdc,
|
PublicDashboardResult: pubdash,
|
||||||
publicDashboardConfigError: nil,
|
PublicDashboardError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns 404 when dashboard not found",
|
Name: "returns 404 when dashboard not found",
|
||||||
dashboardUid: "77777",
|
DashboardUid: "77777",
|
||||||
expectedHttpResponse: http.StatusNotFound,
|
ExpectedHttpResponse: http.StatusNotFound,
|
||||||
publicDashboardConfigResult: nil,
|
PublicDashboardResult: nil,
|
||||||
publicDashboardConfigError: models.ErrDashboardNotFound,
|
PublicDashboardError: models.ErrDashboardNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns 500 when internal server error",
|
Name: "returns 500 when internal server error",
|
||||||
dashboardUid: "1",
|
DashboardUid: "1",
|
||||||
expectedHttpResponse: http.StatusInternalServerError,
|
ExpectedHttpResponse: http.StatusInternalServerError,
|
||||||
publicDashboardConfigResult: nil,
|
PublicDashboardResult: nil,
|
||||||
publicDashboardConfigError: errors.New("database broken"),
|
PublicDashboardError: errors.New("database broken"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
||||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||||
dashSvc.On("GetPublicDashboardConfig", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
|
dashSvc.On("GetPublicDashboardConfig", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
|
||||||
Return(test.publicDashboardConfigResult, test.publicDashboardConfigError)
|
Return(test.PublicDashboardResult, test.PublicDashboardError)
|
||||||
sc.hs.dashboardService = dashSvc
|
sc.hs.dashboardService = dashSvc
|
||||||
|
|
||||||
setInitCtxSignedInViewer(sc.initCtx)
|
setInitCtxSignedInViewer(sc.initCtx)
|
||||||
@@ -176,13 +181,13 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedHttpResponse, response.Code)
|
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||||
|
|
||||||
if response.Code == http.StatusOK {
|
if response.Code == http.StatusOK {
|
||||||
var pdcResp models.PublicDashboardConfig
|
var pdcResp models.PublicDashboard
|
||||||
err := json.Unmarshal(response.Body.Bytes(), &pdcResp)
|
err := json.Unmarshal(response.Body.Bytes(), &pdcResp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.publicDashboardConfigResult, &pdcResp)
|
assert.Equal(t, test.PublicDashboardResult, &pdcResp)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -190,40 +195,40 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
|
|||||||
|
|
||||||
func TestApiSavePublicDashboardConfig(t *testing.T) {
|
func TestApiSavePublicDashboardConfig(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
Name string
|
||||||
dashboardUid string
|
DashboardUid string
|
||||||
publicDashboardConfig *models.PublicDashboardConfig
|
publicDashboardConfig *models.PublicDashboard
|
||||||
expectedHttpResponse int
|
ExpectedHttpResponse int
|
||||||
saveDashboardError error
|
saveDashboardError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "returns 200 when update persists",
|
Name: "returns 200 when update persists",
|
||||||
dashboardUid: "1",
|
DashboardUid: "1",
|
||||||
publicDashboardConfig: &models.PublicDashboardConfig{IsPublic: true},
|
publicDashboardConfig: &models.PublicDashboard{IsEnabled: true},
|
||||||
expectedHttpResponse: http.StatusOK,
|
ExpectedHttpResponse: http.StatusOK,
|
||||||
saveDashboardError: nil,
|
saveDashboardError: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns 500 when not persisted",
|
Name: "returns 500 when not persisted",
|
||||||
expectedHttpResponse: http.StatusInternalServerError,
|
ExpectedHttpResponse: http.StatusInternalServerError,
|
||||||
publicDashboardConfig: &models.PublicDashboardConfig{},
|
publicDashboardConfig: &models.PublicDashboard{},
|
||||||
saveDashboardError: errors.New("backend failed to save"),
|
saveDashboardError: errors.New("backend failed to save"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns 404 when dashboard not found",
|
Name: "returns 404 when dashboard not found",
|
||||||
expectedHttpResponse: http.StatusNotFound,
|
ExpectedHttpResponse: http.StatusNotFound,
|
||||||
publicDashboardConfig: &models.PublicDashboardConfig{},
|
publicDashboardConfig: &models.PublicDashboard{},
|
||||||
saveDashboardError: models.ErrDashboardNotFound,
|
saveDashboardError: models.ErrDashboardNotFound,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
||||||
|
|
||||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||||
dashSvc.On("SavePublicDashboardConfig", mock.Anything, mock.AnythingOfType("*dashboards.SavePublicDashboardConfigDTO")).
|
dashSvc.On("SavePublicDashboardConfig", mock.Anything, mock.AnythingOfType("*dashboards.SavePublicDashboardConfigDTO")).
|
||||||
Return(&models.PublicDashboardConfig{IsPublic: true}, test.saveDashboardError)
|
Return(&models.PublicDashboard{IsEnabled: true}, test.saveDashboardError)
|
||||||
sc.hs.dashboardService = dashSvc
|
sc.hs.dashboardService = dashSvc
|
||||||
|
|
||||||
setInitCtxSignedInViewer(sc.initCtx)
|
setInitCtxSignedInViewer(sc.initCtx)
|
||||||
@@ -235,7 +240,7 @@ func TestApiSavePublicDashboardConfig(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedHttpResponse, response.Code)
|
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||||
|
|
||||||
// check the result if it's a 200
|
// check the result if it's a 200
|
||||||
if response.Code == http.StatusOK {
|
if response.Code == http.StatusOK {
|
||||||
@@ -326,10 +331,14 @@ func TestAPIQueryPublicDashboard(t *testing.T) {
|
|||||||
t.Run("Returns query data when feature toggle is enabled", func(t *testing.T) {
|
t.Run("Returns query data when feature toggle is enabled", func(t *testing.T) {
|
||||||
server, fakeDashboardService := setup(true)
|
server, fakeDashboardService := setup(true)
|
||||||
|
|
||||||
|
fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil)
|
||||||
|
fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil)
|
||||||
|
|
||||||
fakeDashboardService.On(
|
fakeDashboardService.On(
|
||||||
"BuildPublicDashboardMetricRequest",
|
"BuildPublicDashboardMetricRequest",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
"abc123",
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
int64(2),
|
int64(2),
|
||||||
).Return(dtos.MetricRequest{
|
).Return(dtos.MetricRequest{
|
||||||
Queries: []*simplejson.Json{
|
Queries: []*simplejson.Json{
|
||||||
@@ -385,10 +394,13 @@ func TestAPIQueryPublicDashboard(t *testing.T) {
|
|||||||
t.Run("Status code is 500 when the query fails", func(t *testing.T) {
|
t.Run("Status code is 500 when the query fails", func(t *testing.T) {
|
||||||
server, fakeDashboardService := setup(true)
|
server, fakeDashboardService := setup(true)
|
||||||
|
|
||||||
|
fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil)
|
||||||
|
fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil)
|
||||||
fakeDashboardService.On(
|
fakeDashboardService.On(
|
||||||
"BuildPublicDashboardMetricRequest",
|
"BuildPublicDashboardMetricRequest",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
"abc123",
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
int64(2),
|
int64(2),
|
||||||
).Return(dtos.MetricRequest{
|
).Return(dtos.MetricRequest{
|
||||||
Queries: []*simplejson.Json{
|
Queries: []*simplejson.Json{
|
||||||
@@ -422,10 +434,13 @@ func TestAPIQueryPublicDashboard(t *testing.T) {
|
|||||||
t.Run("Status code is 200 when a panel has queries from multiple datasources", func(t *testing.T) {
|
t.Run("Status code is 200 when a panel has queries from multiple datasources", func(t *testing.T) {
|
||||||
server, fakeDashboardService := setup(true)
|
server, fakeDashboardService := setup(true)
|
||||||
|
|
||||||
|
fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil)
|
||||||
|
fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil)
|
||||||
fakeDashboardService.On(
|
fakeDashboardService.On(
|
||||||
"BuildPublicDashboardMetricRequest",
|
"BuildPublicDashboardMetricRequest",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
"abc123",
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
int64(2),
|
int64(2),
|
||||||
).Return(dtos.MetricRequest{
|
).Return(dtos.MetricRequest{
|
||||||
Queries: []*simplejson.Json{
|
Queries: []*simplejson.Json{
|
||||||
@@ -505,3 +520,111 @@ func TestAPIQueryPublicDashboard(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) {
|
||||||
|
config := setting.NewCfg()
|
||||||
|
db := sqlstore.InitTestDB(t)
|
||||||
|
scenario := setupHTTPServerWithCfgDb(t, false, false, config, db, db, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
|
||||||
|
scenario.initCtx.SkipCache = true
|
||||||
|
cacheService := service.ProvideCacheService(localcache.ProvideService(), db)
|
||||||
|
qds := query.ProvideService(
|
||||||
|
nil,
|
||||||
|
cacheService,
|
||||||
|
nil,
|
||||||
|
&fakePluginRequestValidator{},
|
||||||
|
&fakeDatasources.FakeDataSourceService{},
|
||||||
|
&fakePluginClient{
|
||||||
|
QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
|
resp := backend.Responses{
|
||||||
|
"A": backend.DataResponse{
|
||||||
|
Frames: []*data.Frame{{}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &backend.QueryDataResponse{Responses: resp}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&fakeOAuthTokenService{},
|
||||||
|
)
|
||||||
|
scenario.hs.queryDataService = qds
|
||||||
|
|
||||||
|
_ = db.AddDataSource(context.Background(), &models.AddDataSourceCommand{
|
||||||
|
Uid: "ds1",
|
||||||
|
OrgId: 1,
|
||||||
|
Name: "laban",
|
||||||
|
Type: models.DS_MYSQL,
|
||||||
|
Access: models.DS_ACCESS_DIRECT,
|
||||||
|
Url: "http://test",
|
||||||
|
Database: "site",
|
||||||
|
ReadOnly: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Dashboard
|
||||||
|
saveDashboardCmd := models.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
FolderId: 1,
|
||||||
|
IsFolder: false,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test",
|
||||||
|
"panels": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"targets": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"datasource": map[string]string{
|
||||||
|
"type": "mysql",
|
||||||
|
"uid": "ds1",
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
dashboard, _ := scenario.dashboardsStore.SaveDashboard(saveDashboardCmd)
|
||||||
|
|
||||||
|
// Create public dashboard
|
||||||
|
savePubDashboardCmd := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
OrgId: dashboard.OrgId,
|
||||||
|
PublicDashboard: &models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pubdash, err := scenario.hs.dashboardService.SavePublicDashboardConfig(context.Background(), savePubDashboardCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
response := callAPI(
|
||||||
|
scenario.server,
|
||||||
|
http.MethodPost,
|
||||||
|
fmt.Sprintf("/api/public/dashboards/%s/panels/1/query", pubdash.AccessToken),
|
||||||
|
strings.NewReader(`{}`),
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, response.Code)
|
||||||
|
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.JSONEq(
|
||||||
|
t,
|
||||||
|
`{
|
||||||
|
"results": {
|
||||||
|
"A": {
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"fields": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
string(bodyBytes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ type DashboardMeta struct {
|
|||||||
Provisioned bool `json:"provisioned"`
|
Provisioned bool `json:"provisioned"`
|
||||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||||
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
||||||
IsPublic bool `json:"isPublic"`
|
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||||
PublicDashboardUid string `json:"publicDashboardUid"`
|
|
||||||
}
|
}
|
||||||
type AnnotationPermission struct {
|
type AnnotationPermission struct {
|
||||||
Dashboard AnnotationActions `json:"dashboard"`
|
Dashboard AnnotationActions `json:"dashboard"`
|
||||||
|
|||||||
@@ -200,7 +200,6 @@ type Dashboard struct {
|
|||||||
FolderId int64
|
FolderId int64
|
||||||
IsFolder bool
|
IsFolder bool
|
||||||
HasAcl bool
|
HasAcl bool
|
||||||
IsPublic bool
|
|
||||||
|
|
||||||
Title string
|
Title string
|
||||||
Data *simplejson.Json
|
Data *simplejson.Json
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPublicDashboardFailedGenerateUniqueUid = DashboardErr{
|
ErrPublicDashboardFailedGenerateUniqueUid = DashboardErr{
|
||||||
Reason: "Failed to generate unique dashboard id",
|
Reason: "Failed to generate unique public dashboard id",
|
||||||
|
StatusCode: 500,
|
||||||
|
}
|
||||||
|
ErrPublicDashboardFailedGenerateAccesstoken = DashboardErr{
|
||||||
|
Reason: "Failed to public dashboard access token",
|
||||||
StatusCode: 500,
|
StatusCode: 500,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardNotFound = DashboardErr{
|
ErrPublicDashboardNotFound = DashboardErr{
|
||||||
@@ -21,20 +31,51 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type PublicDashboardConfig struct {
|
|
||||||
IsPublic bool `json:"isPublic"`
|
|
||||||
PublicDashboard PublicDashboard `json:"publicDashboard"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublicDashboard struct {
|
type PublicDashboard struct {
|
||||||
Uid string `json:"uid" xorm:"uid"`
|
Uid string `json:"uid" xorm:"uid"`
|
||||||
DashboardUid string `json:"dashboardUid" xorm:"dashboard_uid"`
|
DashboardUid string `json:"dashboardUid" xorm:"dashboard_uid"`
|
||||||
OrgId int64 `json:"orgId" xorm:"org_id"`
|
OrgId int64 `json:"-" xorm:"org_id"` // Don't ever marshal orgId to Json
|
||||||
TimeSettings string `json:"timeSettings" xorm:"time_settings"`
|
TimeSettings *simplejson.Json `json:"timeSettings" xorm:"time_settings"`
|
||||||
|
IsEnabled bool `json:"isEnabled" xorm:"is_enabled"`
|
||||||
|
AccessToken string `json:"accessToken" xorm:"access_token"`
|
||||||
|
|
||||||
|
CreatedBy int64 `json:"createdBy" xorm:"created_by"`
|
||||||
|
UpdatedBy int64 `json:"updatedBy" xorm:"updated_by"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"createdAt" xorm:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt" xorm:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd PublicDashboard) TableName() string {
|
func (pd PublicDashboard) TableName() string {
|
||||||
return "dashboard_public_config"
|
return "dashboard_public"
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeSettings struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// build time settings object from json on public dashboard. If empty, use
|
||||||
|
// defaults on the dashboard
|
||||||
|
func (pd PublicDashboard) BuildTimeSettings(dashboard *Dashboard) *TimeSettings {
|
||||||
|
ts := &TimeSettings{
|
||||||
|
From: dashboard.Data.GetPath("time", "from").MustString(),
|
||||||
|
To: dashboard.Data.GetPath("time", "to").MustString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if pd.TimeSettings == nil {
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge time settings from public dashboard
|
||||||
|
to := pd.TimeSettings.Get("to").MustString("")
|
||||||
|
from := pd.TimeSettings.Get("from").MustString("")
|
||||||
|
if to != "" && from != "" {
|
||||||
|
ts.From = from
|
||||||
|
ts.To = to
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -44,5 +85,5 @@ func (pd PublicDashboard) TableName() string {
|
|||||||
type SavePublicDashboardConfigCommand struct {
|
type SavePublicDashboardConfigCommand struct {
|
||||||
DashboardUid string
|
DashboardUid string
|
||||||
OrgId int64
|
OrgId int64
|
||||||
PublicDashboardConfig PublicDashboardConfig
|
PublicDashboard PublicDashboard
|
||||||
}
|
}
|
||||||
|
|||||||
56
pkg/models/dashboards_public_test.go
Normal file
56
pkg/models/dashboards_public_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPublicDashboardTableName(t *testing.T) {
|
||||||
|
assert.Equal(t, "dashboard_public", PublicDashboard{}.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildTimeSettings(t *testing.T) {
|
||||||
|
var dashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-8", "to": "now"}})
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
dashboard *Dashboard
|
||||||
|
pubdash *PublicDashboard
|
||||||
|
timeResult *TimeSettings
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should use dashboard time if pubdash time empty",
|
||||||
|
dashboard: &Dashboard{Data: dashboardData},
|
||||||
|
pubdash: &PublicDashboard{},
|
||||||
|
timeResult: &TimeSettings{
|
||||||
|
From: "now-8",
|
||||||
|
To: "now",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should use dashboard time if pubdash to/from empty",
|
||||||
|
dashboard: &Dashboard{Data: dashboardData},
|
||||||
|
pubdash: &PublicDashboard{},
|
||||||
|
timeResult: &TimeSettings{
|
||||||
|
From: "now-8",
|
||||||
|
To: "now",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should use pubdash time",
|
||||||
|
dashboard: &Dashboard{Data: dashboardData},
|
||||||
|
pubdash: &PublicDashboard{TimeSettings: simplejson.NewFromAny(map[string]interface{}{"from": "now-12", "to": "now"})},
|
||||||
|
timeResult: &TimeSettings{
|
||||||
|
From: "now-12",
|
||||||
|
To: "now",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.timeResult, test.pubdash.BuildTimeSettings(test.dashboard))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
//go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go
|
//go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go
|
||||||
// DashboardService is a service for operating on dashboards.
|
// DashboardService is a service for operating on dashboards.
|
||||||
type DashboardService interface {
|
type DashboardService interface {
|
||||||
BuildPublicDashboardMetricRequest(ctx context.Context, publicDashboardUid string, panelId int64) (dtos.MetricRequest, error)
|
BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error)
|
||||||
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
|
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
|
||||||
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
||||||
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
||||||
@@ -19,14 +19,14 @@ type DashboardService interface {
|
|||||||
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
|
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
|
||||||
GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error
|
GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error
|
||||||
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
|
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
|
||||||
GetPublicDashboard(ctx context.Context, publicDashboardUid string) (*models.Dashboard, error)
|
GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error)
|
||||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error)
|
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error)
|
||||||
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
|
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
|
||||||
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
||||||
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
|
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
|
||||||
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
|
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
|
||||||
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
|
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
|
||||||
SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error)
|
SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error)
|
||||||
SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error
|
SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error
|
||||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
||||||
}
|
}
|
||||||
@@ -65,16 +65,18 @@ type Store interface {
|
|||||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||||
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
|
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
|
||||||
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
|
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
|
||||||
GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error)
|
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error)
|
||||||
GetPublicDashboard(uid string) (*models.PublicDashboard, *models.Dashboard, error)
|
GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error)
|
||||||
|
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
||||||
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
|
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
|
||||||
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
||||||
// SaveAlerts saves dashboard alerts.
|
// SaveAlerts saves dashboard alerts.
|
||||||
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
|
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
|
||||||
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
|
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
|
||||||
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||||
SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error)
|
SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error)
|
||||||
UnprovisionDashboard(ctx context.Context, id int64) error
|
UnprovisionDashboard(ctx context.Context, id int64) error
|
||||||
|
UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error
|
||||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
||||||
// ValidateDashboardBeforeSave validates a dashboard before save.
|
// ValidateDashboardBeforeSave validates a dashboard before save.
|
||||||
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
|
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.12.2. DO NOT EDIT.
|
// Code generated by mockery v2.12.1. DO NOT EDIT.
|
||||||
|
|
||||||
package dashboards
|
package dashboards
|
||||||
|
|
||||||
@@ -18,20 +18,20 @@ type FakeDashboardService struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, publicDashboardUid, panelId
|
// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId
|
||||||
func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, publicDashboardUid string, panelId int64) (dtos.MetricRequest, error) {
|
func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||||
ret := _m.Called(ctx, publicDashboardUid, panelId)
|
ret := _m.Called(ctx, dashboard, publicDashboard, panelId)
|
||||||
|
|
||||||
var r0 dtos.MetricRequest
|
var r0 dtos.MetricRequest
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, int64) dtos.MetricRequest); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) dtos.MetricRequest); ok {
|
||||||
r0 = rf(ctx, publicDashboardUid, panelId)
|
r0 = rf(ctx, dashboard, publicDashboard, panelId)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(dtos.MetricRequest)
|
r0 = ret.Get(0).(dtos.MetricRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) error); ok {
|
||||||
r1 = rf(ctx, publicDashboardUid, panelId)
|
r1 = rf(ctx, dashboard, publicDashboard, panelId)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -169,13 +169,13 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *models
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicDashboard provides a mock function with given fields: ctx, publicDashboardUid
|
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
||||||
func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, publicDashboardUid string) (*models.Dashboard, error) {
|
func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) {
|
||||||
ret := _m.Called(ctx, publicDashboardUid)
|
ret := _m.Called(ctx, accessToken)
|
||||||
|
|
||||||
var r0 *models.Dashboard
|
var r0 *models.Dashboard
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Dashboard); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Dashboard); ok {
|
||||||
r0 = rf(ctx, publicDashboardUid)
|
r0 = rf(ctx, accessToken)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.Dashboard)
|
r0 = ret.Get(0).(*models.Dashboard)
|
||||||
@@ -184,7 +184,7 @@ func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, publicDa
|
|||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
r1 = rf(ctx, publicDashboardUid)
|
r1 = rf(ctx, accessToken)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -193,15 +193,15 @@ func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, publicDa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
||||||
func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||||
ret := _m.Called(ctx, orgId, dashboardUid)
|
ret := _m.Called(ctx, orgId, dashboardUid)
|
||||||
|
|
||||||
var r0 *models.PublicDashboardConfig
|
var r0 *models.PublicDashboard
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboardConfig); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok {
|
||||||
r0 = rf(ctx, orgId, dashboardUid)
|
r0 = rf(ctx, orgId, dashboardUid)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.PublicDashboardConfig)
|
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,15 +304,15 @@ func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDash
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SavePublicDashboardConfig provides a mock function with given fields: ctx, dto
|
// SavePublicDashboardConfig provides a mock function with given fields: ctx, dto
|
||||||
func (_m *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
|
func (_m *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||||
ret := _m.Called(ctx, dto)
|
ret := _m.Called(ctx, dto)
|
||||||
|
|
||||||
var r0 *models.PublicDashboardConfig
|
var r0 *models.PublicDashboard
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *SavePublicDashboardConfigDTO) *models.PublicDashboardConfig); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, *SavePublicDashboardConfigDTO) *models.PublicDashboard); ok {
|
||||||
r0 = rf(ctx, dto)
|
r0 = rf(ctx, dto)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.PublicDashboardConfig)
|
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@@ -10,14 +9,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// retrieves public dashboard configuration
|
// retrieves public dashboard configuration
|
||||||
func (d *DashboardStore) GetPublicDashboard(uid string) (*models.PublicDashboard, *models.Dashboard, error) {
|
func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) {
|
||||||
if uid == "" {
|
if accessToken == "" {
|
||||||
return nil, nil, models.ErrPublicDashboardIdentifierNotSet
|
return nil, nil, models.ErrPublicDashboardIdentifierNotSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// get public dashboard
|
// get public dashboard
|
||||||
pdRes := &models.PublicDashboard{Uid: uid}
|
pdRes := &models.PublicDashboard{AccessToken: accessToken}
|
||||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
has, err := sess.Get(pdRes)
|
has, err := sess.Get(pdRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -34,7 +33,7 @@ func (d *DashboardStore) GetPublicDashboard(uid string) (*models.PublicDashboard
|
|||||||
|
|
||||||
// find dashboard
|
// find dashboard
|
||||||
dashRes := &models.Dashboard{OrgId: pdRes.OrgId, Uid: pdRes.DashboardUid}
|
dashRes := &models.Dashboard{OrgId: pdRes.OrgId, Uid: pdRes.DashboardUid}
|
||||||
err = d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
err = d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
has, err := sess.Get(dashRes)
|
has, err := sess.Get(dashRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -53,44 +52,43 @@ func (d *DashboardStore) GetPublicDashboard(uid string) (*models.PublicDashboard
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates a new unique uid to retrieve a public dashboard
|
// generates a new unique uid to retrieve a public dashboard
|
||||||
func generateNewPublicDashboardUid(sess *sqlstore.DBSession) (string, error) {
|
func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
||||||
|
var uid string
|
||||||
|
|
||||||
|
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
uid := util.GenerateShortUID()
|
uid = util.GenerateShortUID()
|
||||||
|
|
||||||
exists, err := sess.Get(&models.PublicDashboard{Uid: uid})
|
exists, err := sess.Get(&models.PublicDashboard{Uid: uid})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.ErrPublicDashboardFailedGenerateUniqueUid
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return uid, nil
|
return uid, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", models.ErrPublicDashboardFailedGenerateUniqueUid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieves public dashboard configuration
|
// retrieves public dashboard configuration
|
||||||
func (d *DashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||||
if dashboardUid == "" {
|
if dashboardUid == "" {
|
||||||
return nil, models.ErrDashboardIdentifierNotSet
|
return nil, models.ErrDashboardIdentifierNotSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// get dashboard and publicDashboard
|
|
||||||
dashRes := &models.Dashboard{OrgId: orgId, Uid: dashboardUid}
|
|
||||||
pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}
|
pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}
|
||||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
// dashboard
|
|
||||||
has, err := sess.Get(dashRes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
return models.ErrDashboardNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// publicDashboard
|
// publicDashboard
|
||||||
_, err = sess.Get(pdRes)
|
_, err := sess.Get(pdRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -102,46 +100,13 @@ func (d *DashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid stri
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pdc := &models.PublicDashboardConfig{
|
return pdRes, err
|
||||||
IsPublic: dashRes.IsPublic,
|
|
||||||
PublicDashboard: *pdRes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return pdc, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// persists public dashboard configuration
|
// persists public dashboard configuration
|
||||||
func (d *DashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error) {
|
func (d *DashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) {
|
||||||
if len(cmd.PublicDashboardConfig.PublicDashboard.DashboardUid) == 0 {
|
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
return nil, models.ErrDashboardIdentifierNotSet
|
_, err := sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard)
|
||||||
}
|
|
||||||
|
|
||||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
||||||
// update isPublic on dashboard entry
|
|
||||||
affectedRowCount, err := sess.Table("dashboard").Where("org_id = ? AND uid = ?", cmd.OrgId, cmd.DashboardUid).Update(map[string]interface{}{"is_public": cmd.PublicDashboardConfig.IsPublic})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if affectedRowCount == 0 {
|
|
||||||
return models.ErrDashboardNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// update dashboard_public_config
|
|
||||||
// if we have a uid, public dashboard config exists. delete it otherwise generate a uid
|
|
||||||
if cmd.PublicDashboardConfig.PublicDashboard.Uid != "" {
|
|
||||||
if _, err = sess.Exec("DELETE FROM dashboard_public_config WHERE uid=?", cmd.PublicDashboardConfig.PublicDashboard.Uid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uid, err := generateNewPublicDashboardUid(sess)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to generate UID for public dashboard: %w", err)
|
|
||||||
}
|
|
||||||
cmd.PublicDashboardConfig.PublicDashboard.Uid = uid
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sess.Insert(&cmd.PublicDashboardConfig.PublicDashboard)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -153,5 +118,19 @@ func (d *DashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDashboar
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cmd.PublicDashboardConfig, nil
|
return &cmd.PublicDashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updates existing public dashboard configuration
|
||||||
|
func (d *DashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
|
||||||
|
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
|
_, err := sess.UseBool("is_enabled").Update(&cmd.PublicDashboard)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@@ -11,6 +14,12 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This is what the db sets empty time settings to
|
||||||
|
var DefaultTimeSettings, _ = simplejson.NewJson([]byte(`{}`))
|
||||||
|
|
||||||
|
// Default time to pass in with seconds rounded
|
||||||
|
var DefaultTime = time.Now().UTC().Round(time.Second)
|
||||||
|
|
||||||
// GetPublicDashboard
|
// GetPublicDashboard
|
||||||
func TestIntegrationGetPublicDashboard(t *testing.T) {
|
func TestIntegrationGetPublicDashboard(t *testing.T) {
|
||||||
var sqlStore *sqlstore.SQLStore
|
var sqlStore *sqlstore.SQLStore
|
||||||
@@ -25,54 +34,55 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns PublicDashboard and Dashboard", func(t *testing.T) {
|
t.Run("returns PublicDashboard and Dashboard", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
pdc, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
pubdash, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
DashboardUid: savedDashboard.Uid,
|
|
||||||
OrgId: savedDashboard.OrgId,
|
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
|
||||||
IsPublic: true,
|
|
||||||
PublicDashboard: models.PublicDashboard{
|
PublicDashboard: models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
Uid: "abc1234",
|
Uid: "abc1234",
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
},
|
TimeSettings: DefaultTimeSettings,
|
||||||
|
CreatedAt: DefaultTime,
|
||||||
|
CreatedBy: 7,
|
||||||
|
AccessToken: "NOTAREALUUID",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pd, d, err := dashboardStore.GetPublicDashboard("abc1234")
|
pd, d, err := dashboardStore.GetPublicDashboard(context.Background(), "NOTAREALUUID")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, pd, &pdc.PublicDashboard)
|
|
||||||
assert.Equal(t, d.Uid, pdc.PublicDashboard.DashboardUid)
|
assert.Equal(t, pd, pubdash)
|
||||||
|
assert.Equal(t, d.Uid, pubdash.DashboardUid)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) {
|
t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
_, _, err := dashboardStore.GetPublicDashboard("")
|
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "")
|
||||||
require.Error(t, models.ErrPublicDashboardIdentifierNotSet, err)
|
require.Error(t, models.ErrPublicDashboardIdentifierNotSet, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) {
|
t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
_, _, err := dashboardStore.GetPublicDashboard("zzzzzz")
|
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "zzzzzz")
|
||||||
require.Error(t, models.ErrPublicDashboardNotFound, err)
|
require.Error(t, models.ErrPublicDashboardNotFound, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) {
|
t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
_, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
|
||||||
IsPublic: true,
|
|
||||||
PublicDashboard: models.PublicDashboard{
|
PublicDashboard: models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
Uid: "abc1234",
|
Uid: "abc1234",
|
||||||
DashboardUid: "nevergonnafindme",
|
DashboardUid: "nevergonnafindme",
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
},
|
CreatedAt: DefaultTime,
|
||||||
|
CreatedBy: 7,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, _, err = dashboardStore.GetPublicDashboard("abc1234")
|
_, _, err = dashboardStore.GetPublicDashboard(context.Background(), "abc1234")
|
||||||
require.Error(t, models.ErrDashboardNotFound, err)
|
require.Error(t, models.ErrDashboardNotFound, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -91,38 +101,40 @@ func TestIntegrationGetPublicDashboardConfig(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns isPublic and set dashboardUid and orgId", func(t *testing.T) {
|
t.Run("returns isPublic and set dashboardUid and orgId", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, &models.PublicDashboardConfig{IsPublic: false, PublicDashboard: models.PublicDashboard{DashboardUid: savedDashboard.Uid, OrgId: savedDashboard.OrgId}}, pdc)
|
assert.Equal(t, &models.PublicDashboard{IsEnabled: false, DashboardUid: savedDashboard.Uid, OrgId: savedDashboard.OrgId}, pubdash)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) {
|
t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
_, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, "")
|
_, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, "")
|
||||||
require.Error(t, models.ErrDashboardIdentifierNotSet, err)
|
require.Error(t, models.ErrDashboardIdentifierNotSet, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns isPublic along with public dashboard when exists", func(t *testing.T) {
|
t.Run("returns isPublic along with public dashboard when exists", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
// insert test public dashboard
|
// insert test public dashboard
|
||||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
resp, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
|
||||||
IsPublic: true,
|
|
||||||
PublicDashboard: models.PublicDashboard{
|
PublicDashboard: models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
Uid: "pubdash-uid",
|
Uid: "pubdash-uid",
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
TimeSettings: "{from: now, to: then}",
|
TimeSettings: DefaultTimeSettings,
|
||||||
},
|
CreatedAt: DefaultTime,
|
||||||
|
CreatedBy: 7,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, resp, pdc)
|
|
||||||
|
assert.True(t, assert.ObjectsAreEqualValues(resp, pubdash))
|
||||||
|
assert.True(t, assert.ObjectsAreEqual(resp, pubdash))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,89 +154,92 @@ func TestIntegrationSavePublicDashboardConfig(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("saves new public dashboard", func(t *testing.T) {
|
t.Run("saves new public dashboard", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
resp, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
|
||||||
IsPublic: true,
|
|
||||||
PublicDashboard: models.PublicDashboard{
|
PublicDashboard: models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
Uid: "pubdash-uid",
|
Uid: "pubdash-uid",
|
||||||
DashboardUid: savedDashboard.Uid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
OrgId: savedDashboard.OrgId,
|
OrgId: savedDashboard.OrgId,
|
||||||
},
|
TimeSettings: DefaultTimeSettings,
|
||||||
|
CreatedAt: DefaultTime,
|
||||||
|
CreatedBy: 7,
|
||||||
|
AccessToken: "NOTAREALUUID",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
//verify saved response and queried response are the same
|
//verify saved response and queried response are the same
|
||||||
assert.Equal(t, resp, pdc)
|
assert.Equal(t, resp, pubdash)
|
||||||
|
|
||||||
// verify we have a valid uid
|
// verify we have a valid uid
|
||||||
assert.True(t, util.IsValidShortUID(pdc.PublicDashboard.Uid))
|
assert.True(t, util.IsValidShortUID(pubdash.Uid))
|
||||||
|
|
||||||
// verify we didn't update all dashboards
|
// verify we didn't update all dashboards
|
||||||
pdc2, err := dashboardStore.GetPublicDashboardConfig(savedDashboard2.OrgId, savedDashboard2.Uid)
|
pubdash2, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard2.OrgId, savedDashboard2.Uid)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, pdc2.IsPublic)
|
assert.False(t, pubdash2.IsEnabled)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
t.Run("returns ErrDashboardIdentifierNotSet", func(t *testing.T) {
|
|
||||||
setup()
|
func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
var sqlStore *sqlstore.SQLStore
|
||||||
DashboardUid: savedDashboard.Uid,
|
var dashboardStore *DashboardStore
|
||||||
OrgId: savedDashboard.OrgId,
|
var savedDashboard *models.Dashboard
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
|
||||||
IsPublic: true,
|
setup := func() {
|
||||||
PublicDashboard: models.PublicDashboard{
|
sqlStore = sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||||
DashboardUid: "",
|
dashboardStore = ProvideDashboardStore(sqlStore)
|
||||||
OrgId: savedDashboard.OrgId,
|
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||||
},
|
}
|
||||||
},
|
|
||||||
})
|
t.Run("updates an existing dashboard", func(t *testing.T) {
|
||||||
require.Error(t, models.ErrDashboardIdentifierNotSet, err)
|
setup()
|
||||||
})
|
|
||||||
|
pdUid := "asdf1234"
|
||||||
t.Run("overwrites existing public dashboard", func(t *testing.T) {
|
|
||||||
setup()
|
_, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
|
DashboardUid: savedDashboard.Uid,
|
||||||
pdUid := util.GenerateShortUID()
|
OrgId: savedDashboard.OrgId,
|
||||||
|
PublicDashboard: models.PublicDashboard{
|
||||||
// insert initial record
|
Uid: pdUid,
|
||||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
DashboardUid: savedDashboard.Uid,
|
||||||
DashboardUid: savedDashboard.Uid,
|
OrgId: savedDashboard.OrgId,
|
||||||
OrgId: savedDashboard.OrgId,
|
IsEnabled: true,
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
CreatedAt: DefaultTime,
|
||||||
IsPublic: true,
|
CreatedBy: 7,
|
||||||
PublicDashboard: models.PublicDashboard{
|
AccessToken: "NOTAREALUUID",
|
||||||
Uid: pdUid,
|
},
|
||||||
DashboardUid: savedDashboard.Uid,
|
})
|
||||||
OrgId: savedDashboard.OrgId,
|
require.NoError(t, err)
|
||||||
},
|
|
||||||
},
|
updatedPublicDashboard := models.PublicDashboard{
|
||||||
})
|
Uid: pdUid,
|
||||||
require.NoError(t, err)
|
DashboardUid: savedDashboard.Uid,
|
||||||
|
OrgId: savedDashboard.OrgId,
|
||||||
// update initial record
|
IsEnabled: false,
|
||||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
TimeSettings: simplejson.NewFromAny(map[string]interface{}{"from": "now-8", "to": "now"}),
|
||||||
DashboardUid: savedDashboard.Uid,
|
UpdatedAt: time.Now().UTC().Round(time.Second),
|
||||||
OrgId: savedDashboard.OrgId,
|
UpdatedBy: 8,
|
||||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
}
|
||||||
IsPublic: false,
|
// update initial record
|
||||||
PublicDashboard: models.PublicDashboard{
|
err = dashboardStore.UpdatePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||||
Uid: pdUid,
|
DashboardUid: savedDashboard.Uid,
|
||||||
DashboardUid: savedDashboard.Uid,
|
OrgId: savedDashboard.OrgId,
|
||||||
OrgId: savedDashboard.OrgId,
|
PublicDashboard: updatedPublicDashboard,
|
||||||
TimeSettings: "{}",
|
})
|
||||||
},
|
require.NoError(t, err)
|
||||||
},
|
|
||||||
})
|
pdRetrieved, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
assert.Equal(t, updatedPublicDashboard.UpdatedAt, pdRetrieved.UpdatedAt)
|
||||||
require.NoError(t, err)
|
// make sure we're correctly updated IsEnabled because we have to call
|
||||||
assert.Equal(t, resp, pdc)
|
// UseBool with xorm
|
||||||
|
assert.Equal(t, updatedPublicDashboard.IsEnabled, pdRetrieved.IsEnabled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ type SaveDashboardDTO struct {
|
|||||||
type SavePublicDashboardConfigDTO struct {
|
type SavePublicDashboardConfigDTO struct {
|
||||||
DashboardUid string
|
DashboardUid string
|
||||||
OrgId int64
|
OrgId int64
|
||||||
PublicDashboardConfig *models.PublicDashboardConfig
|
UserId int64
|
||||||
|
PublicDashboard *models.PublicDashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardSearchProjection struct {
|
type DashboardSearchProjection struct {
|
||||||
|
|||||||
@@ -2,46 +2,42 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gets public dashboard via generated Uid
|
// Gets public dashboard via access token
|
||||||
func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error) {
|
func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) {
|
||||||
pdc, d, err := dr.dashboardStore.GetPublicDashboard(dashboardUid)
|
pubdash, d, err := dr.dashboardStore.GetPublicDashboard(ctx, accessToken)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if pdc == nil || d == nil {
|
if pubdash == nil || d == nil {
|
||||||
return nil, models.ErrPublicDashboardNotFound
|
return nil, models.ErrPublicDashboardNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.IsPublic {
|
if !pubdash.IsEnabled {
|
||||||
return nil, models.ErrPublicDashboardNotFound
|
return nil, models.ErrPublicDashboardNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace dashboard time range with pubdash time range
|
ts := pubdash.BuildTimeSettings(d)
|
||||||
if pdc.TimeSettings != "" {
|
d.Data.SetPath([]string{"time", "from"}, ts.From)
|
||||||
var pdcTimeSettings map[string]interface{}
|
d.Data.SetPath([]string{"time", "to"}, ts.To)
|
||||||
err = json.Unmarshal([]byte(pdc.TimeSettings), &pdcTimeSettings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Data.Set("time", pdcTimeSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicDashboardConfig is a helper method to retrieve the public dashboard configuration for a given dashboard from the database
|
// GetPublicDashboardConfig is a helper method to retrieve the public dashboard configuration for a given dashboard from the database
|
||||||
func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||||
pdc, err := dr.dashboardStore.GetPublicDashboardConfig(orgId, dashboardUid)
|
pdc, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, orgId, dashboardUid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -51,53 +47,108 @@ func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, or
|
|||||||
|
|
||||||
// SavePublicDashboardConfig is a helper method to persist the sharing config
|
// SavePublicDashboardConfig is a helper method to persist the sharing config
|
||||||
// to the database. It handles validations for sharing config and persistence
|
// to the database. It handles validations for sharing config and persistence
|
||||||
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
|
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||||
cmd := models.SavePublicDashboardConfigCommand{
|
if len(dto.DashboardUid) == 0 {
|
||||||
DashboardUid: dto.DashboardUid,
|
return nil, models.ErrDashboardIdentifierNotSet
|
||||||
OrgId: dto.OrgId,
|
|
||||||
PublicDashboardConfig: *dto.PublicDashboardConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eventually we want this to propagate to array of public dashboards
|
// set default value for time settings
|
||||||
cmd.PublicDashboardConfig.PublicDashboard.OrgId = dto.OrgId
|
if dto.PublicDashboard.TimeSettings == nil {
|
||||||
cmd.PublicDashboardConfig.PublicDashboard.DashboardUid = dto.DashboardUid
|
json, err := simplejson.NewJson([]byte("{}"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto.PublicDashboard.TimeSettings = json
|
||||||
|
}
|
||||||
|
|
||||||
pdc, err := dr.dashboardStore.SavePublicDashboardConfig(cmd)
|
if dto.PublicDashboard.Uid == "" {
|
||||||
|
return dr.savePublicDashboardConfig(ctx, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dr.updatePublicDashboardConfig(ctx, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardServiceImpl) savePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||||
|
uid, err := dr.dashboardStore.GenerateNewPublicDashboardUid(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdc, nil
|
accessToken, err := GenerateAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := models.SavePublicDashboardConfigCommand{
|
||||||
|
DashboardUid: dto.DashboardUid,
|
||||||
|
OrgId: dto.OrgId,
|
||||||
|
PublicDashboard: models.PublicDashboard{
|
||||||
|
Uid: uid,
|
||||||
|
DashboardUid: dto.DashboardUid,
|
||||||
|
OrgId: dto.OrgId,
|
||||||
|
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||||
|
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||||
|
CreatedBy: dto.UserId,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return dr.dashboardStore.SavePublicDashboardConfig(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, publicDashboardUid string, panelId int64) (dtos.MetricRequest, error) {
|
func (dr *DashboardServiceImpl) updatePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||||
publicDashboardConfig, dashboard, err := dr.dashboardStore.GetPublicDashboard(publicDashboardUid)
|
cmd := models.SavePublicDashboardConfigCommand{
|
||||||
if err != nil {
|
PublicDashboard: models.PublicDashboard{
|
||||||
return dtos.MetricRequest{}, err
|
Uid: dto.PublicDashboard.Uid,
|
||||||
|
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||||
|
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||||
|
UpdatedBy: dto.UserId,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dashboard.IsPublic {
|
err := dr.dashboardStore.UpdatePublicDashboardConfig(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicDashboard, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, dto.OrgId, dto.DashboardUid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicDashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPublicDashboardMetricRequest merges public dashboard parameters with
|
||||||
|
// dashboard and returns a metrics request to be sent to query backend
|
||||||
|
func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||||
|
if !publicDashboard.IsEnabled {
|
||||||
return dtos.MetricRequest{}, models.ErrPublicDashboardNotFound
|
return dtos.MetricRequest{}, models.ErrPublicDashboardNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeSettings struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(publicDashboardConfig.TimeSettings), &timeSettings)
|
|
||||||
if err != nil {
|
|
||||||
return dtos.MetricRequest{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data)
|
queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data)
|
||||||
|
|
||||||
if _, ok := queriesByPanel[panelId]; !ok {
|
if _, ok := queriesByPanel[panelId]; !ok {
|
||||||
return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound
|
return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ts := publicDashboard.BuildTimeSettings(dashboard)
|
||||||
|
|
||||||
return dtos.MetricRequest{
|
return dtos.MetricRequest{
|
||||||
From: timeSettings.From,
|
From: ts.From,
|
||||||
To: timeSettings.To,
|
To: ts.To,
|
||||||
Queries: queriesByPanel[panelId],
|
Queries: queriesByPanel[panelId],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generates a uuid formatted without dashes to use as access token
|
||||||
|
func GenerateAccessToken() (string, error) {
|
||||||
|
token, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", token), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@@ -15,6 +17,11 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var timeSettings, _ = simplejson.NewJson([]byte(`{"from": "now-12", "to": "now"}`))
|
||||||
|
var defaultPubdashTimeSettings, _ = simplejson.NewJson([]byte(`{}`))
|
||||||
|
var dashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-8", "to": "now"}})
|
||||||
|
var mergedDashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-12", "to": "now"}})
|
||||||
|
|
||||||
func TestGetPublicDashboard(t *testing.T) {
|
func TestGetPublicDashboard(t *testing.T) {
|
||||||
type storeResp struct {
|
type storeResp struct {
|
||||||
pd *models.PublicDashboard
|
pd *models.PublicDashboard
|
||||||
@@ -23,78 +30,90 @@ func TestGetPublicDashboard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
Name string
|
||||||
uid string
|
AccessToken string
|
||||||
storeResp *storeResp
|
StoreResp *storeResp
|
||||||
errResp error
|
ErrResp error
|
||||||
dashResp *models.Dashboard
|
DashResp *models.Dashboard
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "returns a dashboard",
|
Name: "returns a dashboard",
|
||||||
uid: "abc123",
|
AccessToken: "abc123",
|
||||||
storeResp: &storeResp{pd: &models.PublicDashboard{}, d: &models.Dashboard{IsPublic: true}, err: nil},
|
StoreResp: &storeResp{
|
||||||
errResp: nil,
|
pd: &models.PublicDashboard{IsEnabled: true},
|
||||||
dashResp: &models.Dashboard{IsPublic: true},
|
d: &models.Dashboard{Uid: "mydashboard", Data: dashboardData},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
ErrResp: nil,
|
||||||
|
DashResp: &models.Dashboard{Uid: "mydashboard", Data: dashboardData},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "puts pubdash time settings into dashboard",
|
Name: "puts pubdash time settings into dashboard",
|
||||||
uid: "abc123",
|
AccessToken: "abc123",
|
||||||
storeResp: &storeResp{
|
StoreResp: &storeResp{
|
||||||
pd: &models.PublicDashboard{TimeSettings: `{"from": "now-8", "to": "now"}`},
|
pd: &models.PublicDashboard{IsEnabled: true, TimeSettings: timeSettings},
|
||||||
d: &models.Dashboard{
|
d: &models.Dashboard{Data: dashboardData},
|
||||||
IsPublic: true,
|
err: nil,
|
||||||
Data: simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "abc", "to": "123"}}),
|
|
||||||
},
|
},
|
||||||
err: nil},
|
ErrResp: nil,
|
||||||
errResp: nil,
|
DashResp: &models.Dashboard{Data: mergedDashboardData},
|
||||||
dashResp: &models.Dashboard{IsPublic: true, Data: simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-8", "to": "now"}})},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns ErrPublicDashboardNotFound when isPublic is false",
|
Name: "returns ErrPublicDashboardNotFound when isEnabled is false",
|
||||||
uid: "abc123",
|
AccessToken: "abc123",
|
||||||
storeResp: &storeResp{pd: &models.PublicDashboard{}, d: &models.Dashboard{IsPublic: false}, err: nil},
|
StoreResp: &storeResp{
|
||||||
errResp: models.ErrPublicDashboardNotFound,
|
pd: &models.PublicDashboard{IsEnabled: false},
|
||||||
dashResp: nil,
|
d: &models.Dashboard{Uid: "mydashboard"},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
ErrResp: models.ErrPublicDashboardNotFound,
|
||||||
|
DashResp: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns ErrPublicDashboardNotFound if PublicDashboard missing",
|
Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing",
|
||||||
uid: "abc123",
|
AccessToken: "abc123",
|
||||||
storeResp: &storeResp{pd: nil, d: nil, err: nil},
|
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
||||||
errResp: models.ErrPublicDashboardNotFound,
|
ErrResp: models.ErrPublicDashboardNotFound,
|
||||||
dashResp: nil,
|
DashResp: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns ErrPublicDashboardNotFound if Dashboard missing",
|
Name: "returns ErrPublicDashboardNotFound if Dashboard missing",
|
||||||
uid: "abc123",
|
AccessToken: "abc123",
|
||||||
storeResp: &storeResp{pd: nil, d: nil, err: nil},
|
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
||||||
errResp: models.ErrPublicDashboardNotFound,
|
ErrResp: models.ErrPublicDashboardNotFound,
|
||||||
dashResp: nil,
|
DashResp: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
fakeStore := dashboards.FakeDashboardStore{}
|
fakeStore := dashboards.FakeDashboardStore{}
|
||||||
service := &DashboardServiceImpl{
|
service := &DashboardServiceImpl{
|
||||||
log: log.New("test.logger"),
|
log: log.New("test.logger"),
|
||||||
dashboardStore: &fakeStore,
|
dashboardStore: &fakeStore,
|
||||||
}
|
}
|
||||||
fakeStore.On("GetPublicDashboard", mock.Anything).
|
|
||||||
Return(test.storeResp.pd, test.storeResp.d, test.storeResp.err)
|
|
||||||
|
|
||||||
dashboard, err := service.GetPublicDashboard(context.Background(), test.uid)
|
fakeStore.On("GetPublicDashboard", mock.Anything, mock.Anything).
|
||||||
if test.errResp != nil {
|
Return(test.StoreResp.pd, test.StoreResp.d, test.StoreResp.err)
|
||||||
assert.Error(t, test.errResp, err)
|
|
||||||
|
dashboard, err := service.GetPublicDashboard(context.Background(), test.AccessToken)
|
||||||
|
if test.ErrResp != nil {
|
||||||
|
assert.Error(t, test.ErrResp, err)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, test.dashResp, dashboard)
|
|
||||||
|
assert.Equal(t, test.DashResp, dashboard)
|
||||||
|
|
||||||
|
if test.DashResp != nil {
|
||||||
|
assert.NotNil(t, dashboard.CreatedBy)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSavePublicDashboard(t *testing.T) {
|
func TestSavePublicDashboard(t *testing.T) {
|
||||||
t.Run("gets PublicDashboard.orgId and PublicDashboard.DashboardUid set from SavePublicDashboardConfigDTO", func(t *testing.T) {
|
t.Run("Saving public dashboard", func(t *testing.T) {
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||||
@@ -107,56 +126,197 @@ func TestSavePublicDashboard(t *testing.T) {
|
|||||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
DashboardUid: dashboard.Uid,
|
DashboardUid: dashboard.Uid,
|
||||||
OrgId: dashboard.OrgId,
|
OrgId: dashboard.OrgId,
|
||||||
PublicDashboardConfig: &models.PublicDashboardConfig{
|
UserId: 7,
|
||||||
IsPublic: true,
|
PublicDashboard: &models.PublicDashboard{
|
||||||
PublicDashboard: models.PublicDashboard{
|
IsEnabled: true,
|
||||||
DashboardUid: "NOTTHESAME",
|
DashboardUid: "NOTTHESAME",
|
||||||
OrgId: 9999999,
|
OrgId: 9999999,
|
||||||
},
|
TimeSettings: timeSettings,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pdc, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dashboard.Uid, pdc.PublicDashboard.DashboardUid)
|
pubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
assert.Equal(t, dashboard.OrgId, pdc.PublicDashboard.OrgId)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// DashboardUid/OrgId/CreatedBy set by the command, not parameters
|
||||||
|
assert.Equal(t, dashboard.Uid, pubdash.DashboardUid)
|
||||||
|
assert.Equal(t, dashboard.OrgId, pubdash.OrgId)
|
||||||
|
assert.Equal(t, dto.UserId, pubdash.CreatedBy)
|
||||||
|
// IsEnabled set by parameters
|
||||||
|
assert.Equal(t, dto.PublicDashboard.IsEnabled, pubdash.IsEnabled)
|
||||||
|
// CreatedAt set to non-zero time
|
||||||
|
assert.NotEqual(t, &time.Time{}, pubdash.CreatedAt)
|
||||||
|
// Time settings set by db
|
||||||
|
assert.Equal(t, timeSettings, pubdash.TimeSettings)
|
||||||
|
// accessToken is valid uuid
|
||||||
|
_, err = uuid.FromString(pubdash.AccessToken)
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("PLACEHOLDER - dashboard with template variables cannot be saved", func(t *testing.T) {
|
t.Run("Validate pubdash has default time setting value", func(t *testing.T) {
|
||||||
//sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
//dashboardStore := database.ProvideDashboardStore(sqlStore)
|
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||||
//dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||||
|
|
||||||
//service := &DashboardServiceImpl{
|
service := &DashboardServiceImpl{
|
||||||
//log: log.New("test.logger"),
|
log: log.New("test.logger"),
|
||||||
//dashboardStore: dashboardStore,
|
dashboardStore: dashboardStore,
|
||||||
//}
|
}
|
||||||
|
|
||||||
//dto := &dashboards.SavePublicDashboardConfigDTO{
|
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
//DashboardUid: dashboard.Uid,
|
DashboardUid: dashboard.Uid,
|
||||||
//OrgId: dashboard.OrgId,
|
OrgId: dashboard.OrgId,
|
||||||
//PublicDashboardConfig: &models.PublicDashboardConfig{
|
UserId: 7,
|
||||||
//IsPublic: true,
|
PublicDashboard: &models.PublicDashboard{
|
||||||
//PublicDashboard: models.PublicDashboard{
|
IsEnabled: true,
|
||||||
//DashboardUid: "NOTTHESAME",
|
DashboardUid: "NOTTHESAME",
|
||||||
//OrgId: 9999999,
|
OrgId: 9999999,
|
||||||
//},
|
},
|
||||||
//},
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
//pdc, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
//require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
//assert.Equal(t, dashboard.Uid, pdc.PublicDashboard.DashboardUid)
|
pubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
//assert.Equal(t, dashboard.OrgId, pdc.PublicDashboard.OrgId)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, defaultPubdashTimeSettings, pubdash.TimeSettings)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PLACEHOLDER - dashboard with template variables cannot be saved", func(t *testing.T) {})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdatePublicDashboard(t *testing.T) {
|
||||||
|
t.Run("Updating public dashboard", func(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
OrgId: dashboard.OrgId,
|
||||||
|
UserId: 7,
|
||||||
|
PublicDashboard: &models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
|
TimeSettings: timeSettings,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
savedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// attempt to overwrite settings
|
||||||
|
dto = &dashboards.SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
OrgId: dashboard.OrgId,
|
||||||
|
UserId: 8,
|
||||||
|
PublicDashboard: &models.PublicDashboard{
|
||||||
|
Uid: savedPubdash.Uid,
|
||||||
|
OrgId: 9,
|
||||||
|
DashboardUid: "abc1234",
|
||||||
|
CreatedBy: 9,
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
|
||||||
|
IsEnabled: true,
|
||||||
|
TimeSettings: timeSettings,
|
||||||
|
AccessToken: "NOTAREALUUID",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the dto.PublicDashboard has a uid, this will call
|
||||||
|
// service.updatePublicDashboardConfig
|
||||||
|
_, err = service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
updatedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// don't get updated
|
||||||
|
assert.Equal(t, savedPubdash.DashboardUid, updatedPubdash.DashboardUid)
|
||||||
|
assert.Equal(t, savedPubdash.OrgId, updatedPubdash.OrgId)
|
||||||
|
assert.Equal(t, savedPubdash.CreatedAt, updatedPubdash.CreatedAt)
|
||||||
|
assert.Equal(t, savedPubdash.CreatedBy, updatedPubdash.CreatedBy)
|
||||||
|
assert.Equal(t, savedPubdash.AccessToken, updatedPubdash.AccessToken)
|
||||||
|
|
||||||
|
// gets updated
|
||||||
|
assert.Equal(t, dto.PublicDashboard.IsEnabled, updatedPubdash.IsEnabled)
|
||||||
|
assert.Equal(t, dto.PublicDashboard.TimeSettings, updatedPubdash.TimeSettings)
|
||||||
|
assert.Equal(t, dto.UserId, updatedPubdash.UpdatedBy)
|
||||||
|
assert.NotEqual(t, &time.Time{}, updatedPubdash.UpdatedAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Updating set empty time settings", func(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
OrgId: dashboard.OrgId,
|
||||||
|
UserId: 7,
|
||||||
|
PublicDashboard: &models.PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
|
TimeSettings: timeSettings,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the dto.PublicDashboard has a uid, this will call
|
||||||
|
// service.updatePublicDashboardConfig
|
||||||
|
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
savedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// attempt to overwrite settings
|
||||||
|
dto = &dashboards.SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: dashboard.Uid,
|
||||||
|
OrgId: dashboard.OrgId,
|
||||||
|
UserId: 8,
|
||||||
|
PublicDashboard: &models.PublicDashboard{
|
||||||
|
Uid: savedPubdash.Uid,
|
||||||
|
OrgId: 9,
|
||||||
|
DashboardUid: "abc1234",
|
||||||
|
CreatedBy: 9,
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
|
||||||
|
IsEnabled: true,
|
||||||
|
AccessToken: "NOTAREALUUID",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
updatedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
timeSettings, err := simplejson.NewJson([]byte("{}"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, timeSettings, updatedPubdash.TimeSettings)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||||
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true)
|
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true)
|
||||||
|
|
||||||
service := &DashboardServiceImpl{
|
service := &DashboardServiceImpl{
|
||||||
@@ -165,47 +325,44 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
DashboardUid: dashboard.Uid,
|
DashboardUid: publicDashboard.Uid,
|
||||||
OrgId: dashboard.OrgId,
|
OrgId: publicDashboard.OrgId,
|
||||||
PublicDashboardConfig: &models.PublicDashboardConfig{
|
PublicDashboard: &models.PublicDashboard{
|
||||||
IsPublic: true,
|
IsEnabled: true,
|
||||||
PublicDashboard: models.PublicDashboard{
|
|
||||||
DashboardUid: "NOTTHESAME",
|
DashboardUid: "NOTTHESAME",
|
||||||
OrgId: 9999999,
|
OrgId: 9999999,
|
||||||
TimeSettings: `{"from": "FROM", "to": "TO"}`,
|
TimeSettings: timeSettings,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pdc, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
publicDashboardPD, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
nonPublicDto := &dashboards.SavePublicDashboardConfigDTO{
|
nonPublicDto := &dashboards.SavePublicDashboardConfigDTO{
|
||||||
DashboardUid: nonPublicDashboard.Uid,
|
DashboardUid: nonPublicDashboard.Uid,
|
||||||
OrgId: nonPublicDashboard.OrgId,
|
OrgId: nonPublicDashboard.OrgId,
|
||||||
PublicDashboardConfig: &models.PublicDashboardConfig{
|
PublicDashboard: &models.PublicDashboard{
|
||||||
IsPublic: false,
|
IsEnabled: false,
|
||||||
PublicDashboard: models.PublicDashboard{
|
|
||||||
DashboardUid: "NOTTHESAME",
|
DashboardUid: "NOTTHESAME",
|
||||||
OrgId: 9999999,
|
OrgId: 9999999,
|
||||||
TimeSettings: `{"from": "FROM", "to": "TO"}`,
|
TimeSettings: defaultPubdashTimeSettings,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
nonPublicPdc, err := service.SavePublicDashboardConfig(context.Background(), nonPublicDto)
|
nonPublicDashboardPD, err := service.SavePublicDashboardConfig(context.Background(), nonPublicDto)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
||||||
reqDTO, err := service.BuildPublicDashboardMetricRequest(
|
reqDTO, err := service.BuildPublicDashboardMetricRequest(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
pdc.PublicDashboard.Uid,
|
publicDashboard,
|
||||||
|
publicDashboardPD,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "FROM", reqDTO.From)
|
require.Equal(t, timeSettings.Get("from").MustString(), reqDTO.From)
|
||||||
require.Equal(t, "TO", reqDTO.To)
|
require.Equal(t, timeSettings.Get("to").MustString(), reqDTO.To)
|
||||||
require.Len(t, reqDTO.Queries, 2)
|
require.Len(t, reqDTO.Queries, 2)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t,
|
t,
|
||||||
@@ -234,7 +391,8 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
t.Run("returns an error when panel missing", func(t *testing.T) {
|
t.Run("returns an error when panel missing", func(t *testing.T) {
|
||||||
_, err := service.BuildPublicDashboardMetricRequest(
|
_, err := service.BuildPublicDashboardMetricRequest(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
pdc.PublicDashboard.Uid,
|
publicDashboard,
|
||||||
|
publicDashboardPD,
|
||||||
49,
|
49,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -244,7 +402,8 @@ func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
|||||||
t.Run("returns an error when dashboard not public", func(t *testing.T) {
|
t.Run("returns an error when dashboard not public", func(t *testing.T) {
|
||||||
_, err := service.BuildPublicDashboardMetricRequest(
|
_, err := service.BuildPublicDashboardMetricRequest(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
nonPublicPdc.PublicDashboard.Uid,
|
nonPublicDashboard,
|
||||||
|
nonPublicDashboardPD,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
require.ErrorContains(t, err, "Public dashboard not found")
|
require.ErrorContains(t, err, "Public dashboard not found")
|
||||||
@@ -262,19 +421,19 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
|||||||
"id": nil,
|
"id": nil,
|
||||||
"title": title,
|
"title": title,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"panels": []map[string]interface{}{
|
"panels": []interface{}{
|
||||||
{
|
map[string]interface{}{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"targets": []map[string]interface{}{
|
"targets": []interface{}{
|
||||||
{
|
map[string]interface{}{
|
||||||
"datasource": map[string]string{
|
"datasource": map[string]interface{}{
|
||||||
"type": "mysql",
|
"type": "mysql",
|
||||||
"uid": "ds1",
|
"uid": "ds1",
|
||||||
},
|
},
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
},
|
},
|
||||||
{
|
map[string]interface{}{
|
||||||
"datasource": map[string]string{
|
"datasource": map[string]interface{}{
|
||||||
"type": "prometheus",
|
"type": "prometheus",
|
||||||
"uid": "ds2",
|
"uid": "ds2",
|
||||||
},
|
},
|
||||||
@@ -282,11 +441,11 @@ func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore,
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
map[string]interface{}{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"targets": []map[string]interface{}{
|
"targets": []interface{}{
|
||||||
{
|
map[string]interface{}{
|
||||||
"datasource": map[string]string{
|
"datasource": map[string]interface{}{
|
||||||
"type": "mysql",
|
"type": "mysql",
|
||||||
"uid": "ds3",
|
"uid": "ds3",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,6 +67,27 @@ func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *models.
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateNewPublicDashboardUid provides a mock function with given fields: ctx
|
||||||
|
func (_m *FakeDashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
||||||
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
|
||||||
|
r0 = rf(ctx)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||||
|
r1 = rf(ctx)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// GetDashboard provides a mock function with given fields: ctx, query
|
// GetDashboard provides a mock function with given fields: ctx, query
|
||||||
func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) {
|
func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) {
|
||||||
ret := _m.Called(ctx, query)
|
ret := _m.Called(ctx, query)
|
||||||
@@ -298,13 +319,13 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dash
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicDashboard provides a mock function with given fields: uid
|
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
||||||
func (_m *FakeDashboardStore) GetPublicDashboard(uid string) (*models.PublicDashboard, *models.Dashboard, error) {
|
func (_m *FakeDashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) {
|
||||||
ret := _m.Called(uid)
|
ret := _m.Called(ctx, accessToken)
|
||||||
|
|
||||||
var r0 *models.PublicDashboard
|
var r0 *models.PublicDashboard
|
||||||
if rf, ok := ret.Get(0).(func(string) *models.PublicDashboard); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) *models.PublicDashboard); ok {
|
||||||
r0 = rf(uid)
|
r0 = rf(ctx, accessToken)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||||
@@ -312,8 +333,8 @@ func (_m *FakeDashboardStore) GetPublicDashboard(uid string) (*models.PublicDash
|
|||||||
}
|
}
|
||||||
|
|
||||||
var r1 *models.Dashboard
|
var r1 *models.Dashboard
|
||||||
if rf, ok := ret.Get(1).(func(string) *models.Dashboard); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, string) *models.Dashboard); ok {
|
||||||
r1 = rf(uid)
|
r1 = rf(ctx, accessToken)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(1) != nil {
|
if ret.Get(1) != nil {
|
||||||
r1 = ret.Get(1).(*models.Dashboard)
|
r1 = ret.Get(1).(*models.Dashboard)
|
||||||
@@ -321,8 +342,8 @@ func (_m *FakeDashboardStore) GetPublicDashboard(uid string) (*models.PublicDash
|
|||||||
}
|
}
|
||||||
|
|
||||||
var r2 error
|
var r2 error
|
||||||
if rf, ok := ret.Get(2).(func(string) error); ok {
|
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
|
||||||
r2 = rf(uid)
|
r2 = rf(ctx, accessToken)
|
||||||
} else {
|
} else {
|
||||||
r2 = ret.Error(2)
|
r2 = ret.Error(2)
|
||||||
}
|
}
|
||||||
@@ -330,22 +351,22 @@ func (_m *FakeDashboardStore) GetPublicDashboard(uid string) (*models.PublicDash
|
|||||||
return r0, r1, r2
|
return r0, r1, r2
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicDashboardConfig provides a mock function with given fields: orgId, dashboardUid
|
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
||||||
func (_m *FakeDashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
func (_m *FakeDashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||||
ret := _m.Called(orgId, dashboardUid)
|
ret := _m.Called(ctx, orgId, dashboardUid)
|
||||||
|
|
||||||
var r0 *models.PublicDashboardConfig
|
var r0 *models.PublicDashboard
|
||||||
if rf, ok := ret.Get(0).(func(int64, string) *models.PublicDashboardConfig); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok {
|
||||||
r0 = rf(orgId, dashboardUid)
|
r0 = rf(ctx, orgId, dashboardUid)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.PublicDashboardConfig)
|
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||||
r1 = rf(orgId, dashboardUid)
|
r1 = rf(ctx, orgId, dashboardUid)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -441,22 +462,22 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(cmd models.SaveDashboardC
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePublicDashboardConfig provides a mock function with given fields: cmd
|
// SavePublicDashboardConfig provides a mock function with given fields: ctx, cmd
|
||||||
func (_m *FakeDashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error) {
|
func (_m *FakeDashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) {
|
||||||
ret := _m.Called(cmd)
|
ret := _m.Called(ctx, cmd)
|
||||||
|
|
||||||
var r0 *models.PublicDashboardConfig
|
var r0 *models.PublicDashboard
|
||||||
if rf, ok := ret.Get(0).(func(models.SavePublicDashboardConfigCommand) *models.PublicDashboardConfig); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) *models.PublicDashboard); ok {
|
||||||
r0 = rf(cmd)
|
r0 = rf(ctx, cmd)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.PublicDashboardConfig)
|
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(models.SavePublicDashboardConfigCommand) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
|
||||||
r1 = rf(cmd)
|
r1 = rf(ctx, cmd)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -492,6 +513,20 @@ func (_m *FakeDashboardStore) UpdateDashboardACL(ctx context.Context, uid int64,
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePublicDashboardConfig provides a mock function with given fields: ctx, cmd
|
||||||
|
func (_m *FakeDashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
|
||||||
|
ret := _m.Called(ctx, cmd)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
|
||||||
|
r0 = rf(ctx, cmd)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateDashboardBeforeSave provides a mock function with given fields: dashboard, overwrite
|
// ValidateDashboardBeforeSave provides a mock function with given fields: dashboard, overwrite
|
||||||
func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
|
func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
|
||||||
ret := _m.Called(dashboard, overwrite)
|
ret := _m.Called(dashboard, overwrite)
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addPublicDashboardMigration(mg *Migrator) {
|
|
||||||
var dashboardPublicCfgV1 = Table{
|
|
||||||
Name: "dashboard_public_config",
|
|
||||||
Columns: []*Column{
|
|
||||||
{Name: "uid", Type: DB_NVarchar, Length: 40, IsPrimaryKey: true},
|
|
||||||
{Name: "dashboard_uid", Type: DB_NVarchar, Length: 40, Nullable: false},
|
|
||||||
{Name: "org_id", Type: DB_BigInt, Nullable: false},
|
|
||||||
{Name: "time_settings", Type: DB_Text, Nullable: false},
|
|
||||||
{Name: "refresh_rate", Type: DB_Int, Nullable: false, Default: "30"},
|
|
||||||
{Name: "template_variables", Type: DB_MediumText, Nullable: true},
|
|
||||||
},
|
|
||||||
Indices: []*Index{
|
|
||||||
{Cols: []string{"uid"}, Type: UniqueIndex},
|
|
||||||
{Cols: []string{"org_id", "dashboard_uid"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mg.AddMigration("create dashboard public config v1", NewAddTableMigration(dashboardPublicCfgV1))
|
|
||||||
|
|
||||||
// table has no dependencies and was created with incorrect pkey type.
|
|
||||||
// drop then recreate with correct values
|
|
||||||
addDropAllIndicesMigrations(mg, "v1", dashboardPublicCfgV1)
|
|
||||||
mg.AddMigration("Drop old dashboard public config table", NewDropTableMigration("dashboard_public_config"))
|
|
||||||
|
|
||||||
// recreate table with proper primary key type
|
|
||||||
mg.AddMigration("recreate dashboard public config v1", NewAddTableMigration(dashboardPublicCfgV1))
|
|
||||||
addTableIndicesMigrations(mg, "v1", dashboardPublicCfgV1)
|
|
||||||
}
|
|
||||||
52
pkg/services/sqlstore/migrations/dashboard_public_mig.go
Normal file
52
pkg/services/sqlstore/migrations/dashboard_public_mig.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addPublicDashboardMigration(mg *Migrator) {
|
||||||
|
var dashboardPublicCfgV1 = Table{
|
||||||
|
Name: "dashboard_public_config",
|
||||||
|
Columns: []*Column{
|
||||||
|
{Name: "uid", Type: DB_NVarchar, Length: 40, IsPrimaryKey: true},
|
||||||
|
{Name: "dashboard_uid", Type: DB_NVarchar, Length: 40, Nullable: false},
|
||||||
|
{Name: "org_id", Type: DB_BigInt, Nullable: false},
|
||||||
|
|
||||||
|
{Name: "time_settings", Type: DB_Text, Nullable: true},
|
||||||
|
{Name: "template_variables", Type: DB_MediumText, Nullable: true},
|
||||||
|
|
||||||
|
{Name: "access_token", Type: DB_NVarchar, Length: 32, Nullable: false},
|
||||||
|
|
||||||
|
{Name: "created_by", Type: DB_Int, Nullable: false},
|
||||||
|
{Name: "updated_by", Type: DB_Int, Nullable: true},
|
||||||
|
|
||||||
|
{Name: "created_at", Type: DB_DateTime, Nullable: false},
|
||||||
|
{Name: "updated_at", Type: DB_DateTime, Nullable: true},
|
||||||
|
|
||||||
|
{Name: "is_enabled", Type: DB_Bool, Nullable: false, Default: "0"},
|
||||||
|
},
|
||||||
|
Indices: []*Index{
|
||||||
|
{Cols: []string{"uid"}, Type: UniqueIndex},
|
||||||
|
{Cols: []string{"org_id", "dashboard_uid"}},
|
||||||
|
{Cols: []string{"access_token"}, Type: UniqueIndex},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial create table
|
||||||
|
mg.AddMigration("create dashboard public config v1", NewAddTableMigration(dashboardPublicCfgV1))
|
||||||
|
|
||||||
|
// recreate table - no dependencies and was created with incorrect pkey type
|
||||||
|
addDropAllIndicesMigrations(mg, "v1", dashboardPublicCfgV1)
|
||||||
|
mg.AddMigration("Drop old dashboard public config table", NewDropTableMigration("dashboard_public_config"))
|
||||||
|
mg.AddMigration("recreate dashboard public config v1", NewAddTableMigration(dashboardPublicCfgV1))
|
||||||
|
addTableIndicesMigrations(mg, "v1", dashboardPublicCfgV1)
|
||||||
|
|
||||||
|
// recreate table - schema finalized for public dashboards v1
|
||||||
|
addDropAllIndicesMigrations(mg, "v2", dashboardPublicCfgV1)
|
||||||
|
mg.AddMigration("Drop public config table", NewDropTableMigration("dashboard_public_config"))
|
||||||
|
mg.AddMigration("Recreate dashboard public config v2", NewAddTableMigration(dashboardPublicCfgV1))
|
||||||
|
addTableIndicesMigrations(mg, "v2", dashboardPublicCfgV1)
|
||||||
|
|
||||||
|
// rename table
|
||||||
|
addTableRenameMigration(mg, "dashboard_public_config", "dashboard_public", "v2")
|
||||||
|
}
|
||||||
@@ -10423,7 +10423,7 @@
|
|||||||
"provisionedExternalId": {
|
"provisionedExternalId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"publicDashboardUid": {
|
"publicDashboardAccessToken": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
|
|||||||
@@ -9465,7 +9465,7 @@
|
|||||||
"provisionedExternalId": {
|
"provisionedExternalId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"publicDashboardUid": {
|
"publicDashboardAccessToken": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import config from 'app/core/config';
|
|||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
import { ShareModal } from './ShareModal';
|
import { ShareModal } from './ShareModal';
|
||||||
import { PublicDashboardConfig } from './SharePublicDashboardUtils';
|
import { PublicDashboard } from './SharePublicDashboardUtils';
|
||||||
|
|
||||||
// Mock api request
|
// Mock api request
|
||||||
const publicDashboardconfigResp: PublicDashboardConfig = {
|
const publicDashboardconfigResp: PublicDashboard = {
|
||||||
isPublic: true,
|
isEnabled: true,
|
||||||
publicDashboard: { uid: '', dashboardUid: '' },
|
uid: '',
|
||||||
|
dashboardUid: '',
|
||||||
|
accessToken: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const backendSrv = {
|
const backendSrv = {
|
||||||
@@ -88,6 +90,13 @@ describe('SharePublic', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText('Public Dashboard'));
|
fireEvent.click(screen.getByText('Public Dashboard'));
|
||||||
|
|
||||||
await waitFor(() => screen.getByText('Enabled'));
|
await screen.findByText('Welcome to Grafana public dashboards alpha!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// test when checkboxes show up
|
||||||
|
// test checkboxes hidden
|
||||||
|
// test url hidden
|
||||||
|
// test url shows up
|
||||||
|
//
|
||||||
|
// test checking if current version of dashboard in state is persisted to db
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,34 +1,46 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Button, Field, Switch, Alert } from '@grafana/ui';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { Alert, Button, Checkbox, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@grafana/ui';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { VariableModel } from 'app/features/variables/types';
|
import { appEvents } from 'app/core/core';
|
||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dashboardHasTemplateVariables,
|
dashboardHasTemplateVariables,
|
||||||
|
generatePublicDashboardUrl,
|
||||||
getPublicDashboardConfig,
|
getPublicDashboardConfig,
|
||||||
|
PublicDashboard,
|
||||||
|
publicDashboardPersisted,
|
||||||
savePublicDashboardConfig,
|
savePublicDashboardConfig,
|
||||||
PublicDashboardConfig,
|
|
||||||
} from './SharePublicDashboardUtils';
|
} from './SharePublicDashboardUtils';
|
||||||
import { ShareModalTabProps } from './types';
|
import { ShareModalTabProps } from './types';
|
||||||
|
|
||||||
interface Props extends ShareModalTabProps {}
|
interface Props extends ShareModalTabProps {}
|
||||||
|
|
||||||
|
interface Acknowledgements {
|
||||||
|
public: boolean;
|
||||||
|
datasources: boolean;
|
||||||
|
usage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const SharePublicDashboard = (props: Props) => {
|
export const SharePublicDashboard = (props: Props) => {
|
||||||
const dashboardUid = props.dashboard.uid;
|
const dashboardVariables = props.dashboard.getVariables();
|
||||||
const [publicDashboardConfig, setPublicDashboardConfig] = useState<PublicDashboardConfig>({
|
const [publicDashboard, setPublicDashboardConfig] = useState<PublicDashboard>({
|
||||||
isPublic: false,
|
isEnabled: false,
|
||||||
publicDashboard: { uid: '', dashboardUid },
|
uid: '',
|
||||||
|
dashboardUid: props.dashboard.uid,
|
||||||
|
});
|
||||||
|
const [acknowledgements, setAcknowledgements] = useState<Acknowledgements>({
|
||||||
|
public: false,
|
||||||
|
datasources: false,
|
||||||
|
usage: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [dashboardVariables, setDashboardVariables] = useState<VariableModel[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDashboardVariables(props.dashboard.getVariables());
|
getPublicDashboardConfig(props.dashboard.uid, setPublicDashboardConfig).catch();
|
||||||
getPublicDashboardConfig(dashboardUid, setPublicDashboardConfig).catch();
|
}, [props.dashboard.uid]);
|
||||||
}, [props, dashboardUid]);
|
|
||||||
|
|
||||||
const onSavePublicConfig = () => {
|
const onSavePublicConfig = () => {
|
||||||
if (dashboardHasTemplateVariables(dashboardVariables)) {
|
if (dashboardHasTemplateVariables(dashboardVariables)) {
|
||||||
@@ -38,32 +50,143 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
savePublicDashboardConfig(props.dashboard.uid, publicDashboardConfig, setPublicDashboardConfig).catch();
|
savePublicDashboardConfig(props.dashboard.uid, publicDashboard, setPublicDashboardConfig).catch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShareUrlCopy = () => {
|
||||||
|
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAcknowledge = useCallback(
|
||||||
|
(field: string, checked: boolean) => {
|
||||||
|
setAcknowledgements({ ...acknowledgements, [field]: checked });
|
||||||
|
},
|
||||||
|
[acknowledgements]
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if all conditions have been acknowledged
|
||||||
|
const acknowledged = () => {
|
||||||
|
return acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{dashboardHasTemplateVariables(dashboardVariables) && (
|
<p>Welcome to Grafana public dashboards alpha!</p>
|
||||||
|
{dashboardHasTemplateVariables(dashboardVariables) ? (
|
||||||
<Alert severity="warning" title="dashboard cannot be public">
|
<Alert severity="warning" title="dashboard cannot be public">
|
||||||
This dashboard cannot be made public because it has template variables
|
This dashboard cannot be made public because it has template variables
|
||||||
</Alert>
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
To allow the current dashboard to be published publicly, toggle the switch. For now we do not support
|
||||||
|
template variables or frontend datasources.
|
||||||
|
</p>
|
||||||
|
We'd love your feedback. To share, please comment on this{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/grafana/grafana/discussions/49253"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-link"
|
||||||
|
>
|
||||||
|
github discussion
|
||||||
|
</a>
|
||||||
|
<hr />
|
||||||
|
{!publicDashboardPersisted(publicDashboard) && (
|
||||||
|
<div>
|
||||||
|
Before you click Save, please acknowledge the following information: <br />
|
||||||
|
<FieldSet>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
label="Your entire dashboard will be public"
|
||||||
|
value={acknowledgements.public}
|
||||||
|
onChange={(e) => onAcknowledge('public', e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
label="Publishing currently only works with a subset of datasources"
|
||||||
|
value={acknowledgements.datasources}
|
||||||
|
description="Learn more about public datasources"
|
||||||
|
onChange={(e) => onAcknowledge('datasources', e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<Checkbox
|
||||||
|
label="Making your dashboard public will cause queries to run each time the dashboard is viewed which may increase costs"
|
||||||
|
value={acknowledgements.usage}
|
||||||
|
description="Learn more about query caching"
|
||||||
|
onChange={(e) => onAcknowledge('usage', e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</FieldSet>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(publicDashboardPersisted(publicDashboard) || acknowledged()) && (
|
||||||
<p className="share-modal-info-text">Public Dashboard Configuration</p>
|
<div>
|
||||||
|
<h4 className="share-modal-info-text">Public Dashboard Configuration</h4>
|
||||||
|
<FieldSet>
|
||||||
|
Time Range
|
||||||
|
<br />
|
||||||
|
<div style={{ padding: '5px' }}>
|
||||||
|
<Input
|
||||||
|
value={props.dashboard.time.from}
|
||||||
|
disabled={true}
|
||||||
|
addonBefore={
|
||||||
|
<span style={{ width: '50px', display: 'flex', alignItems: 'center', padding: '5px' }}>
|
||||||
|
From:
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={props.dashboard.time.to}
|
||||||
|
disabled={true}
|
||||||
|
addonBefore={
|
||||||
|
<span style={{ width: '50px', display: 'flex', alignItems: 'center', padding: '5px' }}>To:</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
<Field label="Enabled" description="Configures whether current dashboard can be available publicly">
|
<Field label="Enabled" description="Configures whether current dashboard can be available publicly">
|
||||||
<Switch
|
<Switch
|
||||||
id="share-current-time-range"
|
|
||||||
disabled={dashboardHasTemplateVariables(dashboardVariables)}
|
disabled={dashboardHasTemplateVariables(dashboardVariables)}
|
||||||
value={publicDashboardConfig?.isPublic}
|
value={publicDashboard?.isEnabled}
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setPublicDashboardConfig((state) => {
|
setPublicDashboardConfig({
|
||||||
return { ...state, isPublic: !state.isPublic };
|
...publicDashboard,
|
||||||
|
isEnabled: !publicDashboard.isEnabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
{publicDashboardPersisted(publicDashboard) && publicDashboard.isEnabled && (
|
||||||
|
<Field label="Link URL">
|
||||||
|
<Input
|
||||||
|
value={generatePublicDashboardUrl(publicDashboard)}
|
||||||
|
readOnly
|
||||||
|
addonAfter={
|
||||||
|
<ClipboardButton
|
||||||
|
variant="primary"
|
||||||
|
getText={() => {
|
||||||
|
return generatePublicDashboardUrl(publicDashboard);
|
||||||
|
}}
|
||||||
|
onClipboardCopy={onShareUrlCopy}
|
||||||
|
>
|
||||||
|
<Icon name="copy" /> Copy
|
||||||
|
</ClipboardButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
</FieldSet>
|
||||||
<Button onClick={onSavePublicConfig}>Save Sharing Configuration</Button>
|
<Button onClick={onSavePublicConfig}>Save Sharing Configuration</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { VariableModel } from 'app/features/variables/types';
|
import { VariableModel } from 'app/features/variables/types';
|
||||||
|
|
||||||
import { dashboardHasTemplateVariables } from './SharePublicDashboardUtils';
|
import {
|
||||||
|
PublicDashboard,
|
||||||
|
dashboardHasTemplateVariables,
|
||||||
|
generatePublicDashboardUrl,
|
||||||
|
publicDashboardPersisted,
|
||||||
|
} from './SharePublicDashboardUtils';
|
||||||
|
|
||||||
describe('dashboardHasTemplateVariables', () => {
|
describe('dashboardHasTemplateVariables', () => {
|
||||||
it('false', () => {
|
it('false', () => {
|
||||||
@@ -14,3 +19,24 @@ describe('dashboardHasTemplateVariables', () => {
|
|||||||
expect(dashboardHasTemplateVariables(variables)).toBe(true);
|
expect(dashboardHasTemplateVariables(variables)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('generatePublicDashboardUrl', () => {
|
||||||
|
it('has the right uid', () => {
|
||||||
|
let pubdash = { accessToken: 'abcd1234' } as PublicDashboard;
|
||||||
|
expect(generatePublicDashboardUrl(pubdash)).toEqual(`${window.location.origin}/public-dashboards/abcd1234`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('publicDashboardPersisted', () => {
|
||||||
|
it('true', () => {
|
||||||
|
let pubdash = { uid: 'abcd1234' } as PublicDashboard;
|
||||||
|
expect(publicDashboardPersisted(pubdash)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('false', () => {
|
||||||
|
let pubdash = { uid: '' } as PublicDashboard;
|
||||||
|
expect(publicDashboardPersisted(pubdash)).toBe(false);
|
||||||
|
pubdash = {} as PublicDashboard;
|
||||||
|
expect(publicDashboardPersisted(pubdash)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,39 +5,53 @@ import { VariableModel } from 'app/features/variables/types';
|
|||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
import { DashboardDataDTO, DashboardMeta } from 'app/types/dashboard';
|
import { DashboardDataDTO, DashboardMeta } from 'app/types/dashboard';
|
||||||
|
|
||||||
export interface PublicDashboardConfig {
|
export interface PublicDashboard {
|
||||||
isPublic: boolean;
|
accessToken?: string;
|
||||||
publicDashboard: {
|
isEnabled: boolean;
|
||||||
uid: string;
|
uid: string;
|
||||||
dashboardUid: string;
|
dashboardUid: string;
|
||||||
timeSettings?: object;
|
timeSettings?: object;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardResponse {
|
export interface DashboardResponse {
|
||||||
dashboard: DashboardDataDTO;
|
dashboard: DashboardDataDTO;
|
||||||
meta: DashboardMeta;
|
meta: DashboardMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dashboardHasTemplateVariables = (variables: VariableModel[]): boolean => {
|
|
||||||
return variables.length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPublicDashboardConfig = async (
|
export const getPublicDashboardConfig = async (
|
||||||
dashboardUid: string,
|
dashboardUid: string,
|
||||||
setPublicDashboardConfig: React.Dispatch<React.SetStateAction<PublicDashboardConfig>>
|
setPublicDashboard: React.Dispatch<React.SetStateAction<PublicDashboard>>
|
||||||
) => {
|
) => {
|
||||||
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
||||||
const pdResp: PublicDashboardConfig = await getBackendSrv().get(url);
|
const pdResp: PublicDashboard = await getBackendSrv().get(url);
|
||||||
setPublicDashboardConfig(pdResp);
|
setPublicDashboard(pdResp);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const savePublicDashboardConfig = async (
|
export const savePublicDashboardConfig = async (
|
||||||
dashboardUid: string,
|
dashboardUid: string,
|
||||||
publicDashboardConfig: PublicDashboardConfig,
|
publicDashboardConfig: PublicDashboard,
|
||||||
setPublicDashboardConfig: Function
|
setPublicDashboard: React.Dispatch<React.SetStateAction<PublicDashboard>>
|
||||||
) => {
|
) => {
|
||||||
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
||||||
const pdResp: PublicDashboardConfig = await getBackendSrv().post(url, publicDashboardConfig);
|
const pdResp: PublicDashboard = await getBackendSrv().post(url, publicDashboardConfig);
|
||||||
|
|
||||||
|
// Never allow a user to send the orgId
|
||||||
|
// @ts-ignore
|
||||||
|
delete pdResp.orgId;
|
||||||
|
|
||||||
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
||||||
setPublicDashboardConfig(pdResp);
|
setPublicDashboard(pdResp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instance methods
|
||||||
|
export const dashboardHasTemplateVariables = (variables: VariableModel[]): boolean => {
|
||||||
|
return variables.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const publicDashboardPersisted = (publicDashboard: PublicDashboard): boolean => {
|
||||||
|
return publicDashboard.uid !== '' && publicDashboard.uid !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generatePublicDashboardUrl = (publicDashboard: PublicDashboard): string => {
|
||||||
|
return `${window.location.origin}/public-dashboards/${publicDashboard.accessToken}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface DashboardPageRouteParams {
|
|||||||
uid?: string;
|
uid?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
|
accessToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DashboardPageRouteSearchParams = {
|
export type DashboardPageRouteSearchParams = {
|
||||||
@@ -130,6 +131,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
urlFolderId: queryParams.folderId,
|
urlFolderId: queryParams.folderId,
|
||||||
routeName: this.props.route.routeName,
|
routeName: this.props.route.routeName,
|
||||||
fixUrl: !isPublic,
|
fixUrl: !isPublic,
|
||||||
|
accessToken: match.params.accessToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
// small delay to start live updates
|
// small delay to start live updates
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
dashboard.getTimezone(),
|
dashboard.getTimezone(),
|
||||||
timeData,
|
timeData,
|
||||||
width,
|
width,
|
||||||
dashboard.meta.publicDashboardUid
|
dashboard.meta.publicDashboardAccessToken
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// The panel should render on refresh as well if it doesn't have a query, like clock panel
|
// The panel should render on refresh as well if it doesn't have a query, like clock panel
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ let panelModel = new PanelModel({
|
|||||||
let panelData = createEmptyQueryResponse();
|
let panelData = createEmptyQueryResponse();
|
||||||
|
|
||||||
describe('Panel Header', () => {
|
describe('Panel Header', () => {
|
||||||
const dashboardModel = new DashboardModel({}, { isPublic: true });
|
const dashboardModel = new DashboardModel({}, { publicDashboardAccessToken: 'abc123' });
|
||||||
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');
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ 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 = new DashboardModel({}, { isPublic: false });
|
const dashboardModel = new DashboardModel({}, { publicDashboardAccessToken: '' });
|
||||||
window.history.pushState({}, 'Test Title', '/d/abc/123');
|
window.history.pushState({}, 'Test Title', '/d/abc/123');
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<h2 className={styles.titleText}>{title}</h2>
|
<h2 className={styles.titleText}>{title}</h2>
|
||||||
{!dashboard.meta.isPublic && (
|
{!dashboard.meta.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" />
|
||||||
<PanelHeaderMenuWrapper
|
<PanelHeaderMenuWrapper
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const getPublicDashboardRoutes = (): RouteDescriptor[] => {
|
|||||||
if (config.featureToggles.publicDashboards) {
|
if (config.featureToggles.publicDashboards) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
path: '/public-dashboards/:uid',
|
path: '/public-dashboards/:accessToken',
|
||||||
pageClass: 'page-dashboard',
|
pageClass: 'page-dashboard',
|
||||||
routeName: DashboardRoutes.Public,
|
routeName: DashboardRoutes.Public,
|
||||||
component: SafeDynamicImport(
|
component: SafeDynamicImport(
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ export class PublicDashboardDataSource extends DataSourceApi<any> {
|
|||||||
* Ideally final -- any other implementation may not work as expected
|
* Ideally final -- any other implementation may not work as expected
|
||||||
*/
|
*/
|
||||||
query(request: DataQueryRequest<any>): Observable<DataQueryResponse> {
|
query(request: DataQueryRequest<any>): Observable<DataQueryResponse> {
|
||||||
const { intervalMs, maxDataPoints, range, requestId, publicDashboardUid, panelId } = request;
|
const { intervalMs, maxDataPoints, range, requestId, publicDashboardAccessToken, panelId } = request;
|
||||||
let targets = request.targets;
|
let targets = request.targets;
|
||||||
|
|
||||||
const queries = targets.map((q) => {
|
const queries = targets.map((q) => {
|
||||||
return {
|
return {
|
||||||
...q,
|
...q,
|
||||||
publicDashboardUid,
|
publicDashboardAccessToken,
|
||||||
intervalMs,
|
intervalMs,
|
||||||
maxDataPoints,
|
maxDataPoints,
|
||||||
};
|
};
|
||||||
@@ -37,7 +37,7 @@ export class PublicDashboardDataSource extends DataSourceApi<any> {
|
|||||||
return of({ data: [] });
|
return of({ data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const body: any = { queries, publicDashboardUid, panelId };
|
const body: any = { queries, publicDashboardAccessToken, panelId };
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
body.range = range;
|
body.range = range;
|
||||||
@@ -47,7 +47,7 @@ export class PublicDashboardDataSource extends DataSourceApi<any> {
|
|||||||
|
|
||||||
return getBackendSrv()
|
return getBackendSrv()
|
||||||
.fetch<BackendDataSourceResponse>({
|
.fetch<BackendDataSourceResponse>({
|
||||||
url: `/api/public/dashboards/${publicDashboardUid}/panels/${panelId}/query`,
|
url: `/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: body,
|
data: body,
|
||||||
requestId,
|
requestId,
|
||||||
|
|||||||
@@ -319,14 +319,14 @@ export class PanelModel implements DataConfigSource, IPanelModel {
|
|||||||
dashboardTimezone: string,
|
dashboardTimezone: string,
|
||||||
timeData: TimeOverrideResult,
|
timeData: TimeOverrideResult,
|
||||||
width: number,
|
width: number,
|
||||||
publicDashboardUid?: string
|
publicDashboardAccessToken?: string
|
||||||
) {
|
) {
|
||||||
this.getQueryRunner().run({
|
this.getQueryRunner().run({
|
||||||
datasource: this.datasource,
|
datasource: this.datasource,
|
||||||
queries: this.targets,
|
queries: this.targets,
|
||||||
panelId: this.id,
|
panelId: this.id,
|
||||||
dashboardId: dashboardId,
|
dashboardId: dashboardId,
|
||||||
publicDashboardUid,
|
publicDashboardAccessToken,
|
||||||
timezone: dashboardTimezone,
|
timezone: dashboardTimezone,
|
||||||
timeRange: timeData.timeRange,
|
timeRange: timeData.timeRange,
|
||||||
timeInfo: timeData.timeInfo,
|
timeInfo: timeData.timeInfo,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface InitDashboardArgs {
|
|||||||
urlSlug?: string;
|
urlSlug?: string;
|
||||||
urlType?: string;
|
urlType?: string;
|
||||||
urlFolderId?: string | null;
|
urlFolderId?: string | null;
|
||||||
|
accessToken?: string;
|
||||||
routeName?: string;
|
routeName?: string;
|
||||||
fixUrl: boolean;
|
fixUrl: boolean;
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ async function fetchDashboard(
|
|||||||
return dashDTO;
|
return dashDTO;
|
||||||
}
|
}
|
||||||
case DashboardRoutes.Public: {
|
case DashboardRoutes.Public: {
|
||||||
return await dashboardLoaderSrv.loadDashboard('public', args.urlSlug, args.urlUid);
|
return await dashboardLoaderSrv.loadDashboard('public', args.urlSlug, args.accessToken);
|
||||||
}
|
}
|
||||||
case DashboardRoutes.Normal: {
|
case DashboardRoutes.Normal: {
|
||||||
const dashDTO: DashboardDTO = await dashboardLoaderSrv.loadDashboard(args.urlType, args.urlSlug, args.urlUid);
|
const dashDTO: DashboardDTO = await dashboardLoaderSrv.loadDashboard(args.urlType, args.urlSlug, args.urlUid);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export interface QueryRunnerOptions<
|
|||||||
queries: TQuery[];
|
queries: TQuery[];
|
||||||
panelId?: number;
|
panelId?: number;
|
||||||
dashboardId?: number;
|
dashboardId?: number;
|
||||||
publicDashboardUid?: 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
|
||||||
@@ -203,7 +203,7 @@ export class PanelQueryRunner {
|
|||||||
datasource,
|
datasource,
|
||||||
panelId,
|
panelId,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
publicDashboardUid,
|
publicDashboardAccessToken,
|
||||||
timeRange,
|
timeRange,
|
||||||
timeInfo,
|
timeInfo,
|
||||||
cacheTimeout,
|
cacheTimeout,
|
||||||
@@ -223,7 +223,7 @@ export class PanelQueryRunner {
|
|||||||
timezone,
|
timezone,
|
||||||
panelId,
|
panelId,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
publicDashboardUid,
|
publicDashboardAccessToken,
|
||||||
range: timeRange,
|
range: timeRange,
|
||||||
timeInfo,
|
timeInfo,
|
||||||
interval: '',
|
interval: '',
|
||||||
@@ -239,7 +239,7 @@ export class PanelQueryRunner {
|
|||||||
(request as any).rangeRaw = timeRange.raw;
|
(request as any).rangeRaw = timeRange.raw;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ds = await getDataSource(datasource, request.scopedVars, publicDashboardUid);
|
const ds = await getDataSource(datasource, request.scopedVars, publicDashboardAccessToken);
|
||||||
const isMixedDS = ds.meta?.mixed;
|
const isMixedDS = ds.meta?.mixed;
|
||||||
|
|
||||||
// Attach the data source to each query
|
// Attach the data source to each query
|
||||||
@@ -359,9 +359,9 @@ export class PanelQueryRunner {
|
|||||||
async function getDataSource(
|
async function getDataSource(
|
||||||
datasource: DataSourceRef | string | DataSourceApi | null,
|
datasource: DataSourceRef | string | DataSourceApi | null,
|
||||||
scopedVars: ScopedVars,
|
scopedVars: ScopedVars,
|
||||||
publicDashboardUid?: string
|
publicDashboardAccessToken?: string
|
||||||
): Promise<DataSourceApi> {
|
): Promise<DataSourceApi> {
|
||||||
if (publicDashboardUid) {
|
if (publicDashboardAccessToken) {
|
||||||
return new PublicDashboardDataSource();
|
return new PublicDashboardDataSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ export interface DashboardMeta {
|
|||||||
fromFile?: boolean;
|
fromFile?: boolean;
|
||||||
hasUnsavedFolderChange?: boolean;
|
hasUnsavedFolderChange?: boolean;
|
||||||
annotationsPermissions?: AnnotationsPermissions;
|
annotationsPermissions?: AnnotationsPermissions;
|
||||||
isPublic?: boolean;
|
publicDashboardAccessToken?: string;
|
||||||
publicDashboardUid?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnnotationActions {
|
export interface AnnotationActions {
|
||||||
|
|||||||
Reference in New Issue
Block a user