mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Shouldn't be able to overwrite a dashboard if you don't have permissions (#10900)
* dashboards: new command for validating dashboard before update Removes validation logic from saveDashboard and later on use the new command for validating dashboard before saving a dashboard. This due to the fact that we need to validate permissions for overwriting other dashboards by uid and title. * dashboards: use the new command for validating dashboard before saving Had to refactor dashboard provisioning a bit to be able to sidetrack the permission validation in a somewhat reasonable way. Adds some initial tests of the dashboard repository, but needs to be extended later. At least now you can mock the dashboard guardian * dashboards: removes validation logic in the save dashboard api layer Use the dashboard repository solely for create/update dashboards and let it do all the validation. One exception regarding quota validation which still is in api layer since that logic is in a macaron middleware. Need to move out-commented api tests later. * dashboards: fix database tests for validate and saving dashboards * dashboards: rename dashboard repository to dashboard service Split the old dashboard repository interface in two new interfaces, IDashboardService and IDashboardProvisioningService. Makes it more explicit when using it from the provisioning package and there's no possibility of calling an incorrect method for saving a dashboard. * database: make the InitTestDB function available to use from other packages * dashboards: rename ValidateDashboardForUpdateCommand and some refactoring * dashboards: integration tests of dashboard service * dashboard: fix sqlstore test due to folder exist validation * dashboards: move dashboard service integration tests to sqlstore package Had to move it to the sqlstore package due to concurrency problems when running against mysql and postgres. Using InitTestDB from two packages added conflicts when clearing and running migrations on the test database * dashboards: refactor how to find id to be used for save permission check * dashboards: remove duplicated dashboard tests * dashboards: cleanup dashboard service integration tests * dashboards: handle save dashboard errors and return correct http status * fix: remove log statement * dashboards: import dashboard should use dashboard service Had to move alerting commands to models package due to problems with import cycles of packages. * dashboards: cleanup dashboard api tests and add some tests for post dashboard * dashboards: rename dashboard service interfaces * dashboards: rename dashboard guardian interface
This commit is contained in:
committed by
Torkel Ödegaard
parent
63f8854a48
commit
53cd39fde5
@@ -5,24 +5,12 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type UpdateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *m.Dashboard
|
||||
}
|
||||
|
||||
type ValidateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *m.Dashboard
|
||||
}
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("alerting", updateDashboardAlerts)
|
||||
bus.AddHandler("alerting", validateDashboardAlerts)
|
||||
}
|
||||
|
||||
func validateDashboardAlerts(cmd *ValidateDashboardAlertsCommand) error {
|
||||
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
|
||||
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
||||
|
||||
if _, err := extractor.GetAlerts(); err != nil {
|
||||
@@ -32,7 +20,7 @@ func validateDashboardAlerts(cmd *ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error {
|
||||
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
|
||||
saveAlerts := m.SaveAlertsCommand{
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
|
||||
231
pkg/services/dashboards/dashboard_service.go
Normal file
231
pkg/services/dashboards/dashboard_service.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// DashboardService service for operating on dashboards
|
||||
type DashboardService interface {
|
||||
SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
|
||||
}
|
||||
|
||||
// DashboardProvisioningService service for operating on provisioned dashboards
|
||||
type DashboardProvisioningService interface {
|
||||
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
|
||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||
}
|
||||
|
||||
// NewService factory for creating a new dashboard service
|
||||
var NewService = func() DashboardService {
|
||||
return &dashboardServiceImpl{}
|
||||
}
|
||||
|
||||
// NewProvisioningService factory for creating a new dashboard provisioning service
|
||||
var NewProvisioningService = func() DashboardProvisioningService {
|
||||
return &dashboardServiceImpl{}
|
||||
}
|
||||
|
||||
type SaveDashboardDTO struct {
|
||||
OrgId int64
|
||||
UpdatedAt time.Time
|
||||
User *models.SignedInUser
|
||||
Message string
|
||||
Overwrite bool
|
||||
Dashboard *models.Dashboard
|
||||
}
|
||||
|
||||
type dashboardServiceImpl struct{}
|
||||
|
||||
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
|
||||
dash := dto.Dashboard
|
||||
|
||||
dash.Title = strings.TrimSpace(dash.Title)
|
||||
dash.Data.Set("title", dash.Title)
|
||||
dash.SetUid(strings.TrimSpace(dash.Uid))
|
||||
|
||||
if dash.Title == "" {
|
||||
return nil, models.ErrDashboardTitleEmpty
|
||||
}
|
||||
|
||||
if dash.IsFolder && dash.FolderId > 0 {
|
||||
return nil, models.ErrDashboardFolderCannotHaveParent
|
||||
}
|
||||
|
||||
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(models.RootFolderName) {
|
||||
return nil, models.ErrDashboardFolderNameExists
|
||||
}
|
||||
|
||||
if !util.IsValidShortUid(dash.Uid) {
|
||||
return nil, models.ErrDashboardInvalidUid
|
||||
} else if len(dash.Uid) > 40 {
|
||||
return nil, models.ErrDashboardUidToLong
|
||||
}
|
||||
|
||||
validateAlertsCmd := models.ValidateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dash,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
|
||||
return nil, models.ErrDashboardContainsInvalidAlertData
|
||||
}
|
||||
|
||||
validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dash,
|
||||
Overwrite: dto.Overwrite,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateBeforeSaveCmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User)
|
||||
if canSave, err := guard.CanSave(); err != nil || !canSave {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, models.ErrDashboardUpdateAccessDenied
|
||||
}
|
||||
|
||||
cmd := &models.SaveDashboardCommand{
|
||||
Dashboard: dash.Data,
|
||||
Message: dto.Message,
|
||||
OrgId: dto.OrgId,
|
||||
Overwrite: dto.Overwrite,
|
||||
UserId: dto.User.UserId,
|
||||
FolderId: dash.FolderId,
|
||||
IsFolder: dash.IsFolder,
|
||||
}
|
||||
|
||||
if !dto.UpdatedAt.IsZero() {
|
||||
cmd.UpdatedAt = dto.UpdatedAt
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
|
||||
alertCmd := models.UpdateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
UserId: dto.User.UserId,
|
||||
Dashboard: cmd.Result,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
return models.ErrDashboardFailedToUpdateAlertData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
dto.User = &models.SignedInUser{
|
||||
UserId: 0,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
}
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveCmd := &models.SaveProvisionedDashboardCommand{
|
||||
DashboardCmd: cmd,
|
||||
DashboardProvisioning: provisioning,
|
||||
}
|
||||
|
||||
// dashboard
|
||||
err = bus.Dispatch(saveCmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//alerts
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
dto.User = &models.SignedInUser{
|
||||
UserId: 0,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
}
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
type FakeDashboardService struct {
|
||||
SaveDashboardResult *models.Dashboard
|
||||
SaveDashboardError error
|
||||
SavedDashboards []*SaveDashboardDTO
|
||||
}
|
||||
|
||||
func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
s.SavedDashboards = append(s.SavedDashboards, dto)
|
||||
|
||||
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
|
||||
s.SaveDashboardResult = dto.Dashboard
|
||||
}
|
||||
|
||||
return s.SaveDashboardResult, s.SaveDashboardError
|
||||
}
|
||||
|
||||
func MockDashboardService(mock *FakeDashboardService) {
|
||||
NewService = func() DashboardService {
|
||||
return mock
|
||||
}
|
||||
}
|
||||
144
pkg/services/dashboards/dashboard_service_test.go
Normal file
144
pkg/services/dashboards/dashboard_service_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
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/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDashboardService(t *testing.T) {
|
||||
Convey("Dashboard service tests", t, func() {
|
||||
service := dashboardServiceImpl{}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(&fakeDashboardGuardian{canSave: true})
|
||||
|
||||
Convey("Save dashboard validation", func() {
|
||||
dto := &SaveDashboardDTO{}
|
||||
|
||||
Convey("When saving a dashboard with empty title it should return error", func() {
|
||||
titles := []string{"", " ", " \t "}
|
||||
|
||||
for _, title := range titles {
|
||||
dto.Dashboard = models.NewDashboard(title)
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardTitleEmpty)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should return validation error if it's a folder and have a folder id", func() {
|
||||
dto.Dashboard = models.NewDashboardFolder("Folder")
|
||||
dto.Dashboard.FolderId = 1
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderCannotHaveParent)
|
||||
})
|
||||
|
||||
Convey("Should return validation error if folder is named General", func() {
|
||||
dto.Dashboard = models.NewDashboardFolder("General")
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderNameExists)
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard should validate uid", func() {
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
Uid string
|
||||
Error error
|
||||
}{
|
||||
{Uid: "", Error: nil},
|
||||
{Uid: " ", Error: nil},
|
||||
{Uid: " \t ", Error: nil},
|
||||
{Uid: "asdf90_-", Error: nil},
|
||||
{Uid: "asdf/90", Error: models.ErrDashboardInvalidUid},
|
||||
{Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil},
|
||||
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidToLong},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dto.Dashboard = models.NewDashboard("title")
|
||||
dto.Dashboard.SetUid(tc.Uid)
|
||||
dto.User = &models.SignedInUser{}
|
||||
|
||||
_, err := service.buildSaveDashboardCommand(dto)
|
||||
So(err, ShouldEqual, tc.Error)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should return validation error if alert data is invalid", func() {
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return errors.New("error")
|
||||
})
|
||||
|
||||
dto.Dashboard = models.NewDashboard("Dash")
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardContainsInvalidAlertData)
|
||||
})
|
||||
})
|
||||
|
||||
Reset(func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockDashboardGuardian(mock *fakeDashboardGuardian) {
|
||||
guardian.New = func(dashId int64, orgId int64, user *models.SignedInUser) guardian.DashboardGuardian {
|
||||
mock.orgId = orgId
|
||||
mock.dashId = dashId
|
||||
mock.user = user
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDashboardGuardian struct {
|
||||
dashId int64
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
canSave bool
|
||||
canEdit bool
|
||||
canView bool
|
||||
canAdmin bool
|
||||
hasPermission bool
|
||||
checkPermissionBeforeUpdate bool
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanSave() (bool, error) {
|
||||
return g.canSave, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanEdit() (bool, error) {
|
||||
return g.canEdit, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanView() (bool, error) {
|
||||
return g.canView, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanAdmin() (bool, error) {
|
||||
return g.canAdmin, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) HasPermission(permission models.PermissionType) (bool, error) {
|
||||
return g.hasPermission, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) {
|
||||
return g.checkPermissionBeforeUpdate, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) GetAcl() ([]*models.DashboardAclInfoDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
SaveDashboard(*SaveDashboardDTO) (*models.Dashboard, error)
|
||||
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||
}
|
||||
|
||||
var repositoryInstance Repository
|
||||
|
||||
func GetRepository() Repository {
|
||||
return repositoryInstance
|
||||
}
|
||||
|
||||
func SetRepository(rep Repository) {
|
||||
repositoryInstance = rep
|
||||
}
|
||||
|
||||
type SaveDashboardDTO struct {
|
||||
OrgId int64
|
||||
UpdatedAt time.Time
|
||||
UserId int64
|
||||
Message string
|
||||
Overwrite bool
|
||||
Dashboard *models.Dashboard
|
||||
}
|
||||
|
||||
type DashboardRepository struct{}
|
||||
|
||||
func (dr *DashboardRepository) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
|
||||
dashboard := dto.Dashboard
|
||||
|
||||
if dashboard.Title == "" {
|
||||
return nil, models.ErrDashboardTitleEmpty
|
||||
}
|
||||
|
||||
if err := util.VerifyUid(dashboard.Uid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dashboard,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
|
||||
return nil, models.ErrDashboardContainsInvalidAlertData
|
||||
}
|
||||
|
||||
cmd := &models.SaveDashboardCommand{
|
||||
Dashboard: dashboard.Data,
|
||||
Message: dto.Message,
|
||||
OrgId: dto.OrgId,
|
||||
Overwrite: dto.Overwrite,
|
||||
UserId: dto.UserId,
|
||||
FolderId: dashboard.FolderId,
|
||||
IsFolder: dashboard.IsFolder,
|
||||
}
|
||||
|
||||
if !dto.UpdatedAt.IsZero() {
|
||||
cmd.UpdatedAt = dto.UpdatedAt
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
|
||||
alertCmd := alerting.UpdateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
UserId: dto.UserId,
|
||||
Dashboard: cmd.Result,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
return models.ErrDashboardFailedToUpdateAlertData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveCmd := &models.SaveProvisionedDashboardCommand{
|
||||
DashboardCmd: cmd,
|
||||
DashboardProvisioning: provisioning,
|
||||
}
|
||||
|
||||
// dashboard
|
||||
err = bus.Dispatch(saveCmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//alerts
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestDashboardsService(t *testing.T) {
|
||||
|
||||
bus.ClearBusHandlers()
|
||||
|
||||
bus.AddHandler("test", func(cmd *alerting.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
Uid string
|
||||
Error error
|
||||
}{
|
||||
{Uid: "", Error: nil},
|
||||
{Uid: "asdf90_-", Error: nil},
|
||||
{Uid: "asdf/90", Error: util.ErrDashboardInvalidUid},
|
||||
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: util.ErrDashboardUidToLong},
|
||||
}
|
||||
|
||||
repo := &DashboardRepository{}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dto := &SaveDashboardDTO{
|
||||
Dashboard: &models.Dashboard{Title: "title", Uid: tc.Uid},
|
||||
}
|
||||
|
||||
_, err := repo.buildSaveDashboardCommand(dto)
|
||||
|
||||
if err != tc.Error {
|
||||
t.Fatalf("expected %s to return %v", tc.Uid, tc.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type DashboardGuardian struct {
|
||||
// DashboardGuardian to be used for guard against operations without access on dashboard and acl
|
||||
type DashboardGuardian interface {
|
||||
CanSave() (bool, error)
|
||||
CanEdit() (bool, error)
|
||||
CanView() (bool, error)
|
||||
CanAdmin() (bool, error)
|
||||
HasPermission(permission m.PermissionType) (bool, error)
|
||||
CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error)
|
||||
GetAcl() ([]*m.DashboardAclInfoDTO, error)
|
||||
}
|
||||
|
||||
type dashboardGuardianImpl struct {
|
||||
user *m.SignedInUser
|
||||
dashId int64
|
||||
orgId int64
|
||||
@@ -16,8 +27,9 @@ type DashboardGuardian struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewDashboardGuardian(dashId int64, orgId int64, user *m.SignedInUser) *DashboardGuardian {
|
||||
return &DashboardGuardian{
|
||||
// New factory for creating a new dashboard guardian instance
|
||||
var New = func(dashId int64, orgId int64, user *m.SignedInUser) DashboardGuardian {
|
||||
return &dashboardGuardianImpl{
|
||||
user: user,
|
||||
dashId: dashId,
|
||||
orgId: orgId,
|
||||
@@ -25,11 +37,11 @@ func NewDashboardGuardian(dashId int64, orgId int64, user *m.SignedInUser) *Dash
|
||||
}
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanSave() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanSave() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_EDIT)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanEdit() (bool, error) {
|
||||
if setting.ViewersCanEdit {
|
||||
return g.HasPermission(m.PERMISSION_VIEW)
|
||||
}
|
||||
@@ -37,15 +49,15 @@ func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_EDIT)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanView() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanView() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_VIEW)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanAdmin() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanAdmin() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_ADMIN)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) HasPermission(permission m.PermissionType) (bool, error) {
|
||||
if g.user.OrgRole == m.ROLE_ADMIN {
|
||||
return true, nil
|
||||
}
|
||||
@@ -58,7 +70,7 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
|
||||
return g.checkAcl(permission, acl)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.DashboardAclInfoDTO) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) checkAcl(permission m.PermissionType, acl []*m.DashboardAclInfoDTO) (bool, error) {
|
||||
orgRole := g.user.OrgRole
|
||||
teamAclItems := []*m.DashboardAclInfoDTO{}
|
||||
|
||||
@@ -106,7 +118,7 @@ func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.Dashb
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
|
||||
if g.user.OrgRole == m.ROLE_ADMIN {
|
||||
return true, nil
|
||||
}
|
||||
@@ -121,7 +133,7 @@ func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionT
|
||||
}
|
||||
|
||||
// GetAcl returns dashboard acl
|
||||
func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
if g.acl != nil {
|
||||
return g.acl, nil
|
||||
}
|
||||
@@ -135,7 +147,7 @@ func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
return g.acl, nil
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) getTeams() ([]*m.Team, error) {
|
||||
func (g *dashboardGuardianImpl) getTeams() ([]*m.Team, error) {
|
||||
if g.groups != nil {
|
||||
return g.groups, nil
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ var (
|
||||
)
|
||||
|
||||
type fileReader struct {
|
||||
Cfg *DashboardsAsConfig
|
||||
Path string
|
||||
log log.Logger
|
||||
dashboardRepo dashboards.Repository
|
||||
Cfg *DashboardsAsConfig
|
||||
Path string
|
||||
log log.Logger
|
||||
dashboardService dashboards.DashboardProvisioningService
|
||||
}
|
||||
|
||||
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
|
||||
@@ -48,10 +48,10 @@ func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReade
|
||||
}
|
||||
|
||||
return &fileReader{
|
||||
Cfg: cfg,
|
||||
Path: path,
|
||||
log: log,
|
||||
dashboardRepo: dashboards.GetRepository(),
|
||||
Cfg: cfg,
|
||||
Path: path,
|
||||
log: log,
|
||||
dashboardService: dashboards.NewProvisioningService(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ func (fr *fileReader) startWalkingDisk() error {
|
||||
}
|
||||
}
|
||||
|
||||
folderId, err := getOrCreateFolderId(fr.Cfg, fr.dashboardRepo)
|
||||
folderId, err := getOrCreateFolderId(fr.Cfg, fr.dashboardService)
|
||||
if err != nil && err != ErrFolderNameMissing {
|
||||
return err
|
||||
}
|
||||
|
||||
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardRepo, fr.Cfg.Name)
|
||||
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardService, fr.Cfg.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -180,12 +180,12 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
|
||||
|
||||
fr.log.Debug("saving new dashboard", "file", path)
|
||||
dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime().Unix()}
|
||||
_, err = fr.dashboardRepo.SaveProvisionedDashboard(dash, dp)
|
||||
_, err = fr.dashboardService.SaveProvisionedDashboard(dash, dp)
|
||||
return provisioningMetadata, err
|
||||
}
|
||||
|
||||
func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||
arr, err := repo.GetProvisionedDashboardData(name)
|
||||
func getProvisionedDashboardByPath(service dashboards.DashboardProvisioningService, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||
arr, err := service.GetProvisionedDashboardData(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map
|
||||
return byPath, nil
|
||||
}
|
||||
|
||||
func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (int64, error) {
|
||||
func getOrCreateFolderId(cfg *DashboardsAsConfig, service dashboards.DashboardProvisioningService) (int64, error) {
|
||||
if cfg.Folder == "" {
|
||||
return 0, ErrFolderNameMissing
|
||||
}
|
||||
@@ -213,11 +213,11 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (i
|
||||
// dashboard folder not found. create one.
|
||||
if err == models.ErrDashboardNotFound {
|
||||
dash := &dashboards.SaveDashboardDTO{}
|
||||
dash.Dashboard = models.NewDashboard(cfg.Folder)
|
||||
dash.Dashboard = models.NewDashboardFolder(cfg.Folder)
|
||||
dash.Dashboard.IsFolder = true
|
||||
dash.Overwrite = true
|
||||
dash.OrgId = cfg.OrgId
|
||||
dbDash, err := repo.SaveDashboard(dash)
|
||||
dbDash, err := service.SaveFolderForProvisionedDashboards(dash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@ var (
|
||||
brokenDashboards string = "./test-dashboards/broken-dashboards"
|
||||
oneDashboard string = "./test-dashboards/one-dashboard"
|
||||
|
||||
fakeRepo *fakeDashboardRepo
|
||||
fakeService *fakeDashboardProvisioningService
|
||||
)
|
||||
|
||||
func TestDashboardFileReader(t *testing.T) {
|
||||
Convey("Dashboard file reader", t, func() {
|
||||
bus.ClearBusHandlers()
|
||||
fakeRepo = &fakeDashboardRepo{}
|
||||
origNewDashboardProvisioningService := dashboards.NewProvisioningService
|
||||
fakeService = mockDashboardProvisioningService()
|
||||
|
||||
bus.AddHandler("test", mockGetDashboardQuery)
|
||||
dashboards.SetRepository(fakeRepo)
|
||||
logger := log.New("test.logger")
|
||||
|
||||
Convey("Reading dashboards from disk", func() {
|
||||
@@ -54,7 +54,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
folders := 0
|
||||
dashboards := 0
|
||||
|
||||
for _, i := range fakeRepo.inserted {
|
||||
for _, i := range fakeService.inserted {
|
||||
if i.Dashboard.IsFolder {
|
||||
folders++
|
||||
} else {
|
||||
@@ -71,7 +71,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
|
||||
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
|
||||
|
||||
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
|
||||
fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1),
|
||||
Slug: "grafana",
|
||||
})
|
||||
@@ -82,7 +82,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
err = reader.startWalkingDisk()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeService.inserted), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Invalid configuration should return error", func() {
|
||||
@@ -116,7 +116,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := getOrCreateFolderId(cfg, fakeRepo)
|
||||
_, err := getOrCreateFolderId(cfg, fakeService)
|
||||
So(err, ShouldEqual, ErrFolderNameMissing)
|
||||
})
|
||||
|
||||
@@ -131,15 +131,15 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
folderId, err := getOrCreateFolderId(cfg, fakeRepo)
|
||||
folderId, err := getOrCreateFolderId(cfg, fakeService)
|
||||
So(err, ShouldBeNil)
|
||||
inserted := false
|
||||
for _, d := range fakeRepo.inserted {
|
||||
for _, d := range fakeService.inserted {
|
||||
if d.Dashboard.IsFolder && d.Dashboard.Id == folderId {
|
||||
inserted = true
|
||||
}
|
||||
}
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeService.inserted), ShouldEqual, 1)
|
||||
So(inserted, ShouldBeTrue)
|
||||
})
|
||||
|
||||
@@ -180,6 +180,10 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
So(reader.Path, ShouldEqual, defaultDashboards)
|
||||
})
|
||||
})
|
||||
|
||||
Reset(func() {
|
||||
dashboards.NewProvisioningService = origNewDashboardProvisioningService
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,29 +216,37 @@ func (ffi FakeFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeDashboardRepo struct {
|
||||
func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
|
||||
mock := fakeDashboardProvisioningService{}
|
||||
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
|
||||
return &mock
|
||||
}
|
||||
return &mock
|
||||
}
|
||||
|
||||
type fakeDashboardProvisioningService struct {
|
||||
inserted []*dashboards.SaveDashboardDTO
|
||||
provisioned []*models.DashboardProvisioning
|
||||
getDashboard []*models.Dashboard
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, json)
|
||||
return json.Dashboard, nil
|
||||
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
return s.provisioned, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
return repo.provisioned, nil
|
||||
func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
s.inserted = append(s.inserted, dto)
|
||||
s.provisioned = append(s.provisioned, provisioning)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, dto)
|
||||
repo.provisioned = append(repo.provisioned, provisioning)
|
||||
func (s *fakeDashboardProvisioningService) SaveFolderForProvisionedDashboards(dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
s.inserted = append(s.inserted, dto)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
|
||||
for _, d := range fakeRepo.getDashboard {
|
||||
for _, d := range fakeService.getDashboard {
|
||||
if d.Slug == cmd.Slug {
|
||||
cmd.Result = d
|
||||
return nil
|
||||
|
||||
@@ -23,6 +23,7 @@ func init() {
|
||||
bus.AddHandler("sql", GetDashboardsByPluginId)
|
||||
bus.AddHandler("sql", GetDashboardPermissionsForUser)
|
||||
bus.AddHandler("sql", GetDashboardsBySlug)
|
||||
bus.AddHandler("sql", ValidateDashboardBeforeSave)
|
||||
}
|
||||
|
||||
var generateNewUid func() string = util.GenerateShortUid
|
||||
@@ -36,38 +37,29 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
dash := cmd.GetDashboardModel()
|
||||
|
||||
if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var existingByTitleAndFolder m.Dashboard
|
||||
|
||||
dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashWithTitleAndFolderExists {
|
||||
if dash.Id != existingByTitleAndFolder.Id {
|
||||
if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
|
||||
return m.ErrDashboardWithSameNameAsFolder
|
||||
}
|
||||
|
||||
if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
|
||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||
}
|
||||
if dash.Id > 0 {
|
||||
var existing m.Dashboard
|
||||
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.Id = existingByTitleAndFolder.Id
|
||||
dash.Version = existingByTitleAndFolder.Version
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.Uid = existingByTitleAndFolder.Uid
|
||||
}
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardWithSameNameInFolderExists
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
}
|
||||
|
||||
if dash.Uid == "" {
|
||||
@@ -75,21 +67,21 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dash.Uid = uid
|
||||
dash.Data.Set("uid", uid)
|
||||
dash.SetUid(uid)
|
||||
}
|
||||
|
||||
parentVersion := dash.Version
|
||||
affectedRows := int64(0)
|
||||
var err error
|
||||
|
||||
if dash.Id == 0 {
|
||||
dash.Version = 1
|
||||
dash.SetVersion(1)
|
||||
metrics.M_Api_Dashboard_Insert.Inc()
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Insert(dash)
|
||||
} else {
|
||||
dash.Version++
|
||||
dash.Data.Set("version", dash.Version)
|
||||
v := dash.Version
|
||||
v++
|
||||
dash.SetVersion(v)
|
||||
|
||||
if !cmd.UpdatedAt.IsZero() {
|
||||
dash.Updated = cmd.UpdatedAt
|
||||
@@ -145,72 +137,6 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
|
||||
dashWithIdExists := false
|
||||
var existingById m.Dashboard
|
||||
|
||||
if dash.Id > 0 {
|
||||
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.Uid = existingById.Uid
|
||||
}
|
||||
}
|
||||
|
||||
dashWithUidExists := false
|
||||
var existingByUid m.Dashboard
|
||||
|
||||
if dash.Uid != "" {
|
||||
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !dashWithIdExists && !dashWithUidExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
|
||||
return m.ErrDashboardWithSameUIDExists
|
||||
}
|
||||
|
||||
existing := existingById
|
||||
|
||||
if !dashWithIdExists && dashWithUidExists {
|
||||
dash.Id = existingByUid.Id
|
||||
existing = existingByUid
|
||||
}
|
||||
|
||||
if (existing.IsFolder && !cmd.IsFolder) ||
|
||||
(!existing.IsFolder && cmd.IsFolder) {
|
||||
return m.ErrDashboardTypeMismatch
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.Version = existing.Version
|
||||
} else {
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := generateNewUid()
|
||||
@@ -238,8 +164,8 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dashboard.Data.Set("id", dashboard.Id)
|
||||
dashboard.Data.Set("uid", dashboard.Uid)
|
||||
dashboard.SetId(dashboard.Id)
|
||||
dashboard.SetUid(dashboard.Uid)
|
||||
query.Result = &dashboard
|
||||
return nil
|
||||
}
|
||||
@@ -548,3 +474,128 @@ func GetDashboardUIDById(query *m.GetDashboardRefByIdQuery) error {
|
||||
query.Result = us
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
|
||||
dash := cmd.Dashboard
|
||||
|
||||
dashWithIdExists := false
|
||||
var existingById m.Dashboard
|
||||
|
||||
if dash.Id > 0 {
|
||||
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.SetUid(existingById.Uid)
|
||||
}
|
||||
}
|
||||
|
||||
dashWithUidExists := false
|
||||
var existingByUid m.Dashboard
|
||||
|
||||
if dash.Uid != "" {
|
||||
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dash.FolderId > 0 {
|
||||
var existingFolder m.Dashboard
|
||||
folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder)
|
||||
if folderErr != nil {
|
||||
return folderErr
|
||||
}
|
||||
|
||||
if !folderExists {
|
||||
return m.ErrFolderNotFound
|
||||
}
|
||||
}
|
||||
|
||||
if !dashWithIdExists && !dashWithUidExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
|
||||
return m.ErrDashboardWithSameUIDExists
|
||||
}
|
||||
|
||||
existing := existingById
|
||||
|
||||
if !dashWithIdExists && dashWithUidExists {
|
||||
dash.SetId(existingByUid.Id)
|
||||
dash.SetUid(existingByUid.Uid)
|
||||
existing = existingByUid
|
||||
}
|
||||
|
||||
if (existing.IsFolder && !dash.IsFolder) ||
|
||||
(!existing.IsFolder && dash.IsFolder) {
|
||||
return m.ErrDashboardTypeMismatch
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) error {
|
||||
dash := cmd.Dashboard
|
||||
var existing m.Dashboard
|
||||
|
||||
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists && dash.Id != existing.Id {
|
||||
if existing.IsFolder && !dash.IsFolder {
|
||||
return m.ErrDashboardWithSameNameAsFolder
|
||||
}
|
||||
|
||||
if !existing.IsFolder && dash.IsFolder {
|
||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||
}
|
||||
|
||||
if cmd.Overwrite {
|
||||
dash.SetId(existing.Id)
|
||||
dash.SetUid(existing.Uid)
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardWithSameNameInFolderExists
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = getExistingDashboardByTitleAndFolder(sess, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
984
pkg/services/sqlstore/dashboard_service_integration_test.go
Normal file
984
pkg/services/sqlstore/dashboard_service_integration_test.go
Normal file
@@ -0,0 +1,984 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestIntegratedDashboardService(t *testing.T) {
|
||||
Convey("Dashboard service integration tests", t, func() {
|
||||
InitTestDB(t)
|
||||
var testOrgId int64 = 1
|
||||
|
||||
Convey("Given saved folders and dashboards in organization A", func() {
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
savedFolder := saveTestFolder("Saved folder", testOrgId)
|
||||
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
|
||||
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
|
||||
savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
|
||||
otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedFolder.Title, ShouldEqual, "Saved folder")
|
||||
So(savedFolder.Slug, ShouldEqual, "saved-folder")
|
||||
So(savedFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedFolder.IsFolder, ShouldBeTrue)
|
||||
So(savedFolder.FolderId, ShouldEqual, 0)
|
||||
So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
|
||||
|
||||
So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
|
||||
So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
|
||||
So(savedDashInFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedDashInFolder.IsFolder, ShouldBeFalse)
|
||||
So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
// Basic validation tests
|
||||
|
||||
Convey("When saving a dashboard with non-existing id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
}),
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
// Given other organization
|
||||
|
||||
Convey("Given organization B", func() {
|
||||
var otherOrgId int64 = 2
|
||||
|
||||
Convey("When saving a dashboard with id that are saved in organization A", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "Expect error",
|
||||
}),
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
|
||||
Convey("When saving a dashboard with uid that are saved in organization A", func() {
|
||||
var otherOrgId int64 = 2
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Dash with existing uid in other org",
|
||||
}),
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should create dashboard in other organization", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.OrgId, ShouldEqual, otherOrgId)
|
||||
So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Given user has no permission to save
|
||||
|
||||
permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
|
||||
|
||||
Convey("When trying to create a new dashboard in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, 0)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to create a new dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: otherSavedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, otherSavedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInGeneralFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Given user has permission to save
|
||||
|
||||
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
|
||||
|
||||
Convey("and overwrite flag is set to false", func() {
|
||||
shouldOverwrite := false
|
||||
|
||||
Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a folder with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new folder", func() {
|
||||
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
|
||||
So(res.IsFolder, ShouldBeTrue)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.IsFolder, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard without id and uid and unique title in folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash without id and uid",
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
So(res.Id, ShouldBeGreaterThan, 0)
|
||||
So(len(res.Uid), ShouldBeGreaterThan, 0)
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard when dashboard id is zero ", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 0,
|
||||
"title": "Dash with zero id",
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard in non-existing folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Expect error",
|
||||
}),
|
||||
FolderId: 123412321,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in folder not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrFolderNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by id without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in version mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by id with current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Updated title",
|
||||
"version": savedDashInGeneralFolder.Version,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in version mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid with current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Updated title",
|
||||
"version": savedDashInFolder.Version,
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a folder with same name as existing folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and overwrite flag is set to true", func() {
|
||||
shouldOverwrite := true
|
||||
|
||||
Convey("When updating an existing dashboard by id without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Updated title",
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Updated title",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating uid for existing dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"uid": "new-uid",
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInFolder.Id)
|
||||
So(res.Uid, ShouldEqual, "new-uid")
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Uid, ShouldEqual, "new-uid")
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating uid to an existing uid for existing dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"uid": savedDashInGeneralFolder.Uid,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in same uid exists error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInFolder.Id)
|
||||
So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
|
||||
So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedFolder.Id,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedFolder.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name as folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in folder with same name as dashboard error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockDashboardGuardian(mock *mockDashboardGuarder) {
|
||||
guardian.New = func(dashId int64, orgId int64, user *models.SignedInUser) guardian.DashboardGuardian {
|
||||
mock.orgId = orgId
|
||||
mock.dashId = dashId
|
||||
mock.user = user
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
type mockDashboardGuarder struct {
|
||||
dashId int64
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
canSave bool
|
||||
canSaveCallCounter int
|
||||
canEdit bool
|
||||
canView bool
|
||||
canAdmin bool
|
||||
hasPermission bool
|
||||
checkPermissionBeforeRemove bool
|
||||
checkPermissionBeforeUpdate bool
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanSave() (bool, error) {
|
||||
g.canSaveCallCounter++
|
||||
return g.canSave, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanEdit() (bool, error) {
|
||||
return g.canEdit, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanView() (bool, error) {
|
||||
return g.canView, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanAdmin() (bool, error) {
|
||||
return g.canAdmin, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) HasPermission(permission models.PermissionType) (bool, error) {
|
||||
return g.hasPermission, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) {
|
||||
return g.checkPermissionBeforeUpdate, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) GetAcl() ([]*models.DashboardAclInfoDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type scenarioContext struct {
|
||||
dashboardGuardianMock *mockDashboardGuarder
|
||||
}
|
||||
|
||||
type scenarioFunc func(c *scenarioContext)
|
||||
|
||||
func dashboardGuardianScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &scenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
type dashboardPermissionScenarioContext struct {
|
||||
dashboardGuardianMock *mockDashboardGuarder
|
||||
}
|
||||
|
||||
type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
|
||||
|
||||
func dashboardPermissionScenario(desc string, mock *mockDashboardGuarder, fn dashboardPermissionScenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &dashboardPermissionScenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
|
||||
mock := &mockDashboardGuarder{
|
||||
canSave: canSave,
|
||||
}
|
||||
dashboardPermissionScenario(desc, mock, fn)
|
||||
}
|
||||
|
||||
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
res, _ := dashboards.NewService().SaveDashboard(&dto)
|
||||
return res
|
||||
}
|
||||
|
||||
func callSaveWithError(cmd models.SaveDashboardCommand) error {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
_, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
return err
|
||||
}
|
||||
|
||||
func dashboardServiceScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &scenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: false,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
}),
|
||||
}
|
||||
|
||||
dto := dashboards.SaveDashboardDTO{
|
||||
OrgId: orgId,
|
||||
Dashboard: cmd.GetDashboardModel(),
|
||||
User: &models.SignedInUser{
|
||||
UserId: 1,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func saveTestFolder(title string, orgId int64) *models.Dashboard {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: 0,
|
||||
IsFolder: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
}),
|
||||
}
|
||||
|
||||
dto := dashboards.SaveDashboardDTO{
|
||||
OrgId: orgId,
|
||||
Dashboard: cmd.GetDashboardModel(),
|
||||
User: &models.SignedInUser{
|
||||
UserId: 1,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
|
||||
dash := (&cmd).GetDashboardModel()
|
||||
|
||||
return dashboards.SaveDashboardDTO{
|
||||
Dashboard: dash,
|
||||
Message: cmd.Message,
|
||||
OrgId: cmd.OrgId,
|
||||
User: &models.SignedInUser{UserId: cmd.UserId},
|
||||
Overwrite: cmd.Overwrite,
|
||||
}
|
||||
}
|
||||
@@ -100,324 +100,6 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should return not found error if no dashboard is found for update", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard in another org", func() {
|
||||
query := m.GetDashboardQuery{Slug: "test-dash-23", OrgId: 1}
|
||||
GetDashboard(&query)
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 2,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(query.Result.Id),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should be able to save dashboards with same name in different folders", func() {
|
||||
firstSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "randomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&firstSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
secondSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "moreRandomHash",
|
||||
}),
|
||||
FolderId: 1,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&secondSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(firstSaveCmd.Result.Id, ShouldNotEqual, secondSaveCmd.Result.Id)
|
||||
})
|
||||
|
||||
Convey("Should be able to overwrite dashboard in same folder using title", func() {
|
||||
insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||
dashInFolder := insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: folder.Id,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.Id, ShouldEqual, dashInFolder.Id)
|
||||
So(cmd.Result.Uid, ShouldEqual, dashInFolder.Uid)
|
||||
})
|
||||
|
||||
Convey("Should be able to overwrite dashboard in General folder using title", func() {
|
||||
dashInGeneral := insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||
insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.Id, ShouldEqual, dashInGeneral.Id)
|
||||
So(cmd.Result.Uid, ShouldEqual, dashInGeneral.Uid)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard in general folder using title", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
FolderId: 0,
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard in folder using title", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard using id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedFolder.Id,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard with folder using id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDash.Id,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedFolder.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard with folder using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to save dashboard with same name in the same folder without overwrite", func() {
|
||||
firstSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "randomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&firstSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
secondSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "moreRandomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&secondSaveCmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
|
||||
Convey("Should be able to save and update dashboard using same uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"uid": "dsfalkjngailuedt",
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to get updated dashboard by uid", func() {
|
||||
query := m.GetDashboardQuery{
|
||||
Uid: savedDash.Uid,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := GetDashboard(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(query.Result.Id, ShouldEqual, savedDash.Id)
|
||||
So(query.Result.Title, ShouldEqual, "new title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard with the same title and folder id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": "randomHash",
|
||||
"title": "folderId",
|
||||
"style": "light",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||
|
||||
cmd = m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": cmd.Result.Id,
|
||||
"uid": "randomHash",
|
||||
"title": "folderId",
|
||||
"style": "dark",
|
||||
"version": cmd.Result.Version,
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update using uid without id and overwrite", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "folderId",
|
||||
"version": savedDash.Version,
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: savedDash.FolderId,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should retry generation of uid once if it fails.", func() {
|
||||
timesCalled := 0
|
||||
generateNewUid = func() string {
|
||||
@@ -499,6 +181,36 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return error if no dashboard is found for update when dashboard id is greater than zero", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should not return error if no dashboard is found for update when dashboard id is zero", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 0,
|
||||
"title": "New dash",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard tags", func() {
|
||||
query := m.GetDashboardTagsQuery{OrgId: 1}
|
||||
|
||||
@@ -627,6 +339,9 @@ func insertTestDashboard(title string, orgId int64, folderId int64, isFolder boo
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cmd.Result.Data.Set("id", cmd.Result.Id)
|
||||
cmd.Result.Data.Set("uid", cmd.Result.Uid)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func updateTestDashboard(dashboard *m.Dashboard, data map[string]interface{}) {
|
||||
data["uid"] = dashboard.Uid
|
||||
data["id"] = dashboard.Id
|
||||
|
||||
saveCmd := m.SaveDashboardCommand{
|
||||
OrgId: dashboard.OrgId,
|
||||
|
||||
@@ -1,61 +1,13 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
)
|
||||
|
||||
var (
|
||||
dbSqlite = "sqlite"
|
||||
dbMySql = "mysql"
|
||||
dbPostgres = "postgres"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
selectedDb := dbSqlite
|
||||
//selectedDb := dbMySql
|
||||
//selectedDb := dbPostgres
|
||||
|
||||
var x *xorm.Engine
|
||||
var err error
|
||||
|
||||
// environment variable present for test db?
|
||||
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
||||
selectedDb = db
|
||||
}
|
||||
|
||||
switch strings.ToLower(selectedDb) {
|
||||
case dbMySql:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
case dbPostgres:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
default:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
}
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
|
||||
}
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Id int64
|
||||
Name string
|
||||
|
||||
@@ -7,14 +7,15 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
@@ -101,7 +102,6 @@ func SetEngine(engine *xorm.Engine) (err error) {
|
||||
|
||||
// Init repo instances
|
||||
annotations.SetRepository(&SqlAnnotationRepo{})
|
||||
dashboards.SetRepository(&dashboards.DashboardRepository{})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,3 +216,46 @@ func LoadConfig() {
|
||||
DbCfg.ServerCertName = sec.Key("server_cert_name").String()
|
||||
DbCfg.Path = sec.Key("path").MustString("data/grafana.db")
|
||||
}
|
||||
|
||||
var (
|
||||
dbSqlite = "sqlite"
|
||||
dbMySql = "mysql"
|
||||
dbPostgres = "postgres"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
selectedDb := dbSqlite
|
||||
//selectedDb := dbMySql
|
||||
//selectedDb := dbPostgres
|
||||
|
||||
var x *xorm.Engine
|
||||
var err error
|
||||
|
||||
// environment variable present for test db?
|
||||
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
||||
selectedDb = db
|
||||
}
|
||||
|
||||
switch strings.ToLower(selectedDb) {
|
||||
case dbMySql:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
case dbPostgres:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
default:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
}
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
|
||||
}
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user