Provisioning: Do not allow deletion of provisioned dashboards (#16211)

* Unprovision dashboard in case of DisableDeletion = true

* Rename command struct

* Handle removed provision files

* Allow html in confirm-modal

* Do not show confirm button without onConfirm

* Show dialog on deleting provisioned dashboard

* Changed DeleteDashboard to DeleteProvisionedDashboard

* Remove unreachable return

* Add provisioned checks to API

* Remove filter func

* Fix and add tests for deleting dashboards

* Change delete confirm text

* Added and used pkg/errors for error wrapping
This commit is contained in:
Andrej Ocenas
2019-04-10 13:29:10 +02:00
committed by GitHub
parent 9f007137b3
commit 2d7fc55df7
17 changed files with 581 additions and 94 deletions
@@ -9,12 +9,14 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util"
"github.com/pkg/errors"
)
// DashboardService service for operating on dashboards
type DashboardService interface {
SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
DeleteDashboard(dashboardId int64, orgId int64) error
}
// DashboardProvisioningService service for operating on provisioned dashboards
@@ -22,6 +24,8 @@ type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardId int64) error
DeleteProvisionedDashboard(dashboardId int64, orgId int64) error
}
// NewService factory for creating a new dashboard service
@@ -241,6 +245,33 @@ func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Da
return cmd.Result, nil
}
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
// operations by the user where we want to make sure user does not delete provisioned dashboard.
func (dr *dashboardServiceImpl) DeleteDashboard(dashboardId int64, orgId int64) error {
return dr.deleteDashboard(dashboardId, orgId, true)
}
// DeleteProvisionedDashboard removes dashboard from the DB even if it is provisioned.
func (dr *dashboardServiceImpl) DeleteProvisionedDashboard(dashboardId int64, orgId int64) error {
return dr.deleteDashboard(dashboardId, orgId, false)
}
func (dr *dashboardServiceImpl) deleteDashboard(dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
if validateProvisionedDashboard {
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dashboardId}
err := bus.Dispatch(isDashboardProvisioned)
if err != nil {
return errors.Wrap(err, "error while checking if dashboard is provisioned")
}
if isDashboardProvisioned.Result {
return models.ErrDashboardCannotDeleteProvisionedDashboard
}
}
cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId}
return bus.Dispatch(cmd)
}
func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
cmd, err := dr.buildSaveDashboardCommand(dto, false, true)
if err != nil {
@@ -255,6 +286,13 @@ func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.
return cmd.Result, nil
}
// UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
// and provisioned dashboards are left behind but not deleted.
func (dr *dashboardServiceImpl) UnprovisionDashboard(dashboardId int64) error {
cmd := &models.UnprovisionDashboardCommand{Id: dashboardId}
return bus.Dispatch(cmd)
}
type FakeDashboardService struct {
SaveDashboardResult *models.Dashboard
SaveDashboardError error
@@ -275,6 +313,16 @@ func (s *FakeDashboardService) ImportDashboard(dto *SaveDashboardDTO) (*models.D
return s.SaveDashboard(dto)
}
func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) error {
for index, dash := range s.SavedDashboards {
if dash.Dashboard.Id == dashboardId && dash.OrgId == orgId {
s.SavedDashboards = append(s.SavedDashboards[:index], s.SavedDashboards[index+1:]...)
break
}
}
return nil
}
func MockDashboardService(mock *FakeDashboardService) {
NewService = func() DashboardService {
return mock
@@ -1,13 +1,12 @@
package dashboards
import (
"errors"
"testing"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/pkg/errors"
. "github.com/smartystreets/goconvey/convey"
)
@@ -200,8 +199,61 @@ func TestDashboardService(t *testing.T) {
})
})
Convey("Given provisioned dashboard", func() {
result := setupDeleteHandlers(true)
Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1)
So(err, ShouldBeNil)
So(result.deleteWasCalled, ShouldBeTrue)
})
Convey("DeleteDashboard should fail to delete it", func() {
err := service.DeleteDashboard(1, 1)
So(err, ShouldEqual, models.ErrDashboardCannotDeleteProvisionedDashboard)
So(result.deleteWasCalled, ShouldBeFalse)
})
})
Convey("Given non provisioned dashboard", func() {
result := setupDeleteHandlers(false)
Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1)
So(err, ShouldBeNil)
So(result.deleteWasCalled, ShouldBeTrue)
})
Convey("DeleteDashboard should delete it", func() {
err := service.DeleteDashboard(1, 1)
So(err, ShouldBeNil)
So(result.deleteWasCalled, ShouldBeTrue)
})
})
Reset(func() {
guardian.New = origNewDashboardGuardian
})
})
}
type Result struct {
deleteWasCalled bool
}
func setupDeleteHandlers(provisioned bool) *Result {
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = provisioned
return nil
})
result := &Result{}
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
So(cmd.Id, ShouldEqual, 1)
So(cmd.OrgId, ShouldEqual, 1)
result.deleteWasCalled = true
return nil
})
return result
}