mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
publicdashboards: split create/update api paths (#57940)
This PR splits the create and update paths for public dashboards and includes assorted refactors toward a proper REST API. Additionally, we removed the concept of a "public dashboard config" in favor of "public dashboard" Co-authored-by: juanicabanas <juan.cabanas@grafana.com> Co-authored-by: Ezequiel Victorero <ezequiel.victorero@grafana.com>
This commit is contained in:
parent
0367f61bb3
commit
6fcc5b42c0
@ -8,7 +8,7 @@ e2e.scenario({
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
// Opening a dashboard without template variables
|
||||
e2e().intercept('/api/ds/query').as('query');
|
||||
e2e().intercept('POST', '/api/ds/query').as('query');
|
||||
e2e.flows.openDashboard({ uid: 'ZqZnVvFZz' });
|
||||
e2e().wait('@query');
|
||||
|
||||
@ -16,9 +16,7 @@ e2e.scenario({
|
||||
e2e.pages.ShareDashboardModal.shareButton().click();
|
||||
|
||||
// Select public dashboards tab
|
||||
e2e().intercept('GET', '/api/dashboards/uid/ZqZnVvFZz/public-dashboards').as('query-public-dashboard');
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.Tab().click();
|
||||
e2e().wait('@query-public-dashboard');
|
||||
|
||||
// Saving button should be disabled
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.SaveConfigButton().should('be.disabled');
|
||||
@ -57,7 +55,7 @@ e2e.scenario({
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
// Opening a dashboard without template variables
|
||||
e2e().intercept('/api/ds/query').as('query');
|
||||
e2e().intercept('POST', '/api/ds/query').as('query');
|
||||
e2e.flows.openDashboard({ uid: 'ZqZnVvFZz' });
|
||||
e2e().wait('@query');
|
||||
|
||||
@ -125,9 +123,9 @@ e2e.scenario({
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.EnableSwitch().should('be.enabled').click({ force: true });
|
||||
|
||||
// Save public dashboard
|
||||
e2e().intercept('POST', '/api/dashboards/uid/ZqZnVvFZz/public-dashboards').as('save');
|
||||
e2e().intercept('PUT', '/api/dashboards/uid/ZqZnVvFZz/public-dashboards/*').as('update');
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.SaveConfigButton().click();
|
||||
e2e().wait('@save');
|
||||
e2e().wait('@update');
|
||||
|
||||
// Url should be hidden
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.CopyUrlInput().should('not.exist');
|
||||
|
@ -14,9 +14,7 @@ e2e.scenario({
|
||||
e2e.pages.ShareDashboardModal.shareButton().click();
|
||||
|
||||
// Select public dashboards tab
|
||||
e2e().intercept('GET', '/api/dashboards/uid/HYaGDGIMk/public-dashboards').as('query-public-config');
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.Tab().click();
|
||||
e2e().wait('@query-public-config');
|
||||
|
||||
// Warning Alert dashboard cannot be made public because it has template variables
|
||||
e2e.pages.ShareDashboardModal.PublicDashboard.TemplateVariablesWarningAlert().should('be.visible');
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/star"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -100,14 +101,23 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
}
|
||||
|
||||
var (
|
||||
hasPublicDashboard bool
|
||||
err error
|
||||
hasPublicDashboard = false
|
||||
publicDashboardEnabled = false
|
||||
err error
|
||||
)
|
||||
|
||||
// If public dashboards is enabled and we have a public dashboard, update meta
|
||||
// values
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
|
||||
hasPublicDashboard, err = hs.PublicDashboardsApi.PublicDashboardService.ExistsEnabledByDashboardUid(c.Req.Context(), dash.Uid)
|
||||
if err != nil {
|
||||
publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.OrgID, dash.Uid)
|
||||
if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) {
|
||||
return response.Error(500, "Error while retrieving public dashboards", err)
|
||||
}
|
||||
|
||||
if publicDashboard != nil {
|
||||
hasPublicDashboard = true
|
||||
publicDashboardEnabled = publicDashboard.IsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
// When dash contains only keys id, uid that means dashboard data is not valid and json decode failed.
|
||||
@ -172,7 +182,8 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
Url: dash.GetUrl(),
|
||||
FolderTitle: "General",
|
||||
AnnotationsPermissions: annotationPermissions,
|
||||
PublicDashboardEnabled: hasPublicDashboard,
|
||||
PublicDashboardEnabled: publicDashboardEnabled,
|
||||
HasPublicDashboard: hasPublicDashboard,
|
||||
}
|
||||
|
||||
// lookup folder title
|
||||
|
@ -32,6 +32,7 @@ type DashboardMeta struct {
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
||||
HasPublicDashboard bool `json:"hasPublicDashboard"`
|
||||
PublicDashboardAccessToken string `json:"publicDashboardAccessToken"`
|
||||
PublicDashboardUID string `json:"publicDashboardUid"`
|
||||
PublicDashboardEnabled bool `json:"publicDashboardEnabled"`
|
||||
|
@ -257,7 +257,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
AccessToken: "an-access-token",
|
||||
},
|
||||
}
|
||||
err := publicDashboardStore.Save(context.Background(), cmd)
|
||||
_, err := publicDashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
pubdashConfig, _ := publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
|
||||
require.NotNil(t, pubdashConfig)
|
||||
@ -292,7 +292,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
AccessToken: "an-access-token",
|
||||
},
|
||||
}
|
||||
err := publicDashboardStore.Save(context.Background(), cmd)
|
||||
_, err := publicDashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
pubdashConfig, _ := publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
|
||||
require.NotNil(t, pubdashConfig)
|
||||
|
@ -15,8 +15,8 @@ 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"
|
||||
)
|
||||
|
||||
@ -73,10 +73,15 @@ func (api *Api) RegisterAPIEndpoints() {
|
||||
auth(middleware.ReqSignedIn, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, uidScope)),
|
||||
routing.Wrap(api.GetPublicDashboard))
|
||||
|
||||
// Create/Update Public Dashboard
|
||||
// Create Public Dashboard
|
||||
api.RouteRegister.Post("/api/dashboards/uid/:dashboardUid/public-dashboards",
|
||||
auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)),
|
||||
routing.Wrap(api.SavePublicDashboard))
|
||||
routing.Wrap(api.CreatePublicDashboard))
|
||||
|
||||
// Update Public Dashboard
|
||||
api.RouteRegister.Put("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid",
|
||||
auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)),
|
||||
routing.Wrap(api.UpdatePublicDashboard))
|
||||
|
||||
// Delete Public dashboard
|
||||
api.RouteRegister.Delete("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid",
|
||||
@ -94,59 +99,103 @@ func (api *Api) ListPublicDashboards(c *models.ReqContext) response.Response {
|
||||
return response.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// GetPublicDashboard Gets public dashboard configuration for dashboard
|
||||
// GET /api/dashboards/uid/:uid/public-config
|
||||
// GetPublicDashboard Gets public dashboard for dashboard
|
||||
// GET /api/dashboards/uid/:uid/public-dashboards
|
||||
func (api *Api) GetPublicDashboard(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) {
|
||||
if !tokens.IsValidShortUID(dashboardUid) {
|
||||
api.handleError(c.Req.Context(), http.StatusBadRequest, "GetPublicDashboard: no valid dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
|
||||
}
|
||||
|
||||
pdc, err := api.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.OrgID, web.Params(c.Req)[":dashboardUid"])
|
||||
pd, 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)
|
||||
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "GetPublicDashboard: failed to get public dashboard ", err)
|
||||
}
|
||||
return response.JSON(http.StatusOK, pdc)
|
||||
|
||||
if pd == nil {
|
||||
return api.handleError(c.Req.Context(), http.StatusNotFound, "GetPublicDashboard: public dashboard not found", ErrPublicDashboardNotFound)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, pd)
|
||||
}
|
||||
|
||||
// SavePublicDashboard Sets public dashboard configuration for dashboard
|
||||
// POST /api/dashboards/uid/:uid/public-config
|
||||
func (api *Api) SavePublicDashboard(c *models.ReqContext) response.Response {
|
||||
// CreatePublicDashboard Sets public dashboard for dashboard
|
||||
// POST /api/dashboards/uid/:uid/public-dashboards
|
||||
func (api *Api) CreatePublicDashboard(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) {
|
||||
api.handleError(c.Req.Context(), http.StatusBadRequest, "SavePublicDashboard: invalid dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
|
||||
if !tokens.IsValidShortUID(dashboardUid) {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "CreatePublicDashboard: invalid dashboardUid", dashboards.ErrDashboardIdentifierInvalid)
|
||||
}
|
||||
|
||||
pubdash := &PublicDashboard{}
|
||||
if err := web.Bind(c.Req, pubdash); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "SavePublicDashboard: bad request data", err)
|
||||
pd := &PublicDashboard{}
|
||||
if err := web.Bind(c.Req, pd); err != nil {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "CreatePublicDashboard: bad request data", err)
|
||||
}
|
||||
|
||||
// Always set the orgID and userID from the session
|
||||
pubdash.OrgId = c.OrgID
|
||||
pd.OrgId = c.OrgID
|
||||
dto := SavePublicDashboardDTO{
|
||||
UserId: c.UserID,
|
||||
OrgId: c.OrgID,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboard: pubdash,
|
||||
PublicDashboard: pd,
|
||||
}
|
||||
|
||||
//Create the public dashboard
|
||||
pd, err := api.PublicDashboardService.Create(c.Req.Context(), c.SignedInUser, &dto)
|
||||
if err != nil {
|
||||
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "CreatePublicDashboard: failed to create public dashboard", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, pd)
|
||||
}
|
||||
|
||||
// UpdatePublicDashboard Sets public dashboard for dashboard
|
||||
// PUT /api/dashboards/uid/:uid/public-dashboards
|
||||
func (api *Api) UpdatePublicDashboard(c *models.ReqContext) response.Response {
|
||||
// exit if we don't have a valid dashboardUid
|
||||
dashboardUid := web.Params(c.Req)[":dashboardUid"]
|
||||
if !tokens.IsValidShortUID(dashboardUid) {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "UpdatePublicDashboard: invalid dashboardUid", dashboards.ErrDashboardIdentifierInvalid)
|
||||
}
|
||||
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
if !tokens.IsValidShortUID(uid) {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "UpdatePublicDashboard: invalid public dashboard uid", ErrPublicDashboardIdentifierNotSet)
|
||||
}
|
||||
|
||||
pd := &PublicDashboard{}
|
||||
if err := web.Bind(c.Req, pd); err != nil {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "UpdatePublicDashboard: bad request data", err)
|
||||
}
|
||||
|
||||
// Always set the orgID and userID from the session
|
||||
pd.OrgId = c.OrgID
|
||||
pd.Uid = uid
|
||||
dto := SavePublicDashboardDTO{
|
||||
UserId: c.UserID,
|
||||
OrgId: c.OrgID,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboard: pd,
|
||||
}
|
||||
|
||||
// Save the public dashboard
|
||||
pubdash, err := api.PublicDashboardService.Save(c.Req.Context(), c.SignedInUser, &dto)
|
||||
pd, err := api.PublicDashboardService.Update(c.Req.Context(), c.SignedInUser, &dto)
|
||||
if err != nil {
|
||||
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "SavePublicDashboardConfig: failed to save public dashboard configuration", err)
|
||||
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "UpdatePublicDashboard: failed to update public dashboard", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, pubdash)
|
||||
return response.JSON(http.StatusOK, pd)
|
||||
}
|
||||
|
||||
// Delete a public dashboard
|
||||
// DELETE /api/dashboards/uid/:dashboardUid/public-dashboards/:uid
|
||||
func (api *Api) DeletePublicDashboard(c *models.ReqContext) response.Response {
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
if uid == "" || !util.IsValidShortUID(uid) {
|
||||
if !tokens.IsValidShortUID(uid) {
|
||||
return api.handleError(c.Req.Context(), http.StatusBadRequest, "DeletePublicDashboard: invalid dashboard uid", dashboards.ErrDashboardIdentifierNotSet)
|
||||
}
|
||||
|
||||
@ -171,7 +220,6 @@ func (api *Api) handleError(ctx context.Context, code int, message string, err e
|
||||
return response.Error(publicDashboardErr.StatusCode, publicDashboardErr.Error(), publicDashboardErr)
|
||||
}
|
||||
|
||||
// handle dashboard errors as well
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr)
|
||||
|
@ -57,10 +57,20 @@ func TestAPIFeatureFlag(t *testing.T) {
|
||||
Path: "/api/dashboards/uid/abc123/public-dashboards",
|
||||
},
|
||||
{
|
||||
Name: "API: Save Public Dashboard",
|
||||
Name: "API: Create Public Dashboard",
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/dashboards/uid/abc123/public-dashboards",
|
||||
},
|
||||
{
|
||||
Name: "API: Update Public Dashboard",
|
||||
Method: http.MethodPut,
|
||||
Path: "/api/dashboards/uid/abc123/public-dashboards",
|
||||
},
|
||||
{
|
||||
Name: "API: Delete Public Dashboard",
|
||||
Method: http.MethodDelete,
|
||||
Path: "/api/dashboards/uid/:dashboardUid/public-dashboards/:uid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@ -148,6 +158,354 @@ func TestAPIListPublicDashboard(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIGetPublicDashboard(t *testing.T) {
|
||||
pubdash := &PublicDashboard{IsEnabled: true}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
DashboardUid string
|
||||
ExpectedHttpResponse int
|
||||
PublicDashboardResult *PublicDashboard
|
||||
PublicDashboardErr error
|
||||
User *user.SignedInUser
|
||||
AccessControlEnabled bool
|
||||
ShouldCallService bool
|
||||
}{
|
||||
{
|
||||
Name: "retrieves public dashboard when dashboard is found",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 404 when dashboard not found",
|
||||
DashboardUid: "77777",
|
||||
ExpectedHttpResponse: http.StatusNotFound,
|
||||
PublicDashboardResult: nil,
|
||||
PublicDashboardErr: dashboards.ErrDashboardNotFound,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 500 when internal server error",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusInternalServerError,
|
||||
PublicDashboardResult: nil,
|
||||
PublicDashboardErr: errors.New("database broken"),
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "retrieves public dashboard when dashboard is found RBAC on",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewerRBAC,
|
||||
AccessControlEnabled: true,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 403 when no permissions RBAC on",
|
||||
ExpectedHttpResponse: http.StatusForbidden,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: true,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(t)
|
||||
|
||||
if test.ShouldCallService {
|
||||
service.On("FindByDashboardUid", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
|
||||
Return(test.PublicDashboardResult, test.PublicDashboardErr)
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = test.AccessControlEnabled
|
||||
|
||||
testServer := setupTestServer(
|
||||
t,
|
||||
cfg,
|
||||
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
|
||||
service,
|
||||
nil,
|
||||
test.User,
|
||||
)
|
||||
|
||||
response := callAPI(
|
||||
testServer,
|
||||
http.MethodGet,
|
||||
"/api/dashboards/uid/1/public-dashboards",
|
||||
nil,
|
||||
t,
|
||||
)
|
||||
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
if response.Code == http.StatusOK {
|
||||
var pdcResp PublicDashboard
|
||||
err := json.Unmarshal(response.Body.Bytes(), &pdcResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.PublicDashboardResult, &pdcResp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiCreatePublicDashboard(t *testing.T) {
|
||||
testCases := []struct {
|
||||
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",
|
||||
publicDashboard: &PublicDashboard{IsEnabled: true},
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
SaveDashboardErr: nil,
|
||||
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,
|
||||
publicDashboard: &PublicDashboard{},
|
||||
SaveDashboardErr: dashboards.ErrDashboardNotFound,
|
||||
User: userAdmin,
|
||||
AccessControlEnabled: false,
|
||||
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,
|
||||
publicDashboard: &PublicDashboard{IsEnabled: true},
|
||||
SaveDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(t)
|
||||
|
||||
// this is to avoid AssertExpectations fail at t.Cleanup when the middleware returns before calling the service
|
||||
if test.ShouldCallService {
|
||||
service.On("Create", mock.Anything, mock.Anything, mock.AnythingOfType("*models.SavePublicDashboardDTO")).
|
||||
Return(&PublicDashboard{IsEnabled: true}, test.SaveDashboardErr)
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = test.AccessControlEnabled
|
||||
|
||||
testServer := setupTestServer(
|
||||
t,
|
||||
cfg,
|
||||
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
|
||||
service,
|
||||
nil,
|
||||
test.User,
|
||||
)
|
||||
|
||||
response := callAPI(
|
||||
testServer,
|
||||
http.MethodPost,
|
||||
"/api/dashboards/uid/1/public-dashboards",
|
||||
strings.NewReader(`{ "isPublic": true }`),
|
||||
t,
|
||||
)
|
||||
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
//check the result if it's a 200
|
||||
if response.Code == http.StatusOK {
|
||||
val, err := json.Marshal(test.publicDashboard)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(val), response.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIUpdatePublicDashboard(t *testing.T) {
|
||||
dashboardUid := "abc1234"
|
||||
publicDashboardUid := "1234asdfasdf"
|
||||
|
||||
adminUser := &user.SignedInUser{UserID: 4, OrgID: 1, OrgRole: org.RoleEditor, Login: "testEditorUser", Permissions: map[int64]map[string][]string{1: {dashboards.ActionDashboardsPublicWrite: {dashboards.ScopeDashboardsAll}}}}
|
||||
|
||||
userEditorPublicDashboard := &user.SignedInUser{UserID: 4, OrgID: 1, OrgRole: org.RoleEditor, Login: "testEditorUser", Permissions: map[int64]map[string][]string{1: {dashboards.ActionDashboardsPublicWrite: {fmt.Sprintf("dashboards:uid:%s", dashboardUid)}}}}
|
||||
|
||||
userEditorAnotherPublicDashboard := &user.SignedInUser{UserID: 4, OrgID: 1, OrgRole: org.RoleEditor, Login: "testEditorUser", Permissions: map[int64]map[string][]string{1: {dashboards.ActionDashboardsPublicWrite: {"another-uid"}}}}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
User *user.SignedInUser
|
||||
DashboardUid string
|
||||
PublicDashboardUid string
|
||||
PublicDashboardRes *PublicDashboard
|
||||
PublicDashboardErr error
|
||||
ExpectedHttpResponse int
|
||||
ShouldCallService bool
|
||||
}{
|
||||
{
|
||||
Name: "Invalid dashboardUid",
|
||||
User: adminUser,
|
||||
DashboardUid: "",
|
||||
PublicDashboardUid: "",
|
||||
PublicDashboardRes: nil,
|
||||
PublicDashboardErr: dashboards.ErrDashboardIdentifierInvalid,
|
||||
ExpectedHttpResponse: http.StatusNotFound,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
{
|
||||
Name: "Invalid public dashboard uid",
|
||||
User: adminUser,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboardUid: "",
|
||||
PublicDashboardRes: nil,
|
||||
PublicDashboardErr: ErrPublicDashboardNotFound,
|
||||
ExpectedHttpResponse: http.StatusNotFound,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
{
|
||||
Name: "Service Error",
|
||||
User: adminUser,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboardUid: publicDashboardUid,
|
||||
PublicDashboardRes: nil,
|
||||
PublicDashboardErr: dashboards.ErrDashboardNotFound,
|
||||
ExpectedHttpResponse: http.StatusNotFound,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "Success",
|
||||
User: adminUser,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboardUid: publicDashboardUid,
|
||||
PublicDashboardRes: &PublicDashboard{Uid: "success"},
|
||||
PublicDashboardErr: nil,
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
|
||||
// permissions
|
||||
{
|
||||
Name: "User can update this public dashboard",
|
||||
User: userEditorPublicDashboard,
|
||||
DashboardUid: dashboardUid,
|
||||
PublicDashboardUid: publicDashboardUid,
|
||||
PublicDashboardRes: &PublicDashboard{Uid: "success"},
|
||||
PublicDashboardErr: nil,
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "User has permissions on another dashboard",
|
||||
User: userEditorAnotherPublicDashboard,
|
||||
PublicDashboardUid: publicDashboardUid,
|
||||
ExpectedHttpResponse: http.StatusForbidden,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
{
|
||||
Name: "Viewer cannot update any dashboard",
|
||||
User: userViewer,
|
||||
PublicDashboardUid: publicDashboardUid,
|
||||
ExpectedHttpResponse: http.StatusForbidden,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(t)
|
||||
|
||||
if test.ShouldCallService {
|
||||
service.On("Update", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(test.PublicDashboardRes, test.PublicDashboardErr)
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards)
|
||||
testServer := setupTestServer(t, cfg, features, service, nil, test.User)
|
||||
url := fmt.Sprintf("/api/dashboards/uid/%s/public-dashboards/%s", test.DashboardUid, test.PublicDashboardUid)
|
||||
body := strings.NewReader(fmt.Sprintf(`{ "uid": "%s"}`, test.PublicDashboardUid))
|
||||
|
||||
response := callAPI(testServer, http.MethodPut, url, body, t)
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
// check whether service called
|
||||
if !test.ShouldCallService {
|
||||
service.AssertNotCalled(t, "Update")
|
||||
}
|
||||
|
||||
fmt.Println(response.Body.String())
|
||||
|
||||
// check response
|
||||
if response.Code == http.StatusOK {
|
||||
val, err := json.Marshal(test.PublicDashboardRes)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(val), response.Body.String())
|
||||
|
||||
// verify 4XXs except 403 && 404
|
||||
} else if test.ExpectedHttpResponse > 200 &&
|
||||
test.ExpectedHttpResponse != 403 &&
|
||||
test.ExpectedHttpResponse != 404 {
|
||||
var errResp JsonErrResponse
|
||||
err := json.Unmarshal(response.Body.Bytes(), &errResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.PublicDashboardErr.Error(), errResp.Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIDeletePublicDashboard(t *testing.T) {
|
||||
dashboardUid := "abc1234"
|
||||
publicDashboardUid := "1234asdfasdf"
|
||||
@ -275,219 +633,3 @@ func TestAPIDeletePublicDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIGetPublicDashboard(t *testing.T) {
|
||||
pubdash := &PublicDashboard{IsEnabled: true}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
DashboardUid string
|
||||
ExpectedHttpResponse int
|
||||
PublicDashboardResult *PublicDashboard
|
||||
PublicDashboardErr error
|
||||
User *user.SignedInUser
|
||||
AccessControlEnabled bool
|
||||
ShouldCallService bool
|
||||
}{
|
||||
{
|
||||
Name: "retrieves public dashboard when dashboard is found",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 404 when dashboard not found",
|
||||
DashboardUid: "77777",
|
||||
ExpectedHttpResponse: http.StatusNotFound,
|
||||
PublicDashboardResult: nil,
|
||||
PublicDashboardErr: dashboards.ErrDashboardNotFound,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 500 when internal server error",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusInternalServerError,
|
||||
PublicDashboardResult: nil,
|
||||
PublicDashboardErr: errors.New("database broken"),
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "retrieves public dashboard when dashboard is found RBAC on",
|
||||
DashboardUid: "1",
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewerRBAC,
|
||||
AccessControlEnabled: true,
|
||||
ShouldCallService: true,
|
||||
},
|
||||
{
|
||||
Name: "returns 403 when no permissions RBAC on",
|
||||
ExpectedHttpResponse: http.StatusForbidden,
|
||||
PublicDashboardResult: pubdash,
|
||||
PublicDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: true,
|
||||
ShouldCallService: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(t)
|
||||
|
||||
if test.ShouldCallService {
|
||||
service.On("FindByDashboardUid", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
|
||||
Return(test.PublicDashboardResult, test.PublicDashboardErr)
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = test.AccessControlEnabled
|
||||
|
||||
testServer := setupTestServer(
|
||||
t,
|
||||
cfg,
|
||||
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
|
||||
service,
|
||||
nil,
|
||||
test.User,
|
||||
)
|
||||
|
||||
response := callAPI(
|
||||
testServer,
|
||||
http.MethodGet,
|
||||
"/api/dashboards/uid/1/public-dashboards",
|
||||
nil,
|
||||
t,
|
||||
)
|
||||
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
if response.Code == http.StatusOK {
|
||||
var pdcResp PublicDashboard
|
||||
err := json.Unmarshal(response.Body.Bytes(), &pdcResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.PublicDashboardResult, &pdcResp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiSavePublicDashboard(t *testing.T) {
|
||||
testCases := []struct {
|
||||
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",
|
||||
publicDashboard: &PublicDashboard{IsEnabled: true},
|
||||
ExpectedHttpResponse: http.StatusOK,
|
||||
SaveDashboardErr: nil,
|
||||
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,
|
||||
publicDashboard: &PublicDashboard{},
|
||||
SaveDashboardErr: dashboards.ErrDashboardNotFound,
|
||||
User: userAdmin,
|
||||
AccessControlEnabled: false,
|
||||
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,
|
||||
publicDashboard: &PublicDashboard{IsEnabled: true},
|
||||
SaveDashboardErr: nil,
|
||||
User: userViewer,
|
||||
AccessControlEnabled: false,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(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.SavePublicDashboardDTO")).
|
||||
Return(&PublicDashboard{IsEnabled: true}, test.SaveDashboardErr)
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = test.AccessControlEnabled
|
||||
|
||||
testServer := setupTestServer(
|
||||
t,
|
||||
cfg,
|
||||
featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards),
|
||||
service,
|
||||
nil,
|
||||
test.User,
|
||||
)
|
||||
|
||||
response := callAPI(
|
||||
testServer,
|
||||
http.MethodPost,
|
||||
"/api/dashboards/uid/1/public-dashboards",
|
||||
strings.NewReader(`{ "isPublic": true }`),
|
||||
t,
|
||||
)
|
||||
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
//check the result if it's a 200
|
||||
if response.Code == http.StatusOK {
|
||||
val, err := json.Marshal(test.publicDashboard)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(val), response.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
ac := acmock.New()
|
||||
cfg.RBACEnabled = false
|
||||
service := publicdashboardsService.ProvideService(cfg, store, qds, annotationsService, ac)
|
||||
pubdash, err := service.Save(context.Background(), &user.SignedInUser{}, savePubDashboardCmd)
|
||||
pubdash, err := service.Create(context.Background(), &user.SignedInUser{}, savePubDashboardCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// setup test server
|
||||
|
@ -66,11 +66,15 @@ func (d *PublicDashboardStoreImpl) FindDashboard(ctx context.Context, orgId int6
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return dashboard, err
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
// Find Returns public dashboard by Uid or nil if not found
|
||||
@ -80,10 +84,10 @@ func (d *PublicDashboardStoreImpl) Find(ctx context.Context, uid string) (*Publi
|
||||
}
|
||||
|
||||
var found bool
|
||||
pdRes := &PublicDashboard{Uid: uid}
|
||||
publicDashboard := &PublicDashboard{Uid: uid}
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
found, err = sess.Get(pdRes)
|
||||
found, err = sess.Get(publicDashboard)
|
||||
return err
|
||||
})
|
||||
|
||||
@ -95,7 +99,7 @@ func (d *PublicDashboardStoreImpl) Find(ctx context.Context, uid string) (*Publi
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pdRes, err
|
||||
return publicDashboard, nil
|
||||
}
|
||||
|
||||
// FindByAccessToken Returns public dashboard by access token or nil if not found
|
||||
@ -105,10 +109,10 @@ func (d *PublicDashboardStoreImpl) FindByAccessToken(ctx context.Context, access
|
||||
}
|
||||
|
||||
var found bool
|
||||
pdRes := &PublicDashboard{AccessToken: accessToken}
|
||||
publicDashboard := &PublicDashboard{AccessToken: accessToken}
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
found, err = sess.Get(pdRes)
|
||||
found, err = sess.Get(publicDashboard)
|
||||
return err
|
||||
})
|
||||
|
||||
@ -120,7 +124,7 @@ func (d *PublicDashboardStoreImpl) FindByAccessToken(ctx context.Context, access
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pdRes, err
|
||||
return publicDashboard, nil
|
||||
}
|
||||
|
||||
// FindByDashboardUid Retrieves public dashboard by dashboard uid or nil if not found
|
||||
@ -128,7 +132,6 @@ func (d *PublicDashboardStoreImpl) FindByDashboardUid(ctx context.Context, orgId
|
||||
if dashboardUid == "" || orgId == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var found bool
|
||||
publicDashboard := &PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
@ -149,67 +152,7 @@ func (d *PublicDashboardStoreImpl) FindByDashboardUid(ctx context.Context, orgId
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return publicDashboard, err
|
||||
}
|
||||
|
||||
// Save Persists public dashboard
|
||||
func (d *PublicDashboardStoreImpl) Save(ctx context.Context, cmd SavePublicDashboardCommand) error {
|
||||
if cmd.PublicDashboard.DashboardUid == "" {
|
||||
return dashboards.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
_, err := sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Update updates existing public dashboard
|
||||
func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDashboardCommand) error {
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
timeSettingsJSON, err := json.Marshal(cmd.PublicDashboard.TimeSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("UPDATE dashboard_public SET is_enabled = ?, annotations_enabled = ?, time_settings = ?, updated_by = ?, updated_at = ? WHERE uid = ?",
|
||||
cmd.PublicDashboard.IsEnabled,
|
||||
cmd.PublicDashboard.AnnotationsEnabled,
|
||||
string(timeSettingsJSON),
|
||||
cmd.PublicDashboard.UpdatedBy,
|
||||
cmd.PublicDashboard.UpdatedAt.UTC().Format("2006-01-02 15:04:05"),
|
||||
cmd.PublicDashboard.Uid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid string) (int64, error) {
|
||||
dashboard := &PublicDashboard{OrgId: orgId, Uid: uid}
|
||||
var affectedRows int64
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
affectedRows, err = sess.Delete(dashboard)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return affectedRows, err
|
||||
return publicDashboard, nil
|
||||
}
|
||||
|
||||
// ExistsEnabledByDashboardUid Responds true if there is an enabled public dashboard for a dashboard uid
|
||||
@ -264,3 +207,62 @@ func (d *PublicDashboardStoreImpl) GetOrgIdByAccessToken(ctx context.Context, ac
|
||||
|
||||
return orgId, err
|
||||
}
|
||||
|
||||
// Creates a public dashboard
|
||||
func (d *PublicDashboardStoreImpl) Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) {
|
||||
if cmd.PublicDashboard.DashboardUid == "" {
|
||||
return 0, dashboards.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
var affectedRows int64
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
affectedRows, err = sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard)
|
||||
return err
|
||||
})
|
||||
|
||||
return affectedRows, err
|
||||
}
|
||||
|
||||
// Updates existing public dashboard
|
||||
func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) {
|
||||
var affectedRows int64
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
timeSettingsJSON, err := json.Marshal(cmd.PublicDashboard.TimeSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlResult, err := sess.Exec("UPDATE dashboard_public SET is_enabled = ?, annotations_enabled = ?, time_settings = ?, updated_by = ?, updated_at = ? WHERE uid = ?",
|
||||
cmd.PublicDashboard.IsEnabled,
|
||||
cmd.PublicDashboard.AnnotationsEnabled,
|
||||
string(timeSettingsJSON),
|
||||
cmd.PublicDashboard.UpdatedBy,
|
||||
cmd.PublicDashboard.UpdatedAt.UTC().Format("2006-01-02 15:04:05"),
|
||||
cmd.PublicDashboard.Uid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
affectedRows, err = sqlResult.RowsAffected()
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return affectedRows, err
|
||||
}
|
||||
|
||||
// Deletes a public dashboard
|
||||
func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid string) (int64, error) {
|
||||
dashboard := &PublicDashboard{OrgId: orgId, Uid: uid}
|
||||
var affectedRows int64
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
affectedRows, err = sess.Delete(dashboard)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return affectedRows, err
|
||||
}
|
||||
|
@ -103,7 +103,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(), SavePublicDashboardCommand{
|
||||
_, err := publicdashboardStore.Create(context.Background(), SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "abc123",
|
||||
@ -125,7 +125,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(), SavePublicDashboardCommand{
|
||||
_, err := publicdashboardStore.Create(context.Background(), SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: false,
|
||||
Uid: "abc123",
|
||||
@ -171,7 +171,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(), SavePublicDashboardCommand{
|
||||
_, err := publicdashboardStore.Create(context.Background(), SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "abc123",
|
||||
@ -193,7 +193,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(), SavePublicDashboardCommand{
|
||||
_, err := publicdashboardStore.Create(context.Background(), SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: false,
|
||||
Uid: "abc123",
|
||||
@ -257,7 +257,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
|
||||
}
|
||||
|
||||
// insert test public dashboard
|
||||
err := publicdashboardStore.Save(context.Background(), cmd)
|
||||
_, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// retrieve from db
|
||||
@ -320,7 +320,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
|
||||
}
|
||||
|
||||
// insert test public dashboard
|
||||
err := publicdashboardStore.Save(context.Background(), cmd)
|
||||
_, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// retrieve from db
|
||||
@ -338,7 +338,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationSavePublicDashboard(t *testing.T) {
|
||||
func TestIntegrationCreatePublicDashboard(t *testing.T) {
|
||||
var sqlStore db.DB
|
||||
var cfg *setting.Cfg
|
||||
var dashboardStore *dashboardsDB.DashboardStore
|
||||
@ -357,7 +357,7 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("saves new public dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: true,
|
||||
AnnotationsEnabled: true,
|
||||
@ -369,14 +369,14 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
}
|
||||
affectedRows, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, affectedRows, 1)
|
||||
|
||||
pubdash, err := publicdashboardStore.FindByDashboardUid(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify we have a valid uid
|
||||
assert.True(t, util.IsValidShortUID(pubdash.Uid))
|
||||
assert.Equal(t, pubdash.AccessToken, "NOTAREALUUID")
|
||||
|
||||
// verify we didn't update all dashboards
|
||||
pubdash2, err := publicdashboardStore.FindByDashboardUid(context.Background(), savedDashboard2.OrgId, savedDashboard2.Uid)
|
||||
@ -386,7 +386,7 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("guards from saving without dashboardUid", func(t *testing.T) {
|
||||
setup()
|
||||
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "pubdash-uid",
|
||||
@ -397,9 +397,11 @@ func TestIntegrationSavePublicDashboard(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
}
|
||||
affectedRows, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, err, dashboards.ErrDashboardIdentifierNotSet)
|
||||
assert.EqualValues(t, affectedRows, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -423,7 +425,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
setup()
|
||||
|
||||
pdUid := "asdf1234"
|
||||
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: pdUid,
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
@ -434,12 +436,14 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
}
|
||||
affectedRows, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, affectedRows, 1)
|
||||
|
||||
// inserting two different public dashboards to test update works and only affect the desired pd by uid
|
||||
anotherPdUid := "anotherUid"
|
||||
err = publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
|
||||
cmd = SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: anotherPdUid,
|
||||
DashboardUid: anotherSavedDashboard.Uid,
|
||||
@ -450,8 +454,11 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "fakeaccesstoken",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
affectedRows, err = publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, affectedRows, 1)
|
||||
|
||||
updatedPublicDashboard := PublicDashboard{
|
||||
Uid: pdUid,
|
||||
@ -463,11 +470,12 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
UpdatedAt: time.Now().UTC().Round(time.Second),
|
||||
UpdatedBy: 8,
|
||||
}
|
||||
|
||||
// update initial record
|
||||
err = publicdashboardStore.Update(context.Background(), SavePublicDashboardCommand{
|
||||
PublicDashboard: updatedPublicDashboard,
|
||||
})
|
||||
cmd = SavePublicDashboardCommand{PublicDashboard: updatedPublicDashboard}
|
||||
rowsAffected, err := publicdashboardStore.Update(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, rowsAffected, 1)
|
||||
|
||||
// updated dashboard should have changed
|
||||
pdRetrieved, err := publicdashboardStore.FindByDashboardUid(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
@ -503,8 +511,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
||||
}
|
||||
t.Run("GetOrgIdByAccessToken will OrgId when enabled", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
err := publicdashboardStore.Save(context.Background(), SavePublicDashboardCommand{
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "abc123",
|
||||
@ -514,7 +521,8 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "accessToken",
|
||||
},
|
||||
})
|
||||
}
|
||||
_, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
orgId, err := publicdashboardStore.GetOrgIdByAccessToken(context.Background(), "accessToken")
|
||||
@ -525,8 +533,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(), SavePublicDashboardCommand{
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
IsEnabled: false,
|
||||
Uid: "abc123",
|
||||
@ -536,8 +543,11 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
||||
CreatedBy: 7,
|
||||
AccessToken: "accessToken",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_, err := publicdashboardStore.Create(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
orgId, err := publicdashboardStore.GetOrgIdByAccessToken(context.Background(), "accessToken")
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, savedDashboard.OrgId, orgId)
|
||||
@ -634,8 +644,9 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt
|
||||
},
|
||||
}
|
||||
|
||||
err = publicdashboardStore.Save(ctx, cmd)
|
||||
affectedRows, err := publicdashboardStore.Create(ctx, cmd)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, affectedRows, 1)
|
||||
|
||||
pubdash, err := publicdashboardStore.Find(ctx, uid)
|
||||
require.NoError(t, err)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// GenerateAccessToken generates an uuid formatted without dashes to use as access token
|
||||
@ -20,3 +21,9 @@ func IsValidAccessToken(token string) bool {
|
||||
_, err := uuid.Parse(token)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsValidShortUID checks that the uid is not blank and contains valid
|
||||
// characters. Wraps utils.IsValidShortUID
|
||||
func IsValidShortUID(uid string) bool {
|
||||
return uid != "" && util.IsValidShortUID(uid)
|
||||
}
|
||||
|
@ -36,3 +36,19 @@ func TestValidAccessToken(t *testing.T) {
|
||||
assert.False(t, IsValidAccessToken("0123456789012345678901234567890123456789"))
|
||||
})
|
||||
}
|
||||
|
||||
// we just check base cases since this wraps utils.IsValidShortUID which has
|
||||
// test coverage
|
||||
func TestValidUid(t *testing.T) {
|
||||
t.Run("true", func(t *testing.T) {
|
||||
assert.True(t, IsValidShortUID("afqrz7jZZ"))
|
||||
})
|
||||
|
||||
t.Run("false when blank", func(t *testing.T) {
|
||||
assert.False(t, IsValidShortUID(""))
|
||||
})
|
||||
|
||||
t.Run("false when invalid chars", func(t *testing.T) {
|
||||
assert.False(t, IsValidShortUID("afqrz7j%%"))
|
||||
})
|
||||
}
|
||||
|
@ -25,6 +25,29 @@ type FakePublicDashboardService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, u, dto
|
||||
func (_m *FakePublicDashboardService) Create(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.SavePublicDashboardDTO) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, u, dto)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
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)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, orgId, uid
|
||||
func (_m *FakePublicDashboardService) Delete(ctx context.Context, orgId int64, uid string) error {
|
||||
ret := _m.Called(ctx, orgId, uid)
|
||||
@ -312,8 +335,8 @@ func (_m *FakePublicDashboardService) NewPublicDashboardUid(ctx context.Context)
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: ctx, u, dto
|
||||
func (_m *FakePublicDashboardService) Save(ctx context.Context, u *user.SignedInUser, dto *models.SavePublicDashboardDTO) (*models.PublicDashboard, error) {
|
||||
// Update provides a mock function with given fields: ctx, u, dto
|
||||
func (_m *FakePublicDashboardService) Update(ctx context.Context, u *user.SignedInUser, dto *models.SavePublicDashboardDTO) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, u, dto)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
|
@ -18,6 +18,27 @@ type FakePublicDashboardStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakePublicDashboardStore) Create(ctx context.Context, cmd models.SavePublicDashboardCommand) (int64, error) {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardCommand) int64); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, models.SavePublicDashboardCommand) error); ok {
|
||||
r1 = rf(ctx, cmd)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, orgId, uid
|
||||
func (_m *FakePublicDashboardStore) Delete(ctx context.Context, orgId int64, uid string) (int64, error) {
|
||||
ret := _m.Called(ctx, orgId, uid)
|
||||
@ -217,32 +238,25 @@ func (_m *FakePublicDashboardStore) GetOrgIdByAccessToken(ctx context.Context, a
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: ctx, cmd
|
||||
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.SavePublicDashboardCommand) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakePublicDashboardStore) Update(ctx context.Context, cmd models.SavePublicDashboardCommand) error {
|
||||
func (_m *FakePublicDashboardStore) Update(ctx context.Context, cmd models.SavePublicDashboardCommand) (int64, error) {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardCommand) error); ok {
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardCommand) int64); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
return r0
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, models.SavePublicDashboardCommand) error); ok {
|
||||
r1 = rf(ctx, cmd)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -20,7 +20,8 @@ type Service interface {
|
||||
FindAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error)
|
||||
FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*models.Dashboard, error)
|
||||
FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error)
|
||||
Save(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
|
||||
Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
|
||||
Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
|
||||
Delete(ctx context.Context, orgId int64, uid string) error
|
||||
|
||||
GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
|
||||
@ -40,8 +41,8 @@ type Store interface {
|
||||
FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||
FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*models.Dashboard, error)
|
||||
FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error)
|
||||
Save(ctx context.Context, cmd SavePublicDashboardCommand) error
|
||||
Update(ctx context.Context, cmd SavePublicDashboardCommand) error
|
||||
Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
|
||||
Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
|
||||
Delete(ctx context.Context, orgId int64, uid string) (int64, error)
|
||||
|
||||
GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error)
|
||||
|
@ -399,7 +399,7 @@ func TestGetQueryDataResponse(t *testing.T) {
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
pubdashDto, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
pubdashDto, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, _ := service.GetQueryDataResponse(context.Background(), true, publicDashboardQueryDTO, 1, pubdashDto.AccessToken)
|
||||
@ -840,7 +840,7 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
publicDashboardPD, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
publicDashboardPD, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
nonPublicDto := &SavePublicDashboardDTO{
|
||||
@ -854,7 +854,7 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err = service.Save(context.Background(), SignedInUser, nonPublicDto)
|
||||
_, err = service.Create(context.Background(), SignedInUser, nonPublicDto)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
||||
|
@ -121,9 +121,76 @@ func (pd *PublicDashboardServiceImpl) FindByDashboardUid(ctx context.Context, or
|
||||
return pubdash, nil
|
||||
}
|
||||
|
||||
// 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 *SavePublicDashboardDTO) (*PublicDashboard, error) {
|
||||
// Creates and validates the public dashboard and saves it to the database
|
||||
func (pd *PublicDashboardServiceImpl) Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
|
||||
// ensure dashboard exists
|
||||
dashboard, err := pd.FindDashboard(ctx, u.OrgID, dto.DashboardUid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set default value for time settings
|
||||
if dto.PublicDashboard.TimeSettings == nil {
|
||||
dto.PublicDashboard.TimeSettings = &TimeSettings{}
|
||||
}
|
||||
|
||||
// validate fields
|
||||
err = validation.ValidatePublicDashboard(dto, dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verify public dashboard does not exist and that we didn't get one from the
|
||||
// request
|
||||
existingPubdash, err := pd.store.Find(ctx, dto.PublicDashboard.Uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if existingPubdash != nil {
|
||||
return nil, ErrPublicDashboardBadRequest
|
||||
}
|
||||
|
||||
uid, err := pd.NewPublicDashboardUid(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessToken, err := pd.NewPublicDashboardAccessToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: uid,
|
||||
DashboardUid: dto.DashboardUid,
|
||||
OrgId: dto.OrgId,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
AnnotationsEnabled: dto.PublicDashboard.AnnotationsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
CreatedBy: dto.UserId,
|
||||
CreatedAt: time.Now(),
|
||||
AccessToken: accessToken,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = pd.store.Create(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get latest public dashboard to return
|
||||
newPubdash, err := pd.store.Find(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pd.logIsEnabledChanged(existingPubdash, newPubdash, u)
|
||||
|
||||
return newPubdash, err
|
||||
}
|
||||
|
||||
// Updates an existing public dashboard based on publicdashboard.Uid
|
||||
func (pd *PublicDashboardServiceImpl) Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
|
||||
// validate if the dashboard exists
|
||||
dashboard, err := pd.FindDashboard(ctx, u.OrgID, dto.DashboardUid)
|
||||
if err != nil {
|
||||
@ -143,25 +210,41 @@ func (pd *PublicDashboardServiceImpl) Save(ctx context.Context, u *user.SignedIn
|
||||
existingPubdash, err := pd.store.Find(ctx, dto.PublicDashboard.Uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if existingPubdash == nil {
|
||||
return nil, ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
// save changes
|
||||
var pubdashUid string
|
||||
if existingPubdash == nil {
|
||||
err = validation.ValidateSavePublicDashboard(dto, dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubdashUid, err = pd.savePublicDashboard(ctx, dto)
|
||||
} else {
|
||||
pubdashUid, err = pd.updatePublicDashboard(ctx, dto)
|
||||
}
|
||||
// validate dashboard
|
||||
err = validation.ValidatePublicDashboard(dto, dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get latest public dashboard to return
|
||||
newPubdash, err := pd.store.Find(ctx, pubdashUid)
|
||||
// set values to update
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: existingPubdash.Uid,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
AnnotationsEnabled: dto.PublicDashboard.AnnotationsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
UpdatedBy: dto.UserId,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
// persist
|
||||
affectedRows, err := pd.store.Update(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 404 if not found
|
||||
if affectedRows == 0 {
|
||||
return nil, ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
// get latest public dashboard to return
|
||||
newPubdash, err := pd.store.Find(ctx, existingPubdash.Uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -203,58 +286,6 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context.
|
||||
return "", ErrPublicDashboardFailedGenerateAccessToken
|
||||
}
|
||||
|
||||
// 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 *SavePublicDashboardDTO) (string, error) {
|
||||
uid, err := pd.NewPublicDashboardUid(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
accessToken, err := pd.NewPublicDashboardAccessToken(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: uid,
|
||||
DashboardUid: dto.DashboardUid,
|
||||
OrgId: dto.OrgId,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
AnnotationsEnabled: dto.PublicDashboard.AnnotationsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
CreatedBy: dto.UserId,
|
||||
CreatedAt: time.Now(),
|
||||
AccessToken: accessToken,
|
||||
},
|
||||
}
|
||||
|
||||
err = pd.store.Save(ctx, cmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// 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 *SavePublicDashboardDTO) (string, error) {
|
||||
cmd := SavePublicDashboardCommand{
|
||||
PublicDashboard: PublicDashboard{
|
||||
Uid: dto.PublicDashboard.Uid,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
AnnotationsEnabled: dto.PublicDashboard.AnnotationsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
UpdatedBy: dto.UserId,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
return dto.PublicDashboard.Uid, pd.store.Update(ctx, cmd)
|
||||
}
|
||||
|
||||
// FindAll Returns a list of public dashboards by orgId
|
||||
func (pd *PublicDashboardServiceImpl) FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error) {
|
||||
publicDashboards, err := pd.store.FindAll(ctx, orgId)
|
||||
|
@ -120,8 +120,10 @@ func TestGetPublicDashboard(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSavePublicDashboard(t *testing.T) {
|
||||
t.Run("Saving public dashboard", func(t *testing.T) {
|
||||
// We're using sqlite here because testing all of the behaviors with mocks in
|
||||
// the correct order is convoluted.
|
||||
func TestCreatePublicDashboard(t *testing.T) {
|
||||
t.Run("Create public dashboard", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
@ -145,7 +147,7 @@ func TestSavePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
@ -189,7 +191,7 @@ func TestSavePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
@ -220,11 +222,11 @@ func TestSavePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Pubdash access token generation throws an error and pubdash is not persisted", func(t *testing.T) {
|
||||
t.Run("Throws an error when pubdash with generated access token already exists", func(t *testing.T) {
|
||||
dashboard := models.NewDashboard("testDashie")
|
||||
pubdash := &PublicDashboard{
|
||||
IsEnabled: true,
|
||||
@ -238,7 +240,6 @@ func TestSavePublicDashboard(t *testing.T) {
|
||||
publicDashboardStore.On("FindDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
||||
publicDashboardStore.On("Find", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
publicDashboardStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(pubdash, nil)
|
||||
publicDashboardStore.On("NewPublicDashboardUid", mock.Anything).Return("an-uid", nil)
|
||||
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
@ -256,11 +257,59 @@ func TestSavePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Equal(t, err, ErrPublicDashboardFailedGenerateAccessToken)
|
||||
publicDashboardStore.AssertNotCalled(t, "Save")
|
||||
publicDashboardStore.AssertNotCalled(t, "Create")
|
||||
})
|
||||
|
||||
t.Run("Returns error if public dashboard exists", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
service := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: publicdashboardStore,
|
||||
}
|
||||
|
||||
dto := &SavePublicDashboardDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &PublicDashboard{
|
||||
AnnotationsEnabled: false,
|
||||
IsEnabled: true,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to overwrite settings
|
||||
dto = &SavePublicDashboardDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 8,
|
||||
PublicDashboard: &PublicDashboard{
|
||||
Uid: savedPubdash.Uid,
|
||||
OrgId: 9,
|
||||
DashboardUid: "abc1234",
|
||||
CreatedBy: 9,
|
||||
CreatedAt: time.Time{},
|
||||
|
||||
IsEnabled: true,
|
||||
AnnotationsEnabled: true,
|
||||
TimeSettings: timeSettings,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = service.Create(context.Background(), SignedInUser, dto)
|
||||
assert.Equal(t, ErrPublicDashboardBadRequest, err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -287,7 +336,8 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
savedPubdash, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
// insert initial pubdash
|
||||
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to overwrite settings
|
||||
@ -308,10 +358,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
}
|
||||
|
||||
// Since the dto.PublicDashboard has a uid, this will call
|
||||
// service.updatePublicDashboard
|
||||
updatedPubdash, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
// don't get updated
|
||||
@ -350,9 +397,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// Since the dto.PublicDashboard has a uid, this will call
|
||||
// service.updatePublicDashboard
|
||||
savedPubdash, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to overwrite settings
|
||||
@ -372,7 +417,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updatedPubdash, err := service.Save(context.Background(), SignedInUser, dto)
|
||||
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, &TimeSettings{}, updatedPubdash.TimeSettings)
|
||||
@ -422,81 +467,6 @@ func TestDeletePublicDashboard(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardStore, title string, orgId int64,
|
||||
folderId int64, isFolder bool, templateVars []map[string]interface{}, customPanels []interface{}, tags ...interface{}) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
var dashboardPanels []interface{}
|
||||
if customPanels != nil {
|
||||
dashboardPanels = customPanels
|
||||
} else {
|
||||
dashboardPanels = []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"refId": "A",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"refId": "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds3",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds3",
|
||||
},
|
||||
"refId": "C",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
"panels": dashboardPanels,
|
||||
"templating": map[string]interface{}{
|
||||
"list": templateVars,
|
||||
},
|
||||
"time": map[string]interface{}{
|
||||
"from": "2022-09-01T00:00:00.000Z",
|
||||
"to": "2022-09-01T12:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
}
|
||||
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dash)
|
||||
dash.Data.Set("id", dash.Id)
|
||||
dash.Data.Set("uid", dash.Uid)
|
||||
return dash
|
||||
}
|
||||
|
||||
func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T) {
|
||||
type args struct {
|
||||
reqDTO PublicDashboardQueryDTO
|
||||
@ -596,36 +566,6 @@ func TestDashboardEnabledChanged(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func CreateDatasource(dsType string, uid string) struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
} {
|
||||
return struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
}{
|
||||
Type: &dsType,
|
||||
Uid: &uid,
|
||||
}
|
||||
}
|
||||
|
||||
func AddAnnotationsToDashboard(t *testing.T, dash *models.Dashboard, annotations []DashAnnotation) *models.Dashboard {
|
||||
type annotationsDto struct {
|
||||
List []DashAnnotation `json:"list"`
|
||||
}
|
||||
annos := annotationsDto{}
|
||||
annos.List = annotations
|
||||
annoJSON, err := json.Marshal(annos)
|
||||
require.NoError(t, err)
|
||||
|
||||
dashAnnos, err := simplejson.NewJson(annoJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
dash.Data.Set("annotations", dashAnnos)
|
||||
|
||||
return dash
|
||||
}
|
||||
|
||||
func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -962,3 +902,108 @@ func TestPublicDashboardServiceImpl_NewPublicDashboardAccessToken(t *testing.T)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func CreateDatasource(dsType string, uid string) struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
} {
|
||||
return struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
}{
|
||||
Type: &dsType,
|
||||
Uid: &uid,
|
||||
}
|
||||
}
|
||||
|
||||
func AddAnnotationsToDashboard(t *testing.T, dash *models.Dashboard, annotations []DashAnnotation) *models.Dashboard {
|
||||
type annotationsDto struct {
|
||||
List []DashAnnotation `json:"list"`
|
||||
}
|
||||
annos := annotationsDto{}
|
||||
annos.List = annotations
|
||||
annoJSON, err := json.Marshal(annos)
|
||||
require.NoError(t, err)
|
||||
|
||||
dashAnnos, err := simplejson.NewJson(annoJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
dash.Data.Set("annotations", dashAnnos)
|
||||
|
||||
return dash
|
||||
}
|
||||
|
||||
func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardStore, title string, orgId int64,
|
||||
folderId int64, isFolder bool, templateVars []map[string]interface{}, customPanels []interface{}, tags ...interface{}) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
var dashboardPanels []interface{}
|
||||
if customPanels != nil {
|
||||
dashboardPanels = customPanels
|
||||
} else {
|
||||
dashboardPanels = []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"refId": "A",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"refId": "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds3",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds3",
|
||||
},
|
||||
"refId": "C",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
"panels": dashboardPanels,
|
||||
"templating": map[string]interface{}{
|
||||
"list": templateVars,
|
||||
},
|
||||
"time": map[string]interface{}{
|
||||
"from": "2022-09-01T00:00:00.000Z",
|
||||
"to": "2022-09-01T12:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
}
|
||||
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dash)
|
||||
dash.Data.Set("id", dash.Id)
|
||||
dash.Data.Set("uid", dash.Uid)
|
||||
return dash
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
)
|
||||
|
||||
func ValidateSavePublicDashboard(dto *SavePublicDashboardDTO, dashboard *models.Dashboard) error {
|
||||
func ValidatePublicDashboard(dto *SavePublicDashboardDTO, dashboard *models.Dashboard) error {
|
||||
if hasTemplateVariables(dashboard) {
|
||||
return ErrPublicDashboardHasTemplateVariables
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidateSavePublicDashboard(t *testing.T) {
|
||||
func TestValidatePublicDashboard(t *testing.T) {
|
||||
t.Run("Returns validation error when dashboard has template variables", func(t *testing.T) {
|
||||
templateVars := []byte(`{
|
||||
"templating": {
|
||||
@ -24,7 +24,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
|
||||
dashboard := models.NewDashboardFromJson(dashboardData)
|
||||
dto := &SavePublicDashboardDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
|
||||
|
||||
err := ValidateSavePublicDashboard(dto, dashboard)
|
||||
err := ValidatePublicDashboard(dto, dashboard)
|
||||
require.ErrorContains(t, err, ErrPublicDashboardHasTemplateVariables.Reason)
|
||||
})
|
||||
|
||||
@ -38,7 +38,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
|
||||
dashboard := models.NewDashboardFromJson(dashboardData)
|
||||
dto := &SavePublicDashboardDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
|
||||
|
||||
err := ValidateSavePublicDashboard(dto, dashboard)
|
||||
err := ValidatePublicDashboard(dto, dashboard)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ const getConfigError = (err: { status: number }) => ({ error: err.status !== 404
|
||||
export const publicDashboardApi = createApi({
|
||||
reducerPath: 'publicDashboardApi',
|
||||
baseQuery: retry(backendSrvBaseQuery({ baseUrl: '/api/dashboards' }), { maxRetries: 0 }),
|
||||
tagTypes: ['Config', 'PublicDashboards'],
|
||||
tagTypes: ['PublicDashboard', 'AuditTablePublicDashboard'],
|
||||
keepUnusedDataFor: 0,
|
||||
endpoints: (builder) => ({
|
||||
getConfig: builder.query<PublicDashboard, string>({
|
||||
getPublicDashboard: builder.query<PublicDashboard, string>({
|
||||
query: (dashboardUid) => ({
|
||||
url: `/uid/${dashboardUid}/public-dashboards`,
|
||||
manageError: getConfigError,
|
||||
@ -53,9 +53,9 @@ export const publicDashboardApi = createApi({
|
||||
dispatch(notifyApp(createErrorNotification(customError?.error?.data?.message)));
|
||||
}
|
||||
},
|
||||
providesTags: ['Config'],
|
||||
providesTags: ['PublicDashboard'],
|
||||
}),
|
||||
saveConfig: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||
createPublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||
query: (params) => ({
|
||||
url: `/uid/${params.dashboard.uid}/public-dashboards`,
|
||||
method: 'POST',
|
||||
@ -63,21 +63,42 @@ export const publicDashboardApi = createApi({
|
||||
}),
|
||||
async onQueryStarted({ dashboard, payload }, { dispatch, queryFulfilled }) {
|
||||
const { data } = await queryFulfilled;
|
||||
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
||||
dispatch(notifyApp(createSuccessNotification('Public dashboard created!')));
|
||||
|
||||
// Update runtime meta flag
|
||||
dashboard.updateMeta({
|
||||
hasPublicDashboard: true,
|
||||
publicDashboardUid: data.uid,
|
||||
publicDashboardEnabled: data.isEnabled,
|
||||
});
|
||||
},
|
||||
invalidatesTags: ['Config'],
|
||||
invalidatesTags: ['PublicDashboard'],
|
||||
}),
|
||||
updatePublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||
query: (params) => ({
|
||||
url: `/uid/${params.dashboard.uid}/public-dashboards/${params.payload.uid}`,
|
||||
method: 'PUT',
|
||||
data: params.payload,
|
||||
}),
|
||||
extraOptions: { maxRetries: 0 },
|
||||
async onQueryStarted({ dashboard, payload }, { dispatch, queryFulfilled }) {
|
||||
const { data } = await queryFulfilled;
|
||||
dispatch(notifyApp(createSuccessNotification('Public dashboard updated!')));
|
||||
|
||||
// Update runtime meta flag
|
||||
dashboard.updateMeta({
|
||||
hasPublicDashboard: true,
|
||||
publicDashboardUid: data.uid,
|
||||
publicDashboardEnabled: data.isEnabled,
|
||||
});
|
||||
},
|
||||
invalidatesTags: ['PublicDashboard'],
|
||||
}),
|
||||
listPublicDashboards: builder.query<ListPublicDashboardResponse[], void>({
|
||||
query: () => ({
|
||||
url: '/public-dashboards',
|
||||
}),
|
||||
providesTags: ['PublicDashboards'],
|
||||
providesTags: ['AuditTablePublicDashboard'],
|
||||
}),
|
||||
deletePublicDashboard: builder.mutation<void, { dashboardTitle: string; dashboardUid: string; uid: string }>({
|
||||
query: (params) => ({
|
||||
@ -97,14 +118,15 @@ export const publicDashboardApi = createApi({
|
||||
)
|
||||
);
|
||||
},
|
||||
invalidatesTags: ['PublicDashboards'],
|
||||
invalidatesTags: ['AuditTablePublicDashboard'],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetConfigQuery,
|
||||
useSaveConfigMutation,
|
||||
useGetPublicDashboardQuery,
|
||||
useCreatePublicDashboardMutation,
|
||||
useUpdatePublicDashboardMutation,
|
||||
useDeletePublicDashboardMutation,
|
||||
useListPublicDashboardsQuery,
|
||||
} = publicDashboardApi;
|
||||
|
@ -17,20 +17,7 @@ import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { ShareModal } from '../ShareModal';
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
isEnabled: false,
|
||||
annotationsEnabled: false,
|
||||
uid: undefined,
|
||||
dashboardUid: undefined,
|
||||
accessToken: 'an-access-token',
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
const server = setupServer();
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||
@ -147,6 +134,7 @@ describe('SharePublic', () => {
|
||||
expect(screen.getByText('2022-08-30 00:00:00 to 2022-09-04 01:59:59')).toBeInTheDocument();
|
||||
});
|
||||
it('when modal is opened, then loader spinner appears and inputs are disabled', async () => {
|
||||
mockDashboard.meta.hasPublicDashboard = true;
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
|
||||
expect(await screen.findByTestId('Spinner')).toBeInTheDocument();
|
||||
@ -158,6 +146,7 @@ describe('SharePublic', () => {
|
||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||
});
|
||||
it('when fetch errors happen, then all inputs remain disabled', async () => {
|
||||
mockDashboard.meta.hasPublicDashboard = true;
|
||||
server.use(
|
||||
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
||||
return res(ctx.status(500));
|
||||
@ -165,7 +154,7 @@ describe('SharePublic', () => {
|
||||
);
|
||||
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'), { timeout: 7000 });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
|
||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
||||
@ -178,13 +167,16 @@ describe('SharePublic', () => {
|
||||
});
|
||||
|
||||
describe('SharePublic - New config setup', () => {
|
||||
beforeEach(() => {
|
||||
mockDashboard.meta.hasPublicDashboard = false;
|
||||
});
|
||||
it('when modal is opened, then save button is disabled', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||
});
|
||||
it('when fetch is done, then loader spinner is gone, inputs are enabled and save button is disabled', async () => {
|
||||
it('when fetch is done, then no loader spinner appears, inputs are enabled and save button is disabled', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeEnabled();
|
||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeEnabled();
|
||||
@ -196,7 +188,7 @@ describe('SharePublic - New config setup', () => {
|
||||
});
|
||||
it('when checkboxes are filled, then save button remains disabled', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId(selectors.WillBePublicCheckbox));
|
||||
fireEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
|
||||
@ -206,7 +198,7 @@ describe('SharePublic - New config setup', () => {
|
||||
});
|
||||
it('when checkboxes and switch are filled, then save button is enabled', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId(selectors.WillBePublicCheckbox));
|
||||
fireEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
|
||||
@ -219,6 +211,7 @@ describe('SharePublic - New config setup', () => {
|
||||
|
||||
describe('SharePublic - Already persisted', () => {
|
||||
beforeEach(() => {
|
||||
mockDashboard.meta.hasPublicDashboard = true;
|
||||
server.use(
|
||||
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
||||
return res(
|
||||
|
@ -6,7 +6,11 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||
import { reportInteraction } from '@grafana/runtime/src';
|
||||
import { Alert, Button, ClipboardButton, Field, HorizontalGroup, Input, useStyles2, Spinner } from '@grafana/ui/src';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { useGetConfigQuery, useSaveConfigMutation } from 'app/features/dashboard/api/publicDashboardApi';
|
||||
import {
|
||||
useGetPublicDashboardQuery,
|
||||
useCreatePublicDashboardMutation,
|
||||
useUpdatePublicDashboardMutation,
|
||||
} from 'app/features/dashboard/api/publicDashboardApi';
|
||||
import { AcknowledgeCheckboxes } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/AcknowledgeCheckboxes';
|
||||
import { Configuration } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Configuration';
|
||||
import { Description } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/Description';
|
||||
@ -27,13 +31,19 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const [hasPublicDashboard, setHasPublicDashboard] = useState(props.dashboard.meta.hasPublicDashboard);
|
||||
|
||||
const {
|
||||
isLoading: isFetchingLoading,
|
||||
data: publicDashboard,
|
||||
isError: isFetchingError,
|
||||
} = useGetConfigQuery(props.dashboard.uid);
|
||||
} = useGetPublicDashboardQuery(props.dashboard.uid, {
|
||||
// if we don't have a public dashboard, don't try to load public dashboard
|
||||
skip: !hasPublicDashboard,
|
||||
});
|
||||
|
||||
const [saveConfig, { isLoading: isSaveLoading }] = useSaveConfigMutation();
|
||||
const [createPublicDashboard, { isLoading: isSaveLoading }] = useCreatePublicDashboardMutation();
|
||||
const [updatePublicDashboard, { isLoading: isUpdateLoading }] = useUpdatePublicDashboardMutation();
|
||||
|
||||
const [acknowledgements, setAcknowledgements] = useState<Acknowledgements>({
|
||||
public: false,
|
||||
@ -63,7 +73,7 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
|
||||
}, [publicDashboard]);
|
||||
|
||||
const isLoading = isFetchingLoading || isSaveLoading;
|
||||
const isLoading = isFetchingLoading || isSaveLoading || isUpdateLoading;
|
||||
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
|
||||
const acknowledged = acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
|
||||
const isSaveEnabled = useMemo(
|
||||
@ -77,13 +87,23 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
[hasWritePermissions, acknowledged, props.dashboard, isLoading, isFetchingError, enabledSwitch, publicDashboard]
|
||||
);
|
||||
|
||||
const onSavePublicConfig = () => {
|
||||
const onSavePublicConfig = async () => {
|
||||
reportInteraction('grafana_dashboards_public_create_clicked');
|
||||
|
||||
saveConfig({
|
||||
const req = {
|
||||
dashboard: props.dashboard,
|
||||
payload: { ...publicDashboard!, isEnabled: enabledSwitch.isEnabled, annotationsEnabled },
|
||||
});
|
||||
};
|
||||
|
||||
// create or update based on whether we have existing uid
|
||||
|
||||
if (hasPublicDashboard) {
|
||||
await updatePublicDashboard(req).unwrap();
|
||||
setHasPublicDashboard(true);
|
||||
} else {
|
||||
await createPublicDashboard(req).unwrap();
|
||||
setHasPublicDashboard(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onAcknowledge = (field: string, checked: boolean) => {
|
||||
|
@ -44,6 +44,7 @@ export interface DashboardMeta {
|
||||
publicDashboardAccessToken?: string;
|
||||
publicDashboardUid?: string;
|
||||
publicDashboardEnabled?: boolean;
|
||||
hasPublicDashboard?: boolean;
|
||||
dashboardNotFound?: boolean;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user