mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
100
pkg/services/publicdashboards/api/query.go
Normal file
100
pkg/services/publicdashboards/api/query.go
Normal 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)
|
||||
}
|
||||
427
pkg/services/publicdashboards/api/query_test.go
Normal file
427
pkg/services/publicdashboards/api/query_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user