Commit a45ce365 by Jon Gyllenswärd Committed by GitHub

Allow saving of provisioned dashboards (#19820)

Allows saving of provisioned dashboards if the config value allowUiUpdates is set to true

Fixes #11778
parent 782eda3e
...@@ -5,6 +5,7 @@ providers: ...@@ -5,6 +5,7 @@ providers:
folder: 'gdev dashboards' folder: 'gdev dashboards'
folderUid: '' folderUid: ''
type: file type: file
allowUiUpdates: false
updateIntervalSeconds: 60 updateIntervalSeconds: 60
options: options:
path: devenv/dev-dashboards path: devenv/dev-dashboards
...@@ -227,6 +227,8 @@ providers: ...@@ -227,6 +227,8 @@ providers:
editable: true editable: true
# <int> how often Grafana will scan for changed dashboards # <int> how often Grafana will scan for changed dashboards
updateIntervalSeconds: 10 updateIntervalSeconds: 10
# <bool> allow updating provisioned dashboards from the UI
allowUiUpdates: false
options: options:
# <string, required> path to dashboard files on disk. Required # <string, required> path to dashboard files on disk. Required
path: /var/lib/grafana/dashboards path: /var/lib/grafana/dashboards
...@@ -235,13 +237,20 @@ providers: ...@@ -235,13 +237,20 @@ providers:
When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path every **updateIntervalSeconds** and look for updated json files and update/insert those into the database. When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path every **updateIntervalSeconds** and look for updated json files and update/insert those into the database.
#### Making changes to a provisioned dashboard #### Making changes to a provisioned dashboard
It's possible to make changes to a provisioned dashboard in the Grafana UI. However, it is not possible to automatically save the changes back to the provisioning source.
If `allowUiUpdates` is set to `true` and you make changes to a provisioned dashboard, you can `Save` the dashboard then changes will be persisted to the Grafana database.
It's possible to make changes to a provisioned dashboard in Grafana UI, but there's currently no possibility to automatically save the changes back to the provisioning source. > **Note.**
However, if you make changes to a provisioned dashboard you can `Save` the dashboard which will bring up a *Cannot save provisioned dashboard* dialog like seen in the screenshot below. > If a provisioned dashboard is saved from the UI and then later updated from the source, the dashboard stored in the database will always be overwritten. The `version` property in the JSON file will not affect this, even if it is lower than the existing dashboard.
Here available options will let you `Copy JSON to Clipboard` and/or `Save JSON to file` which can help you synchronize your dashboard changes back to the provisioning source. >
> If a provisioned dashboard is saved from the UI and the source is removed, the dashboard stored in the database will be deleted unless the configuration option `disableDeletion` is set to true.
Note: The JSON shown in input field and when using `Copy JSON to Clipboard` and/or `Save JSON to file` will have the `id` field automatically removed to aid the provisioning workflow. If `allowUiUpdates` is configured to `false`, you are not able to make changes to a provisioned dashboard. When you click `Save`, Grafana brings up a *Cannot save provisioned dashboard* dialog. The screenshot below illustrates this behavior.
Grafana offers options to export the JSON definition of a dashboard. Either `Copy JSON to Clipboard` or `Save JSON to file` can help you synchronize your dashboard changes back to the provisioning source.
Note: The JSON definition in the input field when using `Copy JSON to Clipboard` or `Save JSON to file` will have the `id` field automatically removed to aid the provisioning workflow.
{{< docs-imagebox img="/img/docs/v51/provisioning_cannot_save_dashboard.png" max-width="500px" class="docs-image--no-shadow" >}} {{< docs-imagebox img="/img/docs/v51/provisioning_cannot_save_dashboard.png" max-width="500px" class="docs-image--no-shadow" >}}
### Reusable Dashboard Urls ### Reusable Dashboard Urls
......
...@@ -113,7 +113,11 @@ func (hs *HTTPServer) GetDashboard(c *m.ReqContext) Response { ...@@ -113,7 +113,11 @@ func (hs *HTTPServer) GetDashboard(c *m.ReqContext) Response {
} }
if provisioningData != nil { if provisioningData != nil {
meta.Provisioned = true allowUiUpdate := hs.ProvisioningService.GetAllowUiUpdatesFromConfig(provisioningData.Name)
if !allowUiUpdate {
meta.Provisioned = true
}
meta.ProvisionedExternalId, err = filepath.Rel( meta.ProvisionedExternalId, err = filepath.Rel(
hs.ProvisioningService.GetDashboardProvisionerResolvedPath(provisioningData.Name), hs.ProvisioningService.GetDashboardProvisionerResolvedPath(provisioningData.Name),
provisioningData.ExternalId, provisioningData.ExternalId,
...@@ -221,6 +225,13 @@ func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) ...@@ -221,6 +225,13 @@ func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand)
} }
} }
provisioningData, err := dashboards.NewProvisioningService().GetProvisionedDashboardDataByDashboardId(dash.Id)
if err != nil {
return Error(500, "Error while checking if dashboard is provisioned", err)
}
allowUiUpdate := hs.ProvisioningService.GetAllowUiUpdatesFromConfig(provisioningData.Name)
dashItem := &dashboards.SaveDashboardDTO{ dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash, Dashboard: dash,
Message: cmd.Message, Message: cmd.Message,
...@@ -229,7 +240,7 @@ func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) ...@@ -229,7 +240,7 @@ func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand)
Overwrite: cmd.Overwrite, Overwrite: cmd.Overwrite,
} }
dashboard, err := dashboards.NewService().SaveDashboard(dashItem) dashboard, err := dashboards.NewService().SaveDashboard(dashItem, allowUiUpdate)
if err == m.ErrDashboardTitleEmpty || if err == m.ErrDashboardTitleEmpty ||
err == m.ErrDashboardWithSameNameAsFolder || err == m.ErrDashboardWithSameNameAsFolder ||
......
...@@ -958,6 +958,32 @@ func TestDashboardApiEndpoint(t *testing.T) { ...@@ -958,6 +958,32 @@ func TestDashboardApiEndpoint(t *testing.T) {
So(dash.Meta.ProvisionedExternalId, ShouldEqual, "test/dashboard1.json") So(dash.Meta.ProvisionedExternalId, ShouldEqual, "test/dashboard1.json")
}) })
}) })
loggedInUserScenarioWithRole("When allowUiUpdates is true and 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"
}
mock.GetAllowUiUpdatesFromConfigFunc = func(name string) bool {
return true
}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: mock,
}
CallGetDashboard(sc, hs)
So(sc.resp.Code, ShouldEqual, 200)
dash := dtos.DashboardFullWithMeta{}
err := json.NewDecoder(sc.resp.Body).Decode(&dash)
So(err, ShouldBeNil)
Convey("Should have metadata that says Provisioned is false", func() {
So(dash.Meta.Provisioned, ShouldEqual, false)
})
})
}) })
} }
...@@ -1043,13 +1069,18 @@ func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) { ...@@ -1043,13 +1069,18 @@ func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 200) So(sc.resp.Code, ShouldEqual, 200)
} }
func (m mockDashboardProvisioningService) DeleteProvisionedDashboard(dashboardId int64, orgId int64) error {
panic("implement me")
}
func postDashboardScenario(desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd m.SaveDashboardCommand, fn scenarioFunc) { func postDashboardScenario(desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd m.SaveDashboardCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() { Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
hs := HTTPServer{ hs := HTTPServer{
Bus: bus.GetBus(), Bus: bus.GetBus(),
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
ProvisioningService: provisioning.NewProvisioningServiceMock(),
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(url)
...@@ -1063,10 +1094,16 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d ...@@ -1063,10 +1094,16 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d
origNewDashboardService := dashboards.NewService origNewDashboardService := dashboards.NewService
dashboards.MockDashboardService(mock) dashboards.MockDashboardService(mock)
origProvisioningService := dashboards.NewProvisioningService
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
sc.m.Post(routePattern, sc.defaultHandler) sc.m.Post(routePattern, sc.defaultHandler)
defer func() { defer func() {
dashboards.NewService = origNewDashboardService dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService
}() }()
fn(sc) fn(sc)
...@@ -1100,8 +1137,9 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin ...@@ -1100,8 +1137,9 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
hs := HTTPServer{ hs := HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
Bus: bus.GetBus(), Bus: bus.GetBus(),
ProvisioningService: provisioning.NewProvisioningServiceMock(),
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(url)
...@@ -1116,6 +1154,11 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin ...@@ -1116,6 +1154,11 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin
return hs.RestoreDashboardVersion(c, cmd) return hs.RestoreDashboardVersion(c, cmd)
}) })
origProvisioningService := dashboards.NewProvisioningService
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
origNewDashboardService := dashboards.NewService origNewDashboardService := dashboards.NewService
dashboards.MockDashboardService(mock) dashboards.MockDashboardService(mock)
...@@ -1123,6 +1166,7 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin ...@@ -1123,6 +1166,7 @@ func restoreDashboardVersionScenario(desc string, url string, routePattern strin
defer func() { defer func() {
dashboards.NewService = origNewDashboardService dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService
}() }()
fn(sc) fn(sc)
...@@ -1135,3 +1179,26 @@ func (sc *scenarioContext) ToJSON() *simplejson.Json { ...@@ -1135,3 +1179,26 @@ func (sc *scenarioContext) ToJSON() *simplejson.Json {
So(err, ShouldBeNil) So(err, ShouldBeNil)
return result return result
} }
type mockDashboardProvisioningService struct {
}
func (m mockDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning, allowUiUpdates bool) (*m.Dashboard, error) {
panic("implement me")
}
func (m mockDashboardProvisioningService) SaveFolderForProvisionedDashboards(*dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
panic("implement me")
}
func (m mockDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
panic("implement me")
}
func (mock mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*m.DashboardProvisioning, error) {
return &m.DashboardProvisioning{}, nil
}
func (m mockDashboardProvisioningService) UnprovisionDashboard(dashboardId int64) error {
panic("implement me")
}
...@@ -47,6 +47,7 @@ type ProvisioningService interface { ...@@ -47,6 +47,7 @@ type ProvisioningService interface {
ProvisionNotifications() error ProvisionNotifications() error
ProvisionDashboards() error ProvisionDashboards() error
GetDashboardProvisionerResolvedPath(name string) string GetDashboardProvisionerResolvedPath(name string) string
GetAllowUiUpdatesFromConfig(name string) bool
} }
type HTTPServer struct { type HTTPServer struct {
......
...@@ -14,14 +14,14 @@ import ( ...@@ -14,14 +14,14 @@ import (
// DashboardService service for operating on dashboards // DashboardService service for operating on dashboards
type DashboardService interface { type DashboardService interface {
SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
DeleteDashboard(dashboardId int64, orgId int64) error DeleteDashboard(dashboardId int64, orgId int64) error
} }
// DashboardProvisioningService service for operating on provisioned dashboards // DashboardProvisioningService service for operating on provisioned dashboards
type DashboardProvisioningService interface { type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning, allowUiUpdates bool) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error) SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error)
...@@ -182,14 +182,14 @@ func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, ...@@ -182,14 +182,14 @@ func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand,
return bus.Dispatch(&alertCmd) return bus.Dispatch(&alertCmd)
} }
func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) { func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning, allowUiUpdates bool) (*models.Dashboard, error) {
dto.User = &models.SignedInUser{ dto.User = &models.SignedInUser{
UserId: 0, UserId: 0,
OrgRole: models.ROLE_ADMIN, OrgRole: models.ROLE_ADMIN,
OrgId: dto.OrgId, OrgId: dto.OrgId,
} }
cmd, err := dr.buildSaveDashboardCommand(dto, true, false) cmd, err := dr.buildSaveDashboardCommand(dto, true, !allowUiUpdates)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -237,8 +237,9 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash ...@@ -237,8 +237,9 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
return cmd.Result, nil return cmd.Result, nil
} }
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) { func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
cmd, err := dr.buildSaveDashboardCommand(dto, true, true)
cmd, err := dr.buildSaveDashboardCommand(dto, true, !allowUiUpdate)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -309,7 +310,7 @@ type FakeDashboardService struct { ...@@ -309,7 +310,7 @@ type FakeDashboardService struct {
SavedDashboards []*SaveDashboardDTO SavedDashboards []*SaveDashboardDTO
} }
func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) { func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
s.SavedDashboards = append(s.SavedDashboards, dto) s.SavedDashboards = append(s.SavedDashboards, dto)
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil { if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
...@@ -320,7 +321,7 @@ func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Das ...@@ -320,7 +321,7 @@ func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Das
} }
func (s *FakeDashboardService) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) { func (s *FakeDashboardService) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
return s.SaveDashboard(dto) return s.SaveDashboard(dto, true)
} }
func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) error { func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) error {
......
...@@ -27,7 +27,7 @@ func TestDashboardService(t *testing.T) { ...@@ -27,7 +27,7 @@ func TestDashboardService(t *testing.T) {
for _, title := range titles { for _, title := range titles {
dto.Dashboard = models.NewDashboard(title) dto.Dashboard = models.NewDashboard(title)
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto, false)
So(err, ShouldEqual, models.ErrDashboardTitleEmpty) So(err, ShouldEqual, models.ErrDashboardTitleEmpty)
} }
}) })
...@@ -35,13 +35,13 @@ func TestDashboardService(t *testing.T) { ...@@ -35,13 +35,13 @@ func TestDashboardService(t *testing.T) {
Convey("Should return validation error if it's a folder and have a folder id", func() { Convey("Should return validation error if it's a folder and have a folder id", func() {
dto.Dashboard = models.NewDashboardFolder("Folder") dto.Dashboard = models.NewDashboardFolder("Folder")
dto.Dashboard.FolderId = 1 dto.Dashboard.FolderId = 1
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto, false)
So(err, ShouldEqual, models.ErrDashboardFolderCannotHaveParent) So(err, ShouldEqual, models.ErrDashboardFolderCannotHaveParent)
}) })
Convey("Should return validation error if folder is named General", func() { Convey("Should return validation error if folder is named General", func() {
dto.Dashboard = models.NewDashboardFolder("General") dto.Dashboard = models.NewDashboardFolder("General")
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto, false)
So(err, ShouldEqual, models.ErrDashboardFolderNameExists) So(err, ShouldEqual, models.ErrDashboardFolderNameExists)
}) })
...@@ -103,11 +103,36 @@ func TestDashboardService(t *testing.T) { ...@@ -103,11 +103,36 @@ func TestDashboardService(t *testing.T) {
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto, false)
So(provisioningValidated, ShouldBeTrue) So(provisioningValidated, ShouldBeTrue)
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard) So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
}) })
Convey("Should not return validation error if dashboard is provisioned but UI updates allowed", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil
})
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto, true)
So(provisioningValidated, ShouldBeFalse)
So(err, ShouldNotBeNil)
})
Convey("Should return validation error if alert data is invalid", func() { Convey("Should return validation error if alert data is invalid", func() {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error { bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil cmd.Result = nil
...@@ -119,7 +144,7 @@ func TestDashboardService(t *testing.T) { ...@@ -119,7 +144,7 @@ func TestDashboardService(t *testing.T) {
}) })
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto, false)
So(err.Error(), ShouldEqual, "Alert validation error") So(err.Error(), ShouldEqual, "Alert validation error")
}) })
}) })
...@@ -155,7 +180,7 @@ func TestDashboardService(t *testing.T) { ...@@ -155,7 +180,7 @@ func TestDashboardService(t *testing.T) {
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveProvisionedDashboard(dto, nil) _, err := service.SaveProvisionedDashboard(dto, nil, true)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(provisioningValidated, ShouldBeFalse) So(provisioningValidated, ShouldBeFalse)
}) })
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
type DashboardProvisionerImpl struct { type DashboardProvisionerImpl struct {
log log.Logger log log.Logger
fileReaders []*fileReader fileReaders []*fileReader
configs []*DashboardsAsConfig
} }
func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerImpl, error) { func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerImpl, error) {
...@@ -32,6 +33,7 @@ func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerI ...@@ -32,6 +33,7 @@ func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerI
d := &DashboardProvisionerImpl{ d := &DashboardProvisionerImpl{
log: logger, log: logger,
fileReaders: fileReaders, fileReaders: fileReaders,
configs: configs,
} }
return d, nil return d, nil
...@@ -72,6 +74,15 @@ func (provider *DashboardProvisionerImpl) GetProvisionerResolvedPath(name string ...@@ -72,6 +74,15 @@ func (provider *DashboardProvisionerImpl) GetProvisionerResolvedPath(name string
return "" return ""
} }
func (provider *DashboardProvisionerImpl) GetAllowUiUpdatesFromConfig(name string) bool {
for _, config := range provider.configs {
if config.Name == name {
return config.AllowUiUpdates
}
}
return false
}
func getFileReaders(configs []*DashboardsAsConfig, logger log.Logger) ([]*fileReader, error) { func getFileReaders(configs []*DashboardsAsConfig, logger log.Logger) ([]*fileReader, error) {
var readers []*fileReader var readers []*fileReader
......
...@@ -3,16 +3,18 @@ package dashboards ...@@ -3,16 +3,18 @@ package dashboards
import "context" import "context"
type Calls struct { type Calls struct {
Provision []interface{} Provision []interface{}
PollChanges []interface{} PollChanges []interface{}
GetProvisionerResolvedPath []interface{} GetProvisionerResolvedPath []interface{}
GetAllowUiUpdatesFromConfig []interface{}
} }
type DashboardProvisionerMock struct { type DashboardProvisionerMock struct {
Calls *Calls Calls *Calls
ProvisionFunc func() error ProvisionFunc func() error
PollChangesFunc func(ctx context.Context) PollChangesFunc func(ctx context.Context)
GetProvisionerResolvedPathFunc func(name string) string GetProvisionerResolvedPathFunc func(name string) string
GetAllowUiUpdatesFromConfigFunc func(name string) bool
} }
func NewDashboardProvisionerMock() *DashboardProvisionerMock { func NewDashboardProvisionerMock() *DashboardProvisionerMock {
...@@ -37,9 +39,17 @@ func (dpm *DashboardProvisionerMock) PollChanges(ctx context.Context) { ...@@ -37,9 +39,17 @@ func (dpm *DashboardProvisionerMock) PollChanges(ctx context.Context) {
} }
func (dpm *DashboardProvisionerMock) GetProvisionerResolvedPath(name string) string { func (dpm *DashboardProvisionerMock) GetProvisionerResolvedPath(name string) string {
dpm.Calls.PollChanges = append(dpm.Calls.GetProvisionerResolvedPath, name) dpm.Calls.GetProvisionerResolvedPath = append(dpm.Calls.GetProvisionerResolvedPath, name)
if dpm.GetProvisionerResolvedPathFunc != nil { if dpm.GetProvisionerResolvedPathFunc != nil {
return dpm.GetProvisionerResolvedPathFunc(name) return dpm.GetProvisionerResolvedPathFunc(name)
} }
return "" return ""
} }
func (dpm *DashboardProvisionerMock) GetAllowUiUpdatesFromConfig(name string) bool {
dpm.Calls.GetAllowUiUpdatesFromConfig = append(dpm.Calls.GetAllowUiUpdatesFromConfig, name)
if dpm.GetAllowUiUpdatesFromConfigFunc != nil {
return dpm.GetAllowUiUpdatesFromConfigFunc(name)
}
return false
}
...@@ -190,7 +190,7 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil ...@@ -190,7 +190,7 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
CheckSum: jsonFile.checkSum, CheckSum: jsonFile.checkSum,
} }
_, err = fr.dashboardProvisioningService.SaveProvisionedDashboard(dash, dp) _, err = fr.dashboardProvisioningService.SaveProvisionedDashboard(dash, dp, fr.Cfg.AllowUiUpdates)
return provisioningMetadata, err return provisioningMetadata, err
} }
......
...@@ -368,7 +368,7 @@ func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name stri ...@@ -368,7 +368,7 @@ func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name stri
return s.provisioned[name], nil return s.provisioned[name], nil
} }
func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) { func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning, allowUiUpdates bool) (*models.Dashboard, error) {
// Copy the structs as we need to change them but do not want to alter outside world. // Copy the structs as we need to change them but do not want to alter outside world.
var copyProvisioning = &models.DashboardProvisioning{} var copyProvisioning = &models.DashboardProvisioning{}
*copyProvisioning = *provisioning *copyProvisioning = *provisioning
......
...@@ -20,6 +20,7 @@ type DashboardsAsConfig struct { ...@@ -20,6 +20,7 @@ type DashboardsAsConfig struct {
Options map[string]interface{} Options map[string]interface{}
DisableDeletion bool DisableDeletion bool
UpdateIntervalSeconds int64 UpdateIntervalSeconds int64
AllowUiUpdates bool
} }
type DashboardsAsConfigV0 struct { type DashboardsAsConfigV0 struct {
...@@ -32,6 +33,7 @@ type DashboardsAsConfigV0 struct { ...@@ -32,6 +33,7 @@ type DashboardsAsConfigV0 struct {
Options map[string]interface{} `json:"options" yaml:"options"` Options map[string]interface{} `json:"options" yaml:"options"`
DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"` DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"`
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"` UpdateIntervalSeconds int64 `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"`
AllowUiUpdates bool `json:"allowUiUpdates" yaml:"allowUiUpdates"`
} }
type ConfigVersion struct { type ConfigVersion struct {
...@@ -52,6 +54,7 @@ type DashboardProviderConfigs struct { ...@@ -52,6 +54,7 @@ type DashboardProviderConfigs struct {
Options values.JSONValue `json:"options" yaml:"options"` Options values.JSONValue `json:"options" yaml:"options"`
DisableDeletion values.BoolValue `json:"disableDeletion" yaml:"disableDeletion"` DisableDeletion values.BoolValue `json:"disableDeletion" yaml:"disableDeletion"`
UpdateIntervalSeconds values.Int64Value `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"` UpdateIntervalSeconds values.Int64Value `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"`
AllowUiUpdates values.BoolValue `json:"allowUiUpdates" yaml:"allowUiUpdates"`
} }
func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) { func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) {
...@@ -84,6 +87,7 @@ func mapV0ToDashboardAsConfig(v0 []*DashboardsAsConfigV0) []*DashboardsAsConfig ...@@ -84,6 +87,7 @@ func mapV0ToDashboardAsConfig(v0 []*DashboardsAsConfigV0) []*DashboardsAsConfig
Options: v.Options, Options: v.Options,
DisableDeletion: v.DisableDeletion, DisableDeletion: v.DisableDeletion,
UpdateIntervalSeconds: v.UpdateIntervalSeconds, UpdateIntervalSeconds: v.UpdateIntervalSeconds,
AllowUiUpdates: v.AllowUiUpdates,
}) })
} }
...@@ -104,6 +108,7 @@ func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig { ...@@ -104,6 +108,7 @@ func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig {
Options: v.Options.Value(), Options: v.Options.Value(),
DisableDeletion: v.DisableDeletion.Value(), DisableDeletion: v.DisableDeletion.Value(),
UpdateIntervalSeconds: v.UpdateIntervalSeconds.Value(), UpdateIntervalSeconds: v.UpdateIntervalSeconds.Value(),
AllowUiUpdates: v.AllowUiUpdates.Value(),
}) })
} }
......
...@@ -19,6 +19,7 @@ type DashboardProvisioner interface { ...@@ -19,6 +19,7 @@ type DashboardProvisioner interface {
Provision() error Provision() error
PollChanges(ctx context.Context) PollChanges(ctx context.Context)
GetProvisionerResolvedPath(name string) string GetProvisionerResolvedPath(name string) string
GetAllowUiUpdatesFromConfig(name string) bool
} }
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error) type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
...@@ -137,6 +138,10 @@ func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name stri ...@@ -137,6 +138,10 @@ func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name stri
return ps.dashboardProvisioner.GetProvisionerResolvedPath(name) return ps.dashboardProvisioner.GetProvisionerResolvedPath(name)
} }
func (ps *provisioningServiceImpl) GetAllowUiUpdatesFromConfig(name string) bool {
return ps.dashboardProvisioner.GetAllowUiUpdatesFromConfig(name)
}
func (ps *provisioningServiceImpl) cancelPolling() { func (ps *provisioningServiceImpl) cancelPolling() {
if ps.pollingCtxCancel != nil { if ps.pollingCtxCancel != nil {
ps.log.Debug("Stop polling for dashboard changes") ps.log.Debug("Stop polling for dashboard changes")
......
...@@ -5,6 +5,7 @@ type Calls struct { ...@@ -5,6 +5,7 @@ type Calls struct {
ProvisionNotifications []interface{} ProvisionNotifications []interface{}
ProvisionDashboards []interface{} ProvisionDashboards []interface{}
GetDashboardProvisionerResolvedPath []interface{} GetDashboardProvisionerResolvedPath []interface{}
GetAllowUiUpdatesFromConfig []interface{}
} }
type ProvisioningServiceMock struct { type ProvisioningServiceMock struct {
...@@ -13,6 +14,7 @@ type ProvisioningServiceMock struct { ...@@ -13,6 +14,7 @@ type ProvisioningServiceMock struct {
ProvisionNotificationsFunc func() error ProvisionNotificationsFunc func() error
ProvisionDashboardsFunc func() error ProvisionDashboardsFunc func() error
GetDashboardProvisionerResolvedPathFunc func(name string) string GetDashboardProvisionerResolvedPathFunc func(name string) string
GetAllowUiUpdatesFromConfigFunc func(name string) bool
} }
func NewProvisioningServiceMock() *ProvisioningServiceMock { func NewProvisioningServiceMock() *ProvisioningServiceMock {
...@@ -52,3 +54,11 @@ func (mock *ProvisioningServiceMock) GetDashboardProvisionerResolvedPath(name st ...@@ -52,3 +54,11 @@ func (mock *ProvisioningServiceMock) GetDashboardProvisionerResolvedPath(name st
} }
return "" return ""
} }
func (mock *ProvisioningServiceMock) GetAllowUiUpdatesFromConfig(name string) bool {
mock.Calls.GetAllowUiUpdatesFromConfig = append(mock.Calls.GetAllowUiUpdatesFromConfig, name)
if mock.GetAllowUiUpdatesFromConfigFunc != nil {
return mock.GetAllowUiUpdatesFromConfigFunc(name)
}
return false
}
...@@ -964,13 +964,13 @@ func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenari ...@@ -964,13 +964,13 @@ func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenari
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard { func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
dto := toSaveDashboardDto(cmd) dto := toSaveDashboardDto(cmd)
res, _ := dashboards.NewService().SaveDashboard(&dto) res, _ := dashboards.NewService().SaveDashboard(&dto, false)
return res return res
} }
func callSaveWithError(cmd models.SaveDashboardCommand) error { func callSaveWithError(cmd models.SaveDashboardCommand) error {
dto := toSaveDashboardDto(cmd) dto := toSaveDashboardDto(cmd)
_, err := dashboards.NewService().SaveDashboard(&dto) _, err := dashboards.NewService().SaveDashboard(&dto, false)
return err return err
} }
...@@ -994,7 +994,7 @@ func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashbo ...@@ -994,7 +994,7 @@ func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashbo
}, },
} }
res, err := dashboards.NewService().SaveDashboard(&dto) res, err := dashboards.NewService().SaveDashboard(&dto, false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
return res return res
...@@ -1020,7 +1020,7 @@ func saveTestFolder(title string, orgId int64) *models.Dashboard { ...@@ -1020,7 +1020,7 @@ func saveTestFolder(title string, orgId int64) *models.Dashboard {
}, },
} }
res, err := dashboards.NewService().SaveDashboard(&dto) res, err := dashboards.NewService().SaveDashboard(&dto, false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
return res return res
......
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