Provisioning: Show file path of provisioning file in save/delete dialogs (#16706)

* Add file path to metadata and show it in dialogs

* Make path relative to config directory

* Fix tests

* Add test for the relative path

* Refactor to use path relative to provisioner path

* Change return types

* Rename attribute

* Small fixes from review
This commit is contained in:
Andrej Ocenas
2019-04-30 13:32:18 +02:00
committed by GitHub
parent 76ab0aa059
commit eb82a75668
23 changed files with 285 additions and 134 deletions
+2 -2
View File
@@ -283,10 +283,10 @@ func (hs *HTTPServer) registerRoutes() {
// Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) {
dashboardRoute.Get("/uid/:uid", Wrap(GetDashboard))
dashboardRoute.Get("/uid/:uid", Wrap(hs.GetDashboard))
dashboardRoute.Delete("/uid/:uid", Wrap(DeleteDashboardByUID))
dashboardRoute.Get("/db/:slug", Wrap(GetDashboard))
dashboardRoute.Get("/db/:slug", Wrap(hs.GetDashboard))
dashboardRoute.Delete("/db/:slug", Wrap(DeleteDashboardBySlug))
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), Wrap(CalculateDashboardDiff))
+13 -4
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -47,7 +48,7 @@ func dashboardGuardianResponse(err error) Response {
return Error(403, "Access denied to this dashboard", nil)
}
func GetDashboard(c *m.ReqContext) Response {
func (hs *HTTPServer) GetDashboard(c *m.ReqContext) Response {
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, c.Params(":uid"))
if rsp != nil {
return rsp
@@ -106,14 +107,22 @@ func GetDashboard(c *m.ReqContext) Response {
meta.FolderUrl = query.Result.GetUrl()
}
isDashboardProvisioned := &m.IsDashboardProvisionedQuery{DashboardId: dash.Id}
err = bus.Dispatch(isDashboardProvisioned)
provisioningData, err := dashboards.NewProvisioningService().GetProvisionedDashboardDataByDashboardId(dash.Id)
if err != nil {
return Error(500, "Error while checking if dashboard is provisioned", err)
}
if isDashboardProvisioned.Result {
if provisioningData != nil {
meta.Provisioned = true
meta.ProvisionedExternalId, err = filepath.Rel(
hs.ProvisioningService.GetDashboardProvisionerResolvedPath(provisioningData.Name),
provisioningData.ExternalId,
)
if err != nil {
// Not sure when this could happen so not sure how to better handle this. Right now ProvisionedExternalId
// is for better UX, showing in Save/Delete dialogs and so it won't break anything if it is empty.
hs.log.Warn("Failed to create ProvisionedExternalId", "err", err)
}
}
// make sure db version is in sync with json model version
+50 -19
View File
@@ -11,6 +11,7 @@ import (
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
@@ -43,8 +44,8 @@ func TestDashboardApiEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
query.Result = false
bus.AddHandler("test", func(query *m.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
@@ -198,8 +199,8 @@ func TestDashboardApiEndpoint(t *testing.T) {
fakeDash.HasAcl = true
setting.ViewersCanEdit = false
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
query.Result = false
bus.AddHandler("test", func(query *m.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
@@ -235,6 +236,10 @@ func TestDashboardApiEndpoint(t *testing.T) {
return nil
})
hs := &HTTPServer{
Cfg: setting.NewCfg(),
}
// This tests six scenarios:
// 1. user is an org viewer AND has no permissions for this dashboard
// 2. user is an org editor AND has no permissions for this dashboard
@@ -247,7 +252,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
role := m.ROLE_VIEWER
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should lookup dashboard by slug", func() {
@@ -260,7 +265,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should lookup dashboard by uid", func() {
@@ -305,7 +310,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
role := m.ROLE_EDITOR
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should lookup dashboard by slug", func() {
@@ -318,7 +323,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should lookup dashboard by uid", func() {
@@ -636,8 +641,8 @@ func TestDashboardApiEndpoint(t *testing.T) {
dashTwo.FolderId = 3
dashTwo.HasAcl = false
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
query.Result = false
bus.AddHandler("test", func(query *m.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
@@ -766,8 +771,8 @@ func TestDashboardApiEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
query.Result = false
bus.AddHandler("test", func(query *m.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
@@ -905,12 +910,12 @@ func TestDashboardApiEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = &m.Dashboard{Id: 1}
query.Result = &m.Dashboard{Id: 1, Data: &simplejson.Json{}}
return nil
})
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
query.Result = true
bus.AddHandler("test", func(query *m.GetProvisionedDashboardDataByIdQuery) error {
query.Result = &m.DashboardProvisioning{ExternalId: "/tmp/grafana/dashboards/test/dashboard1.json"}
return nil
})
@@ -940,11 +945,32 @@ func TestDashboardApiEndpoint(t *testing.T) {
So(result.Get("error").MustString(), ShouldEqual, m.ErrDashboardCannotDeleteProvisionedDashboard.Error())
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", m.ROLE_EDITOR, func(sc *scenarioContext) {
mock := provisioning.NewProvisioningServiceMock()
mock.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
}
dash := GetDashboardShouldReturn200WithConfig(sc, mock)
Convey("Should return relative path to provisioning file", func() {
So(dash.Meta.ProvisionedExternalId, ShouldEqual, "test/dashboard1.json")
})
})
})
}
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
CallGetDashboard(sc)
func GetDashboardShouldReturn200WithConfig(sc *scenarioContext, provisioningService ProvisioningService) dtos.DashboardFullWithMeta {
if provisioningService == nil {
provisioningService = provisioning.NewProvisioningServiceMock()
}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: provisioningService,
}
CallGetDashboard(sc, hs)
So(sc.resp.Code, ShouldEqual, 200)
@@ -955,8 +981,13 @@ func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta
return dash
}
func CallGetDashboard(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
return GetDashboardShouldReturn200WithConfig(sc, nil)
}
func CallGetDashboard(sc *scenarioContext, hs *HTTPServer) {
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
+23 -22
View File
@@ -7,28 +7,29 @@ import (
)
type DashboardMeta struct {
IsStarred bool `json:"isStarred,omitempty"`
IsHome bool `json:"isHome,omitempty"`
IsSnapshot bool `json:"isSnapshot,omitempty"`
Type string `json:"type,omitempty"`
CanSave bool `json:"canSave"`
CanEdit bool `json:"canEdit"`
CanAdmin bool `json:"canAdmin"`
CanStar bool `json:"canStar"`
Slug string `json:"slug"`
Url string `json:"url"`
Expires time.Time `json:"expires"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
UpdatedBy string `json:"updatedBy"`
CreatedBy string `json:"createdBy"`
Version int `json:"version"`
HasAcl bool `json:"hasAcl"`
IsFolder bool `json:"isFolder"`
FolderId int64 `json:"folderId"`
FolderTitle string `json:"folderTitle"`
FolderUrl string `json:"folderUrl"`
Provisioned bool `json:"provisioned"`
IsStarred bool `json:"isStarred,omitempty"`
IsHome bool `json:"isHome,omitempty"`
IsSnapshot bool `json:"isSnapshot,omitempty"`
Type string `json:"type,omitempty"`
CanSave bool `json:"canSave"`
CanEdit bool `json:"canEdit"`
CanAdmin bool `json:"canAdmin"`
CanStar bool `json:"canStar"`
Slug string `json:"slug"`
Url string `json:"url"`
Expires time.Time `json:"expires"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
UpdatedBy string `json:"updatedBy"`
CreatedBy string `json:"createdBy"`
Version int `json:"version"`
HasAcl bool `json:"hasAcl"`
IsFolder bool `json:"isFolder"`
FolderId int64 `json:"folderId"`
FolderTitle string `json:"folderTitle"`
FolderUrl string `json:"folderUrl"`
Provisioned bool `json:"provisioned"`
ProvisionedExternalId string `json:"provisionedExternalId"`
}
type DashboardFullWithMeta struct {
+19 -13
View File
@@ -25,13 +25,12 @@ import (
"github.com/grafana/grafana/pkg/services/cache"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
macaron "gopkg.in/macaron.v1"
"gopkg.in/macaron.v1"
)
func init() {
@@ -42,6 +41,13 @@ func init() {
})
}
type ProvisioningService interface {
ProvisionDatasources() error
ProvisionNotifications() error
ProvisionDashboards() error
GetDashboardProvisionerResolvedPath(name string) string
}
type HTTPServer struct {
log log.Logger
macaron *macaron.Macaron
@@ -49,17 +55,17 @@ type HTTPServer struct {
streamManager *live.StreamManager
httpSrv *http.Server
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *cache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
ProvisioningService provisioning.ProvisioningService `inject:""`
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *cache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
ProvisioningService ProvisioningService `inject:""`
}
func (hs *HTTPServer) Init() error {