public dashboards: rename api functions (#57789)

This PR imposes better naming conventions on public dashboards api

* rename api functions and remove use of _config_ noun

* fix tests

Co-authored-by: Ezequiel Victorero <ezequiel.victorero@grafana.com>
This commit is contained in:
Jeff Levin 2022-10-27 17:08:11 -08:00 committed by GitHub
parent 6b7d6fe0cb
commit bf672f960a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 655 additions and 633 deletions

View File

@ -245,7 +245,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
setup()
uid := util.GenerateShortUID()
cmd := publicDashboardModels.SavePublicDashboardConfigCommand{
cmd := publicDashboardModels.SavePublicDashboardCommand{
PublicDashboard: publicDashboardModels.PublicDashboard{
Uid: uid,
DashboardUid: savedDash.Uid,
@ -280,7 +280,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
setup()
uid := util.GenerateShortUID()
cmd := publicDashboardModels.SavePublicDashboardConfigCommand{
cmd := publicDashboardModels.SavePublicDashboardCommand{
PublicDashboard: publicDashboardModels.PublicDashboard{
Uid: uid,
DashboardUid: savedDash.Uid,

View File

@ -4,10 +4,8 @@ import (
"context"
"errors"
"net/http"
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
@ -17,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@ -60,7 +57,7 @@ func (api *Api) RegisterAPIEndpoints() {
// because it is deeply dependent on the HTTPServer.Index() method and would result in a
// circular dependency
api.RouteRegister.Get("/api/public/dashboards/:accessToken", routing.Wrap(api.GetPublicDashboard))
api.RouteRegister.Get("/api/public/dashboards/:accessToken", routing.Wrap(api.ViewPublicDashboard))
api.RouteRegister.Post("/api/public/dashboards/:accessToken/panels/:panelId/query", routing.Wrap(api.QueryPublicDashboard))
api.RouteRegister.Get("/api/public/dashboards/:accessToken/annotations", routing.Wrap(api.GetAnnotations))
@ -74,51 +71,12 @@ func (api *Api) RegisterAPIEndpoints() {
// Get public dashboard
api.RouteRegister.Get("/api/dashboards/uid/:dashboardUid/public-dashboards",
auth(middleware.ReqSignedIn, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, uidScope)),
routing.Wrap(api.GetPublicDashboardConfig))
routing.Wrap(api.GetPublicDashboard))
// Create/Update Public Dashboard
api.RouteRegister.Post("/api/dashboards/uid/:dashboardUid/public-dashboards",
auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)),
routing.Wrap(api.SavePublicDashboardConfig))
}
// GetPublicDashboard Gets public dashboard
// GET /api/public/dashboards/:accessToken
func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
pubdash, dash, err := api.PublicDashboardService.FindPublicDashboardAndDashboardByAccessToken(
c.Req.Context(),
accessToken,
)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "GetPublicDashboard: failed to get public dashboard", err)
}
meta := dtos.DashboardMeta{
Slug: dash.Slug,
Type: models.DashTypeDB,
CanStar: false,
CanSave: false,
CanEdit: false,
CanAdmin: false,
CanDelete: false,
Created: dash.Created,
Updated: dash.Updated,
Version: dash.Version,
IsFolder: false,
FolderId: dash.FolderId,
PublicDashboardAccessToken: pubdash.AccessToken,
PublicDashboardUID: pubdash.Uid,
}
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
return response.JSON(http.StatusOK, dto)
routing.Wrap(api.SavePublicDashboard))
}
// ListPublicDashboards Gets list of public dashboards for an org
@ -131,9 +89,9 @@ func (api *Api) ListPublicDashboards(c *models.ReqContext) response.Response {
return response.JSON(http.StatusOK, resp)
}
// GetPublicDashboardConfig Gets public dashboard configuration for dashboard
// GetPublicDashboard Gets public dashboard configuration for dashboard
// GET /api/dashboards/uid/:uid/public-config
func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response {
func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
pdc, err := api.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.OrgID, web.Params(c.Req)[":dashboardUid"])
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "GetPublicDashboardConfig: failed to get public dashboard config", err)
@ -141,9 +99,9 @@ func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response
return response.JSON(http.StatusOK, pdc)
}
// SavePublicDashboardConfig Sets public dashboard configuration for dashboard
// SavePublicDashboard Sets public dashboard configuration for dashboard
// POST /api/dashboards/uid/:uid/public-config
func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Response {
func (api *Api) SavePublicDashboard(c *models.ReqContext) response.Response {
// exit if we don't have a valid dashboardUid
dashboardUid := web.Params(c.Req)[":dashboardUid"]
if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) {
@ -157,7 +115,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
// Always set the orgID and userID from the session
pubdash.OrgId = c.OrgID
dto := SavePublicDashboardConfigDTO{
dto := SavePublicDashboardDTO{
UserId: c.UserID,
OrgId: c.OrgID,
DashboardUid: dashboardUid,
@ -173,54 +131,6 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
return response.JSON(http.StatusOK, pubdash)
}
// QueryPublicDashboard returns all results for a given panel on a public dashboard
// POST /api/public/dashboard/:accessToken/panels/:panelId/query
func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
panelId, err := strconv.ParseInt(web.Params(c.Req)[":panelId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "QueryPublicDashboard: invalid panel ID", err)
}
reqDTO := PublicDashboardQueryDTO{}
if err = web.Bind(c.Req, &reqDTO); err != nil {
return response.Error(http.StatusBadRequest, "QueryPublicDashboard: bad request data", err)
}
resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, accessToken)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "QueryPublicDashboard: error running public dashboard panel queries", err)
}
return toJsonStreamingResponse(api.Features, resp)
}
// GetAnnotations returns annotations for a public dashboard
// GET /api/public/dashboards/:accessToken/annotations
func (api *Api) GetAnnotations(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
reqDTO := AnnotationsQueryDTO{
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
}
annotations, err := api.PublicDashboardService.FindAnnotations(c.Req.Context(), reqDTO, accessToken)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "error getting public dashboard annotations", err)
}
return response.JSON(http.StatusOK, annotations)
}
// util to help us unpack dashboard and publicdashboard errors or use default http code and message
// we should look to do some future refactoring of these errors as publicdashboard err is the same as a dashboarderr, just defined in a
// different package.

