Commit eb82a756 by Andrej Ocenas Committed by GitHub

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
parent 76ab0aa0
......@@ -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))
......
......@@ -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
......
......@@ -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()
}
......
......@@ -29,6 +29,7 @@ type DashboardMeta struct {
FolderTitle string `json:"folderTitle"`
FolderUrl string `json:"folderUrl"`
Provisioned bool `json:"provisioned"`
ProvisionedExternalId string `json:"provisionedExternalId"`
}
type DashboardFullWithMeta struct {
......
......@@ -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
......@@ -59,7 +65,7 @@ type HTTPServer struct {
AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
ProvisioningService provisioning.ProvisioningService `inject:""`
ProvisioningService ProvisioningService `inject:""`
}
func (hs *HTTPServer) Init() error {
......
......@@ -323,15 +323,13 @@ type GetDashboardSlugByIdQuery struct {
Result string
}
type IsDashboardProvisionedQuery struct {
type GetProvisionedDashboardDataByIdQuery struct {
DashboardId int64
Result bool
Result *DashboardProvisioning
}
type GetProvisionedDashboardDataQuery struct {
Name string
Result []*DashboardProvisioning
}
......
......@@ -24,6 +24,7 @@ type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardId int64) error
DeleteProvisionedDashboard(dashboardId int64, orgId int64) error
}
......@@ -37,7 +38,9 @@ var NewService = func() DashboardService {
// NewProvisioningService factory for creating a new dashboard provisioning service
var NewProvisioningService = func() DashboardProvisioningService {
return &dashboardServiceImpl{}
return &dashboardServiceImpl{
log: log.New("dashboard-provisioning-service"),
}
}
type SaveDashboardDTO struct {
......@@ -65,6 +68,16 @@ func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*mod
return cmd.Result, nil
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashboardId}
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}
return cmd.Result, nil
}
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard
......@@ -123,14 +136,12 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
}
if validateProvisionedDashboard {
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dash.Id}
err := bus.Dispatch(isDashboardProvisioned)
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dash.Id)
if err != nil {
return nil, err
}
if isDashboardProvisioned.Result {
if provisionedData != nil {
return nil, models.ErrDashboardCannotSaveProvisionedDashboard
}
}
......@@ -258,13 +269,12 @@ func (dr *dashboardServiceImpl) DeleteProvisionedDashboard(dashboardId int64, or
func (dr *dashboardServiceImpl) deleteDashboard(dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
if validateProvisionedDashboard {
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dashboardId}
err := bus.Dispatch(isDashboardProvisioned)
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dashboardId)
if err != nil {
return errutil.Wrap("failed to check if dashboard is provisioned", err)
}
if isDashboardProvisioned.Result {
if provisionedData != nil {
return models.ErrDashboardCannotDeleteProvisionedDashboard
}
}
......
......@@ -55,8 +55,8 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
......@@ -85,9 +85,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
......@@ -109,8 +109,8 @@ func TestDashboardService(t *testing.T) {
})
Convey("Should return validation error if alert data is invalid", func() {
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
......@@ -129,9 +129,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should not return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
......@@ -166,9 +166,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
......@@ -241,8 +241,12 @@ type Result struct {
}
func setupDeleteHandlers(provisioned bool) *Result {
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = provisioned
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
if provisioned {
cmd.Result = &models.DashboardProvisioning{}
} else {
cmd.Result = nil
}
return nil
})
......
......@@ -112,8 +112,9 @@ func TestFolderService(t *testing.T) {
provisioningValidated := false
bus.AddHandler("test", func(query *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
query.Result = nil
return nil
})
......
......@@ -7,18 +7,11 @@ import (
"github.com/pkg/errors"
)
type DashboardProvisioner interface {
Provision() error
PollChanges(ctx context.Context)
}
type DashboardProvisionerImpl struct {
log log.Logger
fileReaders []*fileReader
}
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerImpl, error) {
logger := log.New("provisioning.dashboard")
cfgReader := &configReader{path: configDirectory, log: logger}
......@@ -61,6 +54,17 @@ func (provider *DashboardProvisionerImpl) PollChanges(ctx context.Context) {
}
}
// GetProvisionerResolvedPath returns resolved path for the specified provisioner name. Can be used to generate
// relative path to provisioning file from it's external_id.
func (provider *DashboardProvisionerImpl) GetProvisionerResolvedPath(name string) string {
for _, reader := range provider.fileReaders {
if reader.Cfg.Name == name {
return reader.resolvedPath()
}
}
return ""
}
func getFileReaders(configs []*DashboardsAsConfig, logger log.Logger) ([]*fileReader, error) {
var readers []*fileReader
......
......@@ -5,12 +5,14 @@ import "context"
type Calls struct {
Provision []interface{}
PollChanges []interface{}
GetProvisionerResolvedPath []interface{}
}
type DashboardProvisionerMock struct {
Calls *Calls
ProvisionFunc func() error
PollChangesFunc func(ctx context.Context)
GetProvisionerResolvedPathFunc func(name string) string
}
func NewDashboardProvisionerMock() *DashboardProvisionerMock {
......@@ -34,3 +36,12 @@ func (dpm *DashboardProvisionerMock) PollChanges(ctx context.Context) {
dpm.PollChangesFunc(ctx)
}
}
func (dpm *DashboardProvisionerMock) GetProvisionerResolvedPath(name string) string {
dpm.Calls.PollChanges = append(dpm.Calls.GetProvisionerResolvedPath, name)
if dpm.GetProvisionerResolvedPathFunc != nil {
return dpm.GetProvisionerResolvedPathFunc(name)
} else {
return ""
}
}
......@@ -70,7 +70,7 @@ func (fr *fileReader) pollChanges(ctx context.Context) {
// to the database.
func (fr *fileReader) startWalkingDisk() error {
fr.log.Debug("Start walking disk", "path", fr.Path)
resolvedPath := fr.resolvePath(fr.Path)
resolvedPath := fr.resolvedPath()
if _, err := os.Stat(resolvedPath); err != nil {
if os.IsNotExist(err) {
return err
......@@ -329,24 +329,23 @@ func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time,
}, nil
}
func (fr *fileReader) resolvePath(path string) string {
if _, err := os.Stat(path); os.IsNotExist(err) {
func (fr *fileReader) resolvedPath() string {
if _, err := os.Stat(fr.Path); os.IsNotExist(err) {
fr.log.Error("Cannot read directory", "error", err)
}
copy := path
path, err := filepath.Abs(path)
path, err := filepath.Abs(fr.Path)
if err != nil {
fr.log.Error("Could not create absolute path", "path", copy, "error", err)
fr.log.Error("Could not create absolute path", "path", fr.Path, "error", err)
}
path, err = filepath.EvalSymlinks(path)
if err != nil {
fr.log.Error("Failed to read content of symlinked path", "path", copy, "error", err)
fr.log.Error("Failed to read content of symlinked path", "path", fr.Path, "error", err)
}
if path == "" {
path = copy
path = fr.Path
fr.log.Info("falling back to original path due to EvalSymlink/Abs failure")
}
return path
......
......@@ -33,7 +33,7 @@ func TestProvsionedSymlinkedFolder(t *testing.T) {
t.Errorf("expected err to be nil")
}
resolvedPath := reader.resolvePath(reader.Path)
resolvedPath := reader.resolvedPath()
if resolvedPath != want {
t.Errorf("got %s want %s", resolvedPath, want)
}
......
......@@ -70,7 +70,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
So(err, ShouldBeNil)
resolvedPath := reader.resolvePath(reader.Path)
resolvedPath := reader.resolvedPath()
So(filepath.IsAbs(resolvedPath), ShouldBeTrue)
})
})
......@@ -435,6 +435,10 @@ func (s *fakeDashboardProvisioningService) DeleteProvisionedDashboard(dashboardI
return nil
}
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) {
return nil, nil
}
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
for _, d := range fakeService.getDashboard {
if d.Slug == cmd.Slug {
......
......@@ -15,9 +15,17 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
type DashboardProvisioner interface {
Provision() error
PollChanges(ctx context.Context)
GetProvisionerResolvedPath(name string) string
}
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
func init() {
registry.RegisterService(NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
func(path string) (DashboardProvisioner, error) {
return dashboards.NewDashboardProvisionerImpl(path)
},
notifiers.Provision,
......@@ -25,14 +33,8 @@ func init() {
))
}
type ProvisioningService interface {
ProvisionDatasources() error
ProvisionNotifications() error
ProvisionDashboards() error
}
func NewProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
newDashboardProvisioner DashboardProvisionerFactory,
provisionNotifiers func(string) error,
provisionDatasources func(string) error,
) *provisioningServiceImpl {
......@@ -48,8 +50,8 @@ type provisioningServiceImpl struct {
Cfg *setting.Cfg `inject:""`
log log.Logger
pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory
dashboardProvisioner dashboards.DashboardProvisioner
newDashboardProvisioner DashboardProvisionerFactory
dashboardProvisioner DashboardProvisioner
provisionNotifiers func(string) error
provisionDatasources func(string) error
mutex sync.Mutex
......@@ -131,6 +133,10 @@ func (ps *provisioningServiceImpl) ProvisionDashboards() error {
return nil
}
func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string {
return ps.dashboardProvisioner.GetProvisionerResolvedPath(name)
}
func (ps *provisioningServiceImpl) cancelPolling() {
if ps.pollingCtxCancel != nil {
ps.log.Debug("Stop polling for dashboard changes")
......
package provisioning
type Calls struct {
ProvisionDatasources []interface{}
ProvisionNotifications []interface{}
ProvisionDashboards []interface{}
GetDashboardProvisionerResolvedPath []interface{}
}
type ProvisioningServiceMock struct {
Calls *Calls
ProvisionDatasourcesFunc func() error
ProvisionNotificationsFunc func() error
ProvisionDashboardsFunc func() error
GetDashboardProvisionerResolvedPathFunc func(name string) string
}
func NewProvisioningServiceMock() *ProvisioningServiceMock {
return &ProvisioningServiceMock{
Calls: &Calls{},
}
}
func (mock *ProvisioningServiceMock) ProvisionDatasources() error {
mock.Calls.ProvisionDatasources = append(mock.Calls.ProvisionDatasources, nil)
if mock.ProvisionDatasourcesFunc != nil {
return mock.ProvisionDatasourcesFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) ProvisionNotifications() error {
mock.Calls.ProvisionNotifications = append(mock.Calls.ProvisionNotifications, nil)
if mock.ProvisionNotificationsFunc != nil {
return mock.ProvisionNotificationsFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) ProvisionDashboards() error {
mock.Calls.ProvisionDashboards = append(mock.Calls.ProvisionDashboards, nil)
if mock.ProvisionDashboardsFunc != nil {
return mock.ProvisionDashboardsFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) GetDashboardProvisionerResolvedPath(name string) string {
mock.Calls.GetDashboardProvisionerResolvedPath = append(mock.Calls.GetDashboardProvisionerResolvedPath, name)
if mock.GetDashboardProvisionerResolvedPathFunc != nil {
return mock.GetDashboardProvisionerResolvedPathFunc(name)
} else {
return ""
}
}
......@@ -92,7 +92,7 @@ func setup() *serviceTestStruct {
}
serviceTest.service = NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
func(path string) (DashboardProvisioner, error) {
return serviceTest.mock, nil
},
nil,
......
......@@ -19,16 +19,16 @@ type DashboardExtras struct {
Value string
}
func GetProvisionedDataByDashboardId(cmd *models.IsDashboardProvisionedQuery) error {
func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
result := &models.DashboardProvisioning{}
exist, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
if err != nil {
return err
}
cmd.Result = exist
if exist {
cmd.Result = result
}
return nil
}
......
......@@ -65,20 +65,20 @@ func TestDashboardProvisioningTest(t *testing.T) {
})
Convey("Can query for one provisioned dashboard", func() {
query := &models.IsDashboardProvisionedQuery{DashboardId: cmd.Result.Id}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeTrue)
So(query.Result, ShouldNotBeNil)
})
Convey("Can query for none provisioned dashboard", func() {
query := &models.IsDashboardProvisionedQuery{DashboardId: 3000}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: 3000}
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
Convey("Deleting folder should delete provision meta data", func() {
......@@ -89,11 +89,11 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(DeleteDashboard(deleteCmd), ShouldBeNil)
query := &models.IsDashboardProvisionedQuery{DashboardId: cmd.Result.Id}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
Convey("UnprovisionDashboard should delete provisioning metadata", func() {
......@@ -103,11 +103,11 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(UnprovisionDashboard(unprovisionCmd), ShouldBeNil)
query := &models.IsDashboardProvisionedQuery{DashboardId: dashId}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashId}
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
})
})
......
......@@ -27,8 +27,8 @@ func TestIntegratedDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
......
......@@ -192,6 +192,8 @@ export class SettingsCtrl {
text2: `
<i>See <a class="external-link" href="http://docs.grafana.org/administration/provisioning/#dashboards" target="_blank">
documentation</a> for more information about provisioning.</i>
</br>
File path: ${this.dashboard.meta.provisionedExternalId}
`,
text2htmlBind: true,
icon: 'fa-trash',
......
import angular from 'angular';
import { saveAs } from 'file-saver';
import coreModule from 'app/core/core_module';
import { DashboardModel } from '../../state';
const template = `
<div class="modal-body">
......@@ -21,6 +22,9 @@ const template = `
<i>See <a class="external-link" href="http://docs.grafana.org/administration/provisioning/#dashboards" target="_blank">
documentation</a> for more information about provisioning.</i>
</small>
<div class="p-t-1">
File path: {{ctrl.dashboardModel.meta.provisionedExternalId}}
</div>
<div class="p-t-2">
<div class="gf-form">
<code-editor content="ctrl.dashboardJson" data-mode="json" data-max-lines=15></code-editor>
......@@ -41,12 +45,14 @@ const template = `
export class SaveProvisionedDashboardModalCtrl {
dash: any;
dashboardModel: DashboardModel;
dashboardJson: string;
dismiss: () => void;
/** @ngInject */
constructor(dashboardSrv) {
this.dash = dashboardSrv.getCurrent().getSaveModelClone();
this.dashboardModel = dashboardSrv.getCurrent();
this.dash = this.dashboardModel.getSaveModelClone();
delete this.dash.id;
this.dashboardJson = angular.toJson(this.dash, true);
}
......
......@@ -26,6 +26,7 @@ export interface DashboardMeta {
canMakeEditable?: boolean;
submenuEnabled?: boolean;
provisioned?: boolean;
provisionedExternalId?: string;
focusPanelId?: number;
isStarred?: boolean;
showSettings?: boolean;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment