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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user