View File

@ -1,43 +1,23 @@
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/datasources"
datasourcesService "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/publicdashboards"
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
var userAdmin = &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleAdmin, Login: "testAdminUser"}
@ -50,76 +30,6 @@ type JsonErrResponse struct {
Error string `json:"error"`
}
func TestAPIGetAnnotations(t *testing.T) {
testCases := []struct {
Name string
ExpectedHttpResponse int
Annotations []AnnotationEvent
ServiceError error
AccessToken string
From string
To string
ExpectedServiceCalled bool
}{
{
Name: "will return success when there is no error and to and from are provided",
ExpectedHttpResponse: http.StatusOK,
Annotations: []AnnotationEvent{{Id: 1}},
ServiceError: nil,
AccessToken: validAccessToken,
From: "123",
To: "123",
ExpectedServiceCalled: true,
},
{
Name: "will return 500 when service returns an error",
ExpectedHttpResponse: http.StatusInternalServerError,
Annotations: nil,
ServiceError: errors.New("an error happened"),
AccessToken: validAccessToken,
From: "123",
To: "123",
ExpectedServiceCalled: true,
},
{
Name: "will return 400 when has an incorrect Access Token",
ExpectedHttpResponse: http.StatusBadRequest,
Annotations: nil,
ServiceError: errors.New("an error happened"),
AccessToken: "TooShortAccessToken",
From: "123",
To: "123",
ExpectedServiceCalled: false,
},
}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.RBACEnabled = false
service := publicdashboards.NewFakePublicDashboardService(t)
if test.ExpectedServiceCalled {
service.On("FindAnnotations", mock.Anything, mock.Anything, mock.AnythingOfType("string")).
Return(test.Annotations, test.ServiceError).Once()
}
testServer := setupTestServer(t, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), service, nil, anonymousUser)
path := fmt.Sprintf("/api/public/dashboards/%s/annotations?from=%s&to=%s", test.AccessToken, test.From, test.To)
response := callAPI(testServer, http.MethodGet, path, nil, t)
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
if test.ExpectedHttpResponse == http.StatusOK {
var items []AnnotationEvent
err := json.Unmarshal(response.Body.Bytes(), &items)
assert.NoError(t, err)
assert.Equal(t, items, test.Annotations)
}
})
}
}
func TestAPIFeatureFlag(t *testing.T) {
testCases := []struct {
Name string
@ -142,7 +52,7 @@ func TestAPIFeatureFlag(t *testing.T) {
Path: "/api/dashboards/public-dashboards",
},
{
Name: "API: Get Public Dashboard Config",
Name: "API: Get Public Dashboard",
Method: http.MethodPost,
Path: "/api/dashboards/uid/abc123/public-dashboards",
},
@ -239,95 +149,6 @@ func TestAPIListPublicDashboard(t *testing.T) {
}
func TestAPIGetPublicDashboard(t *testing.T) {
DashboardUid := "dashboard-abcd1234"
testCases := []struct {
Name string
AccessToken string
ExpectedHttpResponse int
DashboardResult *models.Dashboard
Err error
FixedErrorResponse string
}{
{
Name: "It gets a public dashboard",
AccessToken: validAccessToken,
ExpectedHttpResponse: http.StatusOK,
DashboardResult: &models.Dashboard{
Data: simplejson.NewFromAny(map[string]interface{}{
"Uid": DashboardUid,
}),
},
Err: nil,
FixedErrorResponse: "",
},
{
Name: "It should return 404 if no public dashboard",
AccessToken: validAccessToken,
ExpectedHttpResponse: http.StatusNotFound,
DashboardResult: nil,
Err: ErrPublicDashboardNotFound,
FixedErrorResponse: "",
},
{
Name: "It should return 400 if it is an invalid access token",
AccessToken: "SomeInvalidAccessToken",
ExpectedHttpResponse: http.StatusBadRequest,
DashboardResult: nil,
Err: nil,
FixedErrorResponse: "{\"message\":\"Invalid Access Token\"}",
},
}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
service := publicdashboards.NewFakePublicDashboardService(t)
service.On("FindPublicDashboardAndDashboardByAccessToken", mock.Anything, mock.AnythingOfType("string")).
Return(&PublicDashboard{}, test.DashboardResult, test.Err).Maybe()
cfg := setting.NewCfg()
cfg.RBACEnabled = false
testServer := setupTestServer(
t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
service,
nil,
anonymousUser,
)
response := callAPI(testServer, http.MethodGet,
fmt.Sprintf("/api/public/dashboards/%s", test.AccessToken),
nil,
t,
)
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
if test.Err == nil && test.FixedErrorResponse == "" {
var dashResp dtos.DashboardFullWithMeta
err := json.Unmarshal(response.Body.Bytes(), &dashResp)
require.NoError(t, err)
assert.Equal(t, DashboardUid, dashResp.Dashboard.Get("Uid").MustString())
assert.Equal(t, false, dashResp.Meta.CanEdit)
assert.Equal(t, false, dashResp.Meta.CanDelete)
assert.Equal(t, false, dashResp.Meta.CanSave)
} else if test.FixedErrorResponse != "" {
require.Equal(t, test.ExpectedHttpResponse, response.Code)
require.JSONEq(t, "{\"message\":\"Invalid Access Token\"}", response.Body.String())
} else {
var errResp JsonErrResponse
err := json.Unmarshal(response.Body.Bytes(), &errResp)
require.NoError(t, err)
assert.Equal(t, test.Err.Error(), errResp.Error)
}
})
}
}
func TestAPIGetPublicDashboardConfig(t *testing.T) {
pubdash := &PublicDashboard{IsEnabled: true}
testCases := []struct {
@ -341,7 +162,7 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
ShouldCallService bool
}{
{
Name: "retrieves public dashboard config when dashboard is found",
Name: "retrieves public dashboard when dashboard is found",
DashboardUid: "1",
ExpectedHttpResponse: http.StatusOK,
PublicDashboardResult: pubdash,
@ -371,7 +192,7 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
ShouldCallService: true,
},
{
Name: "retrieves public dashboard config when dashboard is found RBAC on",
Name: "retrieves public dashboard when dashboard is found RBAC on",
DashboardUid: "1",
ExpectedHttpResponse: http.StatusOK,
PublicDashboardResult: pubdash,
@ -432,72 +253,72 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
}
}
func TestApiSavePublicDashboardConfig(t *testing.T) {
func TestApiSavePublicDashboard(t *testing.T) {
testCases := []struct {
Name string
DashboardUid string
publicDashboardConfig *PublicDashboard
ExpectedHttpResponse int
SaveDashboardErr error
User *user.SignedInUser
AccessControlEnabled bool
ShouldCallService bool
Name string
DashboardUid string
publicDashboard *PublicDashboard
ExpectedHttpResponse int
SaveDashboardErr error
User *user.SignedInUser
AccessControlEnabled bool
ShouldCallService bool
}{
{
Name: "returns 200 when update persists",
DashboardUid: "1",
publicDashboardConfig: &PublicDashboard{IsEnabled: true},
ExpectedHttpResponse: http.StatusOK,
SaveDashboardErr: nil,
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
Name: "returns 200 when update persists",
DashboardUid: "1",
publicDashboard: &PublicDashboard{IsEnabled: true},
ExpectedHttpResponse: http.StatusOK,
SaveDashboardErr: nil,
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
},
{
Name: "returns 500 when not persisted",
ExpectedHttpResponse: http.StatusInternalServerError,
publicDashboardConfig: &PublicDashboard{},
SaveDashboardErr: errors.New("backend failed to save"),
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
Name: "returns 500 when not persisted",
ExpectedHttpResponse: http.StatusInternalServerError,
publicDashboard: &PublicDashboard{},
SaveDashboardErr: errors.New("backend failed to save"),
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
},
{
Name: "returns 404 when dashboard not found",
ExpectedHttpResponse: http.StatusNotFound,
publicDashboardConfig: &PublicDashboard{},
SaveDashboardErr: dashboards.ErrDashboardNotFound,
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
Name: "returns 404 when dashboard not found",
ExpectedHttpResponse: http.StatusNotFound,
publicDashboard: &PublicDashboard{},
SaveDashboardErr: dashboards.ErrDashboardNotFound,
User: userAdmin,
AccessControlEnabled: false,
ShouldCallService: true,
},
{
Name: "returns 200 when update persists RBAC on",
DashboardUid: "1",
publicDashboardConfig: &PublicDashboard{IsEnabled: true},
ExpectedHttpResponse: http.StatusOK,
SaveDashboardErr: nil,
User: userAdminRBAC,
AccessControlEnabled: true,
ShouldCallService: true,
Name: "returns 200 when update persists RBAC on",
DashboardUid: "1",
publicDashboard: &PublicDashboard{IsEnabled: true},
ExpectedHttpResponse: http.StatusOK,
SaveDashboardErr: nil,
User: userAdminRBAC,
AccessControlEnabled: true,
ShouldCallService: true,
},
{
Name: "returns 403 when no permissions",
ExpectedHttpResponse: http.StatusForbidden,
publicDashboardConfig: &PublicDashboard{IsEnabled: true},
SaveDashboardErr: nil,
User: userViewer,
AccessControlEnabled: false,
ShouldCallService: false,
Name: "returns 403 when no permissions",
ExpectedHttpResponse: http.StatusForbidden,
publicDashboard: &PublicDashboard{IsEnabled: true},
SaveDashboardErr: nil,
User: userViewer,
AccessControlEnabled: false,
ShouldCallService: false,
},
{
Name: "returns 403 when no permissions RBAC on",
ExpectedHttpResponse: http.StatusForbidden,
publicDashboardConfig: &PublicDashboard{IsEnabled: true},
SaveDashboardErr: nil,
User: userAdmin,
AccessControlEnabled: true,
ShouldCallService: false,
Name: "returns 403 when no permissions RBAC on",
ExpectedHttpResponse: http.StatusForbidden,
publicDashboard: &PublicDashboard{IsEnabled: true},
SaveDashboardErr: nil,
User: userAdmin,
AccessControlEnabled: true,
ShouldCallService: false,
},
}
@ -507,7 +328,7 @@ func TestApiSavePublicDashboardConfig(t *testing.T) {
// this is to avoid AssertExpectations fail at t.Cleanup when the middleware returns before calling the service
if test.ShouldCallService {
service.On("Save", mock.Anything, mock.Anything, mock.AnythingOfType("*models.SavePublicDashboardConfigDTO")).
service.On("Save", mock.Anything, mock.Anything, mock.AnythingOfType("*models.SavePublicDashboardDTO")).
Return(&PublicDashboard{IsEnabled: true}, test.SaveDashboardErr)
}
@ -535,240 +356,10 @@ func TestApiSavePublicDashboardConfig(t *testing.T) {
//check the result if it's a 200
if response.Code == http.StatusOK {
val, err := json.Marshal(test.publicDashboardConfig)
val, err := json.Marshal(test.publicDashboard)
require.NoError(t, err)
assert.Equal(t, string(val), response.Body.String())
}
})
}
}
// `/public/dashboards/:uid/query“ endpoint test
func TestAPIQueryPublicDashboard(t *testing.T) {
mockedResponse := &backend.QueryDataResponse{
Responses: map[string]backend.DataResponse{
"test": {
Frames: data.Frames{
&data.Frame{
Name: "anyDataFrame",
Fields: []*data.Field{
data.NewField("anyGroupName", nil, []*string{
aws.String("group_a"), aws.String("group_b"), aws.String("group_c"),
}),
},
},
},
Error: nil,
},
},
}
expectedResponse := `{
"results": {
"test": {
"frames": [
{
"schema": {
"name": "anyDataFrame",
"fields": [
{
"name": "anyGroupName",
"type": "string",
"typeInfo": {
"frame": "string",
"nullable": true
}
}
]
},
"data": {
"values": [
[
"group_a",
"group_b",
"group_c"
]
]
}
}
]
}
}
}`
setup := func(enabled bool) (*web.Mux, *publicdashboards.FakePublicDashboardService) {
service := publicdashboards.NewFakePublicDashboardService(t)
cfg := setting.NewCfg()
cfg.RBACEnabled = false
testServer := setupTestServer(
t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards, enabled),
service,
nil,
anonymousUser,
)
return testServer, service
}
t.Run("Status code is 400 when the panel ID is invalid", func(t *testing.T) {
server, _ := setup(true)
path := fmt.Sprintf("/api/public/dashboards/%s/panels/notanumber/query", validAccessToken)
resp := callAPI(server, http.MethodPost, path, strings.NewReader("{}"), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Status code is 400 when the access token is invalid", func(t *testing.T) {
server, _ := setup(true)
resp := callAPI(server, http.MethodPost, getValidQueryPath("SomeInvalidAccessToken"), strings.NewReader("{}"), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
require.JSONEq(t, "{\"message\":\"Invalid Access Token\"}", resp.Body.String())
})
t.Run("Status code is 400 when the intervalMS is lesser than 0", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, ErrPublicDashboardBadRequest)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader(`{"intervalMs":-100,"maxDataPoints":1000}`), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Status code is 400 when the maxDataPoints is lesser than 0", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, ErrPublicDashboardBadRequest)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader(`{"intervalMs":100,"maxDataPoints":-1000}`), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Returns query data when feature toggle is enabled", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(mockedResponse, nil)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader("{}"), t)
require.JSONEq(
t,
expectedResponse,
resp.Body.String(),
)
require.Equal(t, http.StatusOK, resp.Code)
})
t.Run("Status code is 500 when the query fails", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, fmt.Errorf("error"))
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader("{}"), t)
require.Equal(t, http.StatusInternalServerError, resp.Code)
})
}
func getValidQueryPath(accessToken string) string {
return fmt.Sprintf("/api/public/dashboards/%s/panels/2/query", accessToken)
}
func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) {
db := db.InitTestDB(t)
cacheService := datasourcesService.ProvideCacheService(localcache.ProvideService(), db)
qds := buildQueryDataService(t, cacheService, nil, db)
dsStore := datasourcesService.CreateStore(db, log.New("publicdashboards.test"))
_ = dsStore.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
Uid: "ds1",
OrgId: 1,
Name: "laban",
Type: datasources.DS_MYSQL,
Access: datasources.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",
},
},
},
},
}),
}
// create dashboard
dashboardStoreService := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg))
dashboard, err := dashboardStoreService.SaveDashboard(context.Background(), saveDashboardCmd)
require.NoError(t, err)
// Create public dashboard
savePubDashboardCmd := &SavePublicDashboardConfigDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
PublicDashboard: &PublicDashboard{
IsEnabled: true,
},
}
annotationsService := annotationstest.NewFakeAnnotationsRepo()
// create public dashboard
store := publicdashboardsStore.ProvideStore(db)
cfg := setting.NewCfg()
ac := acmock.New()
cfg.RBACEnabled = false
service := publicdashboardsService.ProvideService(cfg, store, qds, annotationsService, ac)
pubdash, err := service.Save(context.Background(), &user.SignedInUser{}, savePubDashboardCmd)
require.NoError(t, err)
// setup test server
server := setupTestServer(t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
service,
db,
anonymousUser,
)
resp := callAPI(server, http.MethodPost,
fmt.Sprintf("/api/public/dashboards/%s/panels/1/query", pubdash.AccessToken),
strings.NewReader(`{}`),
t,
)
require.Equal(t, http.StatusOK, resp.Code)
require.NoError(t, err)
require.JSONEq(
t,
`{
"results": {
"A": {
"frames": [
{
"data": {
"values": []
},
"schema": {
"fields": []
}
}
]
}
}
}`,
resp.Body.String(),
)
}

View File

@ -0,0 +1,100 @@
package api
import (
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/web"
)
// ViewPublicDashboard Gets public dashboard
// GET /api/public/dashboards/:accessToken
func (api *Api) ViewPublicDashboard(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
pubdash, dash, err := api.PublicDashboardService.FindPublicDashboardAndDashboardByAccessToken(
c.Req.Context(),
accessToken,
)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "ViewPublicDashboard: failed to get public dashboard", err)
}
meta := dtos.DashboardMeta{
Slug: dash.Slug,
Type: models.DashTypeDB,
CanStar: false,
CanSave: false,
CanEdit: false,
CanAdmin: false,
CanDelete: false,
Created: dash.Created,
Updated: dash.Updated,
Version: dash.Version,
IsFolder: false,
FolderId: dash.FolderId,
PublicDashboardAccessToken: pubdash.AccessToken,
PublicDashboardUID: pubdash.Uid,
}
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
return response.JSON(http.StatusOK, dto)
}
// QueryPublicDashboard returns all results for a given panel on a public dashboard
// POST /api/public/dashboard/:accessToken/panels/:panelId/query
func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
panelId, err := strconv.ParseInt(web.Params(c.Req)[":panelId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "QueryPublicDashboard: invalid panel ID", err)
}
reqDTO := PublicDashboardQueryDTO{}
if err = web.Bind(c.Req, &reqDTO); err != nil {
return response.Error(http.StatusBadRequest, "QueryPublicDashboard: bad request data", err)
}
resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, accessToken)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "QueryPublicDashboard: error running public dashboard panel queries", err)
}
return toJsonStreamingResponse(api.Features, resp)
}
// GetAnnotations returns annotations for a public dashboard
// GET /api/public/dashboards/:accessToken/annotations
func (api *Api) GetAnnotations(c *models.ReqContext) response.Response {
accessToken := web.Params(c.Req)[":accessToken"]
if !tokens.IsValidAccessToken(accessToken) {
return response.Error(http.StatusBadRequest, "Invalid Access Token", nil)
}
reqDTO := AnnotationsQueryDTO{
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
}
annotations, err := api.PublicDashboardService.FindAnnotations(c.Req.Context(), reqDTO, accessToken)
if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "error getting public dashboard annotations", err)
}
return response.JSON(http.StatusOK, annotations)
}

View File

@ -0,0 +1,427 @@
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/datasources"
datasourcesService "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/publicdashboards"
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestAPIViewPublicDashboard(t *testing.T) {
DashboardUid := "dashboard-abcd1234"
testCases := []struct {
Name string
AccessToken string
ExpectedHttpResponse int
DashboardResult *models.Dashboard
Err error
FixedErrorResponse string
}{
{
Name: "It gets a public dashboard",
AccessToken: validAccessToken,
ExpectedHttpResponse: http.StatusOK,
DashboardResult: &models.Dashboard{
Data: simplejson.NewFromAny(map[string]interface{}{
"Uid": DashboardUid,
}),
},
Err: nil,
FixedErrorResponse: "",
},
{
Name: "It should return 404 if no public dashboard",
AccessToken: validAccessToken,
ExpectedHttpResponse: http.StatusNotFound,
DashboardResult: nil,
Err: ErrPublicDashboardNotFound,
FixedErrorResponse: "",
},
{
Name: "It should return 400 if it is an invalid access token",
AccessToken: "SomeInvalidAccessToken",
ExpectedHttpResponse: http.StatusBadRequest,
DashboardResult: nil,
Err: nil,
FixedErrorResponse: "{\"message\":\"Invalid Access Token\"}",
},
}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
service := publicdashboards.NewFakePublicDashboardService(t)
service.On("FindPublicDashboardAndDashboardByAccessToken", mock.Anything, mock.AnythingOfType("string")).
Return(&PublicDashboard{}, test.DashboardResult, test.Err).Maybe()
cfg := setting.NewCfg()
cfg.RBACEnabled = false
testServer := setupTestServer(
t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
service,
nil,
anonymousUser,
)
response := callAPI(testServer, http.MethodGet,
fmt.Sprintf("/api/public/dashboards/%s", test.AccessToken),
nil,
t,
)
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
if test.Err == nil && test.FixedErrorResponse == "" {
var dashResp dtos.DashboardFullWithMeta
err := json.Unmarshal(response.Body.Bytes(), &dashResp)
require.NoError(t, err)
assert.Equal(t, DashboardUid, dashResp.Dashboard.Get("Uid").MustString())
assert.Equal(t, false, dashResp.Meta.CanEdit)
assert.Equal(t, false, dashResp.Meta.CanDelete)
assert.Equal(t, false, dashResp.Meta.CanSave)
} else if test.FixedErrorResponse != "" {
require.Equal(t, test.ExpectedHttpResponse, response.Code)
require.JSONEq(t, "{\"message\":\"Invalid Access Token\"}", response.Body.String())
} else {
var errResp JsonErrResponse
err := json.Unmarshal(response.Body.Bytes(), &errResp)
require.NoError(t, err)
assert.Equal(t, test.Err.Error(), errResp.Error)
}
})
}
}
// `/public/dashboards/:uid/query“ endpoint test
func TestAPIQueryPublicDashboard(t *testing.T) {
mockedResponse := &backend.QueryDataResponse{
Responses: map[string]backend.DataResponse{
"test": {
Frames: data.Frames{
&data.Frame{
Name: "anyDataFrame",
Fields: []*data.Field{
data.NewField("anyGroupName", nil, []*string{
aws.String("group_a"), aws.String("group_b"), aws.String("group_c"),
}),
},
},
},
Error: nil,
},
},
}
expectedResponse := `{
"results": {
"test": {
"frames": [
{
"schema": {
"name": "anyDataFrame",
"fields": [
{
"name": "anyGroupName",
"type": "string",
"typeInfo": {
"frame": "string",
"nullable": true
}
}
]
},
"data": {
"values": [
[
"group_a",
"group_b",
"group_c"
]
]
}
}
]
}
}
}`
setup := func(enabled bool) (*web.Mux, *publicdashboards.FakePublicDashboardService) {
service := publicdashboards.NewFakePublicDashboardService(t)
cfg := setting.NewCfg()
cfg.RBACEnabled = false
testServer := setupTestServer(
t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards, enabled),
service,
nil,
anonymousUser,
)
return testServer, service
}
t.Run("Status code is 400 when the panel ID is invalid", func(t *testing.T) {
server, _ := setup(true)
path := fmt.Sprintf("/api/public/dashboards/%s/panels/notanumber/query", validAccessToken)
resp := callAPI(server, http.MethodPost, path, strings.NewReader("{}"), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Status code is 400 when the access token is invalid", func(t *testing.T) {
server, _ := setup(true)
resp := callAPI(server, http.MethodPost, getValidQueryPath("SomeInvalidAccessToken"), strings.NewReader("{}"), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
require.JSONEq(t, "{\"message\":\"Invalid Access Token\"}", resp.Body.String())
})
t.Run("Status code is 400 when the intervalMS is lesser than 0", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, ErrPublicDashboardBadRequest)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader(`{"intervalMs":-100,"maxDataPoints":1000}`), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Status code is 400 when the maxDataPoints is lesser than 0", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, ErrPublicDashboardBadRequest)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader(`{"intervalMs":100,"maxDataPoints":-1000}`), t)
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Returns query data when feature toggle is enabled", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(mockedResponse, nil)
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader("{}"), t)
require.JSONEq(
t,
expectedResponse,
resp.Body.String(),
)
require.Equal(t, http.StatusOK, resp.Code)
})
t.Run("Status code is 500 when the query fails", func(t *testing.T) {
server, fakeDashboardService := setup(true)
fakeDashboardService.On("GetQueryDataResponse", mock.Anything, true, mock.Anything, int64(2), validAccessToken).Return(&backend.QueryDataResponse{}, fmt.Errorf("error"))
resp := callAPI(server, http.MethodPost, getValidQueryPath(validAccessToken), strings.NewReader("{}"), t)
require.Equal(t, http.StatusInternalServerError, resp.Code)
})
}
func getValidQueryPath(accessToken string) string {
return fmt.Sprintf("/api/public/dashboards/%s/panels/2/query", accessToken)
}
func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) {
db := db.InitTestDB(t)
cacheService := datasourcesService.ProvideCacheService(localcache.ProvideService(), db)
qds := buildQueryDataService(t, cacheService, nil, db)
dsStore := datasourcesService.CreateStore(db, log.New("publicdashboards.test"))
_ = dsStore.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
Uid: "ds1",
OrgId: 1,
Name: "laban",
Type: datasources.DS_MYSQL,
Access: datasources.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",
},
},
},
},
}),
}
// create dashboard
dashboardStoreService := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg))
dashboard, err := dashboardStoreService.SaveDashboard(context.Background(), saveDashboardCmd)
require.NoError(t, err)
// Create public dashboard
savePubDashboardCmd := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
PublicDashboard: &PublicDashboard{
IsEnabled: true,
},
}
annotationsService := annotationstest.NewFakeAnnotationsRepo()
// create public dashboard
store := publicdashboardsStore.ProvideStore(db)
cfg := setting.NewCfg()
ac := acmock.New()
cfg.RBACEnabled = false
service := publicdashboardsService.ProvideService(cfg, store, qds, annotationsService, ac)
pubdash, err := service.Save(context.Background(), &user.SignedInUser{}, savePubDashboardCmd)
require.NoError(t, err)
// setup test server
server := setupTestServer(t,
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
service,
db,
anonymousUser,
)
resp := callAPI(server, http.MethodPost,
fmt.Sprintf("/api/public/dashboards/%s/panels/1/query", pubdash.AccessToken),
strings.NewReader(`{}`),
t,
)
require.Equal(t, http.StatusOK, resp.Code)
require.NoError(t, err)
require.JSONEq(
t,
`{
"results": {
"A": {
"frames": [
{
"data": {
"values": []
},
"schema": {
"fields": []
}
}
]
}
}
}`,
resp.Body.String(),
)
}
func TestAPIGetAnnotations(t *testing.T) {
testCases := []struct {
Name string
ExpectedHttpResponse int
Annotations []AnnotationEvent
ServiceError error
AccessToken string
From string
To string
ExpectedServiceCalled bool
}{
{
Name: "will return success when there is no error and to and from are provided",
ExpectedHttpResponse: http.StatusOK,
Annotations: []AnnotationEvent{{Id: 1}},
ServiceError: nil,
AccessToken: validAccessToken,
From: "123",
To: "123",
ExpectedServiceCalled: true,
},
{
Name: "will return 500 when service returns an error",
ExpectedHttpResponse: http.StatusInternalServerError,
Annotations: nil,
ServiceError: errors.New("an error happened"),
AccessToken: validAccessToken,
From: "123",
To: "123",
ExpectedServiceCalled: true,
},
{
Name: "will return 400 when has an incorrect Access Token",
ExpectedHttpResponse: http.StatusBadRequest,
Annotations: nil,
ServiceError: errors.New("an error happened"),
AccessToken: "TooShortAccessToken",
From: "123",
To: "123",
ExpectedServiceCalled: false,
},
}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.RBACEnabled = false
service := publicdashboards.NewFakePublicDashboardService(t)
if test.ExpectedServiceCalled {
service.On("FindAnnotations", mock.Anything, mock.Anything, mock.AnythingOfType("string")).
Return(test.Annotations, test.ServiceError).Once()
}
testServer := setupTestServer(t, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), service, nil, anonymousUser)
path := fmt.Sprintf("/api/public/dashboards/%s/annotations?from=%s&to=%s", test.AccessToken, test.From, test.To)
response := callAPI(testServer, http.MethodGet, path, nil, t)
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
if test.ExpectedHttpResponse == http.StatusOK {
var items []AnnotationEvent
err := json.Unmarshal(response.Body.Bytes(), &items)
assert.NoError(t, err)
assert.Equal(t, items, test.Annotations)
}
})
}
}

View File

@ -71,7 +71,7 @@ func (d *PublicDashboardStoreImpl) FindDashboard(ctx context.Context, dashboardU
return dashboard, err
}
// Find Returns public dashboard configuration by Uid or nil if not found
// Find Returns public dashboard by Uid or nil if not found
func (d *PublicDashboardStoreImpl) Find(ctx context.Context, uid string) (*PublicDashboard, error) {
if uid == "" {
return nil, nil
@ -121,7 +121,7 @@ func (d *PublicDashboardStoreImpl) FindByAccessToken(ctx context.Context, access
return pdRes, err
}
// FindByDashboardUid Retrieves public dashboard configuration by dashboard uid
// FindByDashboardUid Retrieves public dashboard by dashboard uid
func (d *PublicDashboardStoreImpl) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) {
if dashboardUid == "" {
return nil, dashboards.ErrDashboardIdentifierNotSet
@ -148,8 +148,8 @@ func (d *PublicDashboardStoreImpl) FindByDashboardUid(ctx context.Context, orgId
return pdRes, err
}
// Save Persists public dashboard configuration
func (d *PublicDashboardStoreImpl) Save(ctx context.Context, cmd SavePublicDashboardConfigCommand) error {
// Save Persists public dashboard
func (d *PublicDashboardStoreImpl) Save(ctx context.Context, cmd SavePublicDashboardCommand) error {
if cmd.PublicDashboard.DashboardUid == "" {
return dashboards.ErrDashboardIdentifierNotSet
}
@ -166,8 +166,8 @@ func (d *PublicDashboardStoreImpl) Save(ctx context.Context, cmd SavePublicDashb
return err
}
// Update updates existing public dashboard configuration
func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDashboardConfigCommand) error {
// Update updates existing public dashboard
func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDashboardCommand) error {
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
timeSettingsJSON, err := json.Marshal(cmd.PublicDashboard.TimeSettings)
if err != nil {

View File

@ -104,7 +104,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) {
t.Run("ExistsEnabledByAccessToken will return true when at least one public dashboard has a matching access token", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "abc123",
@ -126,7 +126,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) {
t.Run("ExistsEnabledByAccessToken will return false when IsEnabled=false", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: false,
Uid: "abc123",
@ -172,7 +172,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) {
t.Run("ExistsEnabledByDashboardUid Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "abc123",
@ -194,7 +194,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) {
t.Run("ExistsEnabledByDashboardUid will return false when dashboard has public dashboards but they are not enabled", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: false,
Uid: "abc123",
@ -244,7 +244,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
t.Run("returns along with public dashboard when exists", func(t *testing.T) {
setup()
cmd := SavePublicDashboardConfigCommand{
cmd := SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "pubdash-uid",
@ -295,7 +295,7 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
t.Run("saves new public dashboard", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
AnnotationsEnabled: true,
@ -324,7 +324,7 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
t.Run("guards from saving without dashboardUid", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "pubdash-uid",
@ -360,7 +360,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
setup()
pdUid := "asdf1234"
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
Uid: pdUid,
DashboardUid: savedDashboard.Uid,
@ -376,7 +376,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
// inserting two different public dashboards to test update works and only affect the desired pd by uid
anotherPdUid := "anotherUid"
err = publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err = publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
Uid: anotherPdUid,
DashboardUid: anotherSavedDashboard.Uid,
@ -401,7 +401,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
UpdatedBy: 8,
}
// update initial record
err = publicdashboardStore.Update(context.Background(), SavePublicDashboardConfigCommand{
err = publicdashboardStore.Update(context.Background(), SavePublicDashboardCommand{
PublicDashboard: updatedPublicDashboard,
})
require.NoError(t, err)
@ -441,7 +441,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
t.Run("GetOrgIdByAccessToken will OrgId when enabled", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "abc123",
@ -463,7 +463,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
t.Run("GetOrgIdByAccessToken will return 0 when IsEnabled=false", func(t *testing.T) {
setup()
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardConfigCommand{
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
IsEnabled: false,
Uid: "abc123",
@ -520,7 +520,7 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt
accessToken, err := tokens.GenerateAccessToken()
require.NoError(t, err)
cmd := SavePublicDashboardConfigCommand{
cmd := SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
Uid: uid,
DashboardUid: dashboardUid,

View File

@ -151,7 +151,7 @@ func (pd PublicDashboard) BuildTimeSettings(dashboard *models.Dashboard) TimeSet
}
// DTO for transforming user input in the api
type SavePublicDashboardConfigDTO struct {
type SavePublicDashboardDTO struct {
DashboardUid string
OrgId int64
UserId int64
@ -172,6 +172,6 @@ type AnnotationsQueryDTO struct {
// COMMANDS
//
type SavePublicDashboardConfigCommand struct {
type SavePublicDashboardCommand struct {
PublicDashboard PublicDashboard
}

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
// Code generated by mockery v2.12.1. DO NOT EDIT.
package publicdashboards
@ -15,6 +15,8 @@ import (
pkgmodels "github.com/grafana/grafana/pkg/models"
testing "testing"
user "github.com/grafana/grafana/pkg/services/user"
)
@ -297,11 +299,11 @@ func (_m *FakePublicDashboardService) NewPublicDashboardUid(ctx context.Context)
}
// Save provides a mock function with given fields: ctx, u, dto
func (_m *FakePublicDashboardService) Save(ctx context.Context, u *user.SignedInUser, dto *models.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
func (_m *FakePublicDashboardService) Save(ctx context.Context, u *user.SignedInUser, dto *models.SavePublicDashboardDTO) (*models.PublicDashboard, error) {
ret := _m.Called(ctx, u, dto)
var r0 *models.PublicDashboard
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardConfigDTO) *models.PublicDashboard); ok {
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardDTO) *models.PublicDashboard); ok {
r0 = rf(ctx, u, dto)
} else {
if ret.Get(0) != nil {
@ -310,7 +312,7 @@ func (_m *FakePublicDashboardService) Save(ctx context.Context, u *user.SignedIn
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardConfigDTO) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, *models.SavePublicDashboardDTO) error); ok {
r1 = rf(ctx, u, dto)
} else {
r1 = ret.Error(1)
@ -319,13 +321,8 @@ func (_m *FakePublicDashboardService) Save(ctx context.Context, u *user.SignedIn
return r0, r1
}
type mockConstructorTestingTNewFakePublicDashboardService interface {
mock.TestingT
Cleanup(func())
}
// NewFakePublicDashboardService creates a new instance of FakePublicDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakePublicDashboardService(t mockConstructorTestingTNewFakePublicDashboardService) *FakePublicDashboardService {
// NewFakePublicDashboardService creates a new instance of FakePublicDashboardService. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakePublicDashboardService(t testing.TB) *FakePublicDashboardService {
mock := &FakePublicDashboardService{}
mock.Mock.Test(t)

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
// Code generated by mockery v2.12.1. DO NOT EDIT.
package publicdashboards
@ -9,6 +9,8 @@ import (
mock "github.com/stretchr/testify/mock"
pkgmodels "github.com/grafana/grafana/pkg/models"
testing "testing"
)
// FakePublicDashboardStore is an autogenerated mock type for the Store type
@ -195,11 +197,11 @@ func (_m *FakePublicDashboardStore) GetOrgIdByAccessToken(ctx context.Context, a
}
// Save provides a mock function with given fields: ctx, cmd
func (_m *FakePublicDashboardStore) Save(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
func (_m *FakePublicDashboardStore) Save(ctx context.Context, cmd models.SavePublicDashboardCommand) error {
ret := _m.Called(ctx, cmd)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardCommand) error); ok {
r0 = rf(ctx, cmd)
} else {
r0 = ret.Error(0)
@ -209,11 +211,11 @@ func (_m *FakePublicDashboardStore) Save(ctx context.Context, cmd models.SavePub
}
// Update provides a mock function with given fields: ctx, cmd
func (_m *FakePublicDashboardStore) Update(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
func (_m *FakePublicDashboardStore) Update(ctx context.Context, cmd models.SavePublicDashboardCommand) error {
ret := _m.Called(ctx, cmd)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardCommand) error); ok {
r0 = rf(ctx, cmd)
} else {
r0 = ret.Error(0)
@ -222,13 +224,8 @@ func (_m *FakePublicDashboardStore) Update(ctx context.Context, cmd models.SaveP
return r0
}
type mockConstructorTestingTNewFakePublicDashboardStore interface {
mock.TestingT
Cleanup(func())
}
// NewFakePublicDashboardStore creates a new instance of FakePublicDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakePublicDashboardStore(t mockConstructorTestingTNewFakePublicDashboardStore) *FakePublicDashboardStore {
// NewFakePublicDashboardStore creates a new instance of FakePublicDashboardStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakePublicDashboardStore(t testing.TB) *FakePublicDashboardStore {
mock := &FakePublicDashboardStore{}
mock.Mock.Test(t)

View File

@ -19,7 +19,7 @@ type Service interface {
FindAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error)
FindDashboard(ctx context.Context, dashboardUid string, orgId int64) (*models.Dashboard, error)
FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error)
Save(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error)
Save(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error)
@ -38,8 +38,8 @@ type Store interface {
FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
FindDashboard(ctx context.Context, dashboardUid string, orgId int64) (*models.Dashboard, error)
FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error)
Save(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
Update(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
Save(ctx context.Context, cmd SavePublicDashboardCommand) error
Update(ctx context.Context, cmd SavePublicDashboardCommand) error
GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error)
ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error)

View File

@ -388,7 +388,7 @@ func TestGetQueryDataResponse(t *testing.T) {
}}
dashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels)
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -829,7 +829,7 @@ func TestBuildMetricRequest(t *testing.T) {
MaxDataPoints: int64(200),
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: publicDashboard.Uid,
OrgId: publicDashboard.OrgId,
PublicDashboard: &PublicDashboard{
@ -843,7 +843,7 @@ func TestBuildMetricRequest(t *testing.T) {
publicDashboardPD, err := service.Save(context.Background(), SignedInUser, dto)
require.NoError(t, err)
nonPublicDto := &SavePublicDashboardConfigDTO{
nonPublicDto := &SavePublicDashboardDTO{
DashboardUid: nonPublicDashboard.Uid,
OrgId: nonPublicDashboard.OrgId,
PublicDashboard: &PublicDashboard{

View File

@ -115,7 +115,7 @@ func (pd *PublicDashboardServiceImpl) FindByDashboardUid(ctx context.Context, or
// Save is a helper method to persist the sharing config
// to the database. It handles validations for sharing config and persistence
func (pd *PublicDashboardServiceImpl) Save(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error) {
func (pd *PublicDashboardServiceImpl) Save(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
// validate if the dashboard exists
dashboard, err := pd.FindDashboard(ctx, dto.DashboardUid, u.OrgID)
if err != nil {
@ -193,7 +193,7 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context.
// Called by Save this handles business logic
// to generate token and calls create at the database layer
func (pd *PublicDashboardServiceImpl) savePublicDashboard(ctx context.Context, dto *SavePublicDashboardConfigDTO) (string, error) {
func (pd *PublicDashboardServiceImpl) savePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (string, error) {
uid, err := pd.NewPublicDashboardUid(ctx)
if err != nil {
return "", err
@ -204,7 +204,7 @@ func (pd *PublicDashboardServiceImpl) savePublicDashboard(ctx context.Context, d
return "", err
}
cmd := SavePublicDashboardConfigCommand{
cmd := SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
Uid: uid,
DashboardUid: dto.DashboardUid,
@ -228,8 +228,8 @@ func (pd *PublicDashboardServiceImpl) savePublicDashboard(ctx context.Context, d
// Called by Save this handles business logic for updating a
// dashboard and calls update at the database layer
func (pd *PublicDashboardServiceImpl) updatePublicDashboard(ctx context.Context, dto *SavePublicDashboardConfigDTO) (string, error) {
cmd := SavePublicDashboardConfigCommand{
func (pd *PublicDashboardServiceImpl) updatePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (string, error) {
cmd := SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
Uid: dto.PublicDashboard.Uid,
IsEnabled: dto.PublicDashboard.IsEnabled,

View File

@ -133,7 +133,7 @@ func TestSavePublicDashboard(t *testing.T) {
store: publicdashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -179,7 +179,7 @@ func TestSavePublicDashboard(t *testing.T) {
store: publicdashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -210,7 +210,7 @@ func TestSavePublicDashboard(t *testing.T) {
store: publicdashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -246,7 +246,7 @@ func TestSavePublicDashboard(t *testing.T) {
store: publicDashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: "an-id",
OrgId: 8,
UserId: 7,
@ -277,7 +277,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
store: publicdashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -292,7 +292,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
require.NoError(t, err)
// attempt to overwrite settings
dto = &SavePublicDashboardConfigDTO{
dto = &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 8,
@ -341,7 +341,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
store: publicdashboardStore,
}
dto := &SavePublicDashboardConfigDTO{
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 7,
@ -357,7 +357,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
require.NoError(t, err)
// attempt to overwrite settings
dto = &SavePublicDashboardConfigDTO{
dto = &SavePublicDashboardDTO{
DashboardUid: dashboard.Uid,
OrgId: dashboard.OrgId,
UserId: 8,

View File

@ -7,7 +7,7 @@ import (
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
)
func ValidateSavePublicDashboard(dto *SavePublicDashboardConfigDTO, dashboard *models.Dashboard) error {
func ValidateSavePublicDashboard(dto *SavePublicDashboardDTO, dashboard *models.Dashboard) error {
if hasTemplateVariables(dashboard) {
return ErrPublicDashboardHasTemplateVariables
}

View File

@ -22,7 +22,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
}`)
dashboardData, _ := simplejson.NewJson(templateVars)
dashboard := models.NewDashboardFromJson(dashboardData)
dto := &SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
dto := &SavePublicDashboardDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard)
require.ErrorContains(t, err, ErrPublicDashboardHasTemplateVariables.Reason)
@ -36,7 +36,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
}`)
dashboardData, _ := simplejson.NewJson(templateVars)
dashboard := models.NewDashboardFromJson(dashboardData)
dto := &SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
dto := &SavePublicDashboardDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard)
require.NoError(t, err)