Commit 268fb4dc by Marcus Efraimsson

folders: new folder service for managing folders

parent 39aba034
......@@ -14,6 +14,7 @@ var (
ErrFolderWithSameUIDExists = errors.New("A folder/dashboard with the same uid already exists")
ErrFolderSameNameExists = errors.New("A folder or dashboard in the general folder with the same name already exists")
ErrFolderFailedGenerateUniqueUid = errors.New("Failed to generate unique folder id")
ErrFolderAccessDenied = errors.New("Access denied to folder")
)
type Folder struct {
......@@ -21,7 +22,6 @@ type Folder struct {
Uid string
Title string
Url string
OrgId int64
Version int
Created time.Time
......@@ -33,13 +33,10 @@ type Folder struct {
}
// GetDashboardModel turns the command into the savable model
func (cmd *CreateFolderCommand) GetDashboardModel() *Dashboard {
func (cmd *CreateFolderCommand) GetDashboardModel(orgId int64, userId int64) *Dashboard {
dashFolder := NewDashboardFolder(strings.TrimSpace(cmd.Title))
dashFolder.OrgId = cmd.OrgId
dashFolder.Uid = strings.TrimSpace(cmd.Uid)
dashFolder.Data.Set("uid", cmd.Uid)
userId := cmd.UserId
dashFolder.OrgId = orgId
dashFolder.SetUid(strings.TrimSpace(cmd.Uid))
if userId == 0 {
userId = -1
......@@ -53,15 +50,17 @@ func (cmd *CreateFolderCommand) GetDashboardModel() *Dashboard {
}
// UpdateDashboardModel updates an existing model from command into model for update
func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard) {
func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard, orgId int64, userId int64) {
dashFolder.OrgId = orgId
dashFolder.Title = strings.TrimSpace(cmd.Title)
dashFolder.Data.Set("title", cmd.Title)
dashFolder.Uid = dashFolder.Data.MustString("uid")
dashFolder.Data.Set("version", cmd.Version)
dashFolder.Version = cmd.Version
dashFolder.IsFolder = true
dashFolder.Data.Set("title", dashFolder.Title)
if cmd.Uid != "" {
dashFolder.SetUid(cmd.Uid)
}
userId := cmd.UserId
dashFolder.SetVersion(cmd.Version)
dashFolder.IsFolder = true
if userId == 0 {
userId = -1
......@@ -76,17 +75,16 @@ func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard) {
//
type CreateFolderCommand struct {
OrgId int64 `json:"-"`
UserId int64 `json:"userId"`
Uid string `json:"uid"`
Title string `json:"title"`
Uid string `json:"uid"`
Title string `json:"title"`
Version int `json:"version"`
Overwrite bool `json:"overwrite"`
Result *Folder
}
type UpdateFolderCommand struct {
OrgId int64 `json:"-"`
UserId int64 `json:"userId"`
Uid string `json:"uid"`
Title string `json:"title"`
Version int `json:"version"`
Overwrite bool `json:"overwrite"`
......
......@@ -41,7 +41,10 @@ type SaveDashboardDTO struct {
Dashboard *models.Dashboard
}
type dashboardServiceImpl struct{}
type dashboardServiceImpl struct {
orgId int64
user *models.SignedInUser
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
......@@ -53,7 +56,7 @@ func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*mod
return cmd.Result, nil
}
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard
dash.Title = strings.TrimSpace(dash.Title)
......@@ -78,13 +81,15 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO)
return nil, models.ErrDashboardUidToLong
}
validateAlertsCmd := models.ValidateDashboardAlertsCommand{
OrgId: dto.OrgId,
Dashboard: dash,
}
if validateAlerts {
validateAlertsCmd := models.ValidateDashboardAlertsCommand{
OrgId: dto.OrgId,
Dashboard: dash,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData
}
}
validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{
......@@ -141,7 +146,7 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
UserId: 0,
OrgRole: models.ROLE_ADMIN,
}
cmd, err := dr.buildSaveDashboardCommand(dto)
cmd, err := dr.buildSaveDashboardCommand(dto, true)
if err != nil {
return nil, err
}
......@@ -171,7 +176,7 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
UserId: 0,
OrgRole: models.ROLE_ADMIN,
}
cmd, err := dr.buildSaveDashboardCommand(dto)
cmd, err := dr.buildSaveDashboardCommand(dto, false)
if err != nil {
return nil, err
}
......@@ -190,7 +195,7 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
}
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
cmd, err := dr.buildSaveDashboardCommand(dto)
cmd, err := dr.buildSaveDashboardCommand(dto, true)
if err != nil {
return nil, err
}
......
......@@ -72,7 +72,7 @@ func TestDashboardService(t *testing.T) {
dto.Dashboard.SetUid(tc.Uid)
dto.User = &models.SignedInUser{}
_, err := service.buildSaveDashboardCommand(dto)
_, err := service.buildSaveDashboardCommand(dto, true)
So(err, ShouldEqual, tc.Error)
}
})
......
package dashboards
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
)
// FolderService service for operating on folders
type FolderService interface {
GetFolders(limit int) ([]*models.Folder, error)
GetFolderById(id int64) (*models.Folder, error)
GetFolderByUid(uid string) (*models.Folder, error)
CreateFolder(cmd *models.CreateFolderCommand) error
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(uid string) (*models.Folder, error)
}
// NewFolderService factory for creating a new folder service
var NewFolderService = func(orgId int64, user *models.SignedInUser) FolderService {
return &dashboardServiceImpl{
orgId: orgId,
user: user,
}
}
func (dr *dashboardServiceImpl) GetFolders(limit int) ([]*models.Folder, error) {
if limit == 0 {
limit = 1000
}
searchQuery := search.Query{
SignedInUser: dr.user,
DashboardIds: make([]int64, 0),
FolderIds: make([]int64, 0),
Limit: limit,
OrgId: dr.orgId,
Type: "dash-folder",
Permission: models.PERMISSION_VIEW,
}
if err := bus.Dispatch(&searchQuery); err != nil {
return nil, err
}
folders := make([]*models.Folder, 0)
for _, hit := range searchQuery.Result {
folders = append(folders, &models.Folder{
Id: hit.Id,
Uid: hit.Uid,
Title: hit.Title,
})
}
return folders, nil
}
func (dr *dashboardServiceImpl) GetFolderById(id int64) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(dashFolder.Id, dr.orgId, dr.user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) GetFolderByUid(uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(dashFolder.Id, dr.orgId, dr.user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) error {
dashFolder := cmd.GetDashboardModel(dr.orgId, dr.user.UserId)
dto := &SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: dr.orgId,
User: dr.user,
Overwrite: cmd.Overwrite,
}
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false)
if err != nil {
return toFolderError(err)
}
err = bus.Dispatch(saveDashboardCmd)
if err != nil {
return toFolderError(err)
}
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: saveDashboardCmd.Result.Id}
dashFolder, err = getFolder(query)
if err != nil {
return toFolderError(err)
}
cmd.Result = dashToFolder(dashFolder)
return nil
}
func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.UpdateFolderCommand) error {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: existingUid}
dashFolder, err := getFolder(query)
if err != nil {
return toFolderError(err)
}
cmd.UpdateDashboardModel(dashFolder, dr.orgId, dr.user.UserId)
dto := &SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: dr.orgId,
User: dr.user,
Overwrite: cmd.Overwrite,
}
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false)
if err != nil {
return toFolderError(err)
}
err = bus.Dispatch(saveDashboardCmd)
if err != nil {
return toFolderError(err)
}
query = models.GetDashboardQuery{OrgId: dr.orgId, Id: saveDashboardCmd.Result.Id}
dashFolder, err = getFolder(query)
if err != nil {
return toFolderError(err)
}
cmd.Result = dashToFolder(dashFolder)
return nil
}
func (dr *dashboardServiceImpl) DeleteFolder(uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(query)
if err != nil {
return nil, toFolderError(err)
}
guardian := guardian.New(dashFolder.Id, dr.orgId, dr.user)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id}
if err := bus.Dispatch(&deleteCmd); err != nil {
return nil, toFolderError(err)
}
return dashToFolder(dashFolder), nil
}
func getFolder(query models.GetDashboardQuery) (*models.Dashboard, error) {
if err := bus.Dispatch(&query); err != nil {
return nil, toFolderError(err)
}
if !query.Result.IsFolder {
return nil, models.ErrFolderNotFound
}
return query.Result, nil
}
func dashToFolder(dash *models.Dashboard) *models.Folder {
return &models.Folder{
Id: dash.Id,
Uid: dash.Uid,
Title: dash.Title,
HasAcl: dash.HasAcl,
Url: dash.GetUrl(),
Version: dash.Version,
Created: dash.Created,
CreatedBy: dash.CreatedBy,
Updated: dash.Updated,
UpdatedBy: dash.UpdatedBy,
}
}
func toFolderError(err error) error {
if err == models.ErrDashboardTitleEmpty {
return models.ErrFolderTitleEmpty
}
if err == models.ErrDashboardUpdateAccessDenied {
return models.ErrFolderAccessDenied
}
if err == models.ErrDashboardWithSameNameInFolderExists {
return models.ErrFolderSameNameExists
}
if err == models.ErrDashboardWithSameUIDExists {
return models.ErrFolderWithSameUIDExists
}
if err == models.ErrDashboardVersionMismatch {
return models.ErrFolderVersionMismatch
}
if err == models.ErrDashboardNotFound {
return models.ErrFolderNotFound
}
if err == models.ErrDashboardFailedGenerateUniqueUid {
err = models.ErrFolderFailedGenerateUniqueUid
}
return err
}
package dashboards
import (
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
. "github.com/smartystreets/goconvey/convey"
)
func TestFolderService(t *testing.T) {
Convey("Folder service tests", t, func() {
service := dashboardServiceImpl{
orgId: 1,
user: &models.SignedInUser{UserId: 1},
}
Convey("Given user has no permissions", func() {
origNewGuardian := guardian.New
mockDashboardGuardian(&fakeDashboardGuardian{})
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = models.NewDashboardFolder("Folder")
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
return models.ErrDashboardUpdateAccessDenied
})
Convey("When get folder by id should return access denied error", func() {
_, err := service.GetFolderById(1)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When get folder by uid should return access denied error", func() {
_, err := service.GetFolderByUid("uid")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When creating folder should return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{
Title: "Folder",
})
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When updating folder should return access denied error", func() {
err := service.UpdateFolder("uid", &models.UpdateFolderCommand{
Uid: "uid",
Title: "Folder",
})
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When deleting folder by uid should return access denied error", func() {
_, err := service.DeleteFolder("uid")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
Convey("Given user has permission to save", func() {
origNewGuardian := guardian.New
mockDashboardGuardian(&fakeDashboardGuardian{canSave: true})
dash := models.NewDashboardFolder("Folder")
dash.Id = 1
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = dash
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.SaveDashboardCommand) error {
cmd.Result = dash
return nil
})
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
return nil
})
Convey("When creating folder should not return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{
Title: "Folder",
})
So(err, ShouldBeNil)
})
Convey("When updating folder should not return access denied error", func() {
err := service.UpdateFolder("uid", &models.UpdateFolderCommand{
Uid: "uid",
Title: "Folder",
})
So(err, ShouldBeNil)
})
Convey("When deleting folder by uid should not return access denied error", func() {
_, err := service.DeleteFolder("uid")
So(err, ShouldBeNil)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
Convey("Given user has permission to view", func() {
origNewGuardian := guardian.New
mockDashboardGuardian(&fakeDashboardGuardian{canView: true})
dashFolder := models.NewDashboardFolder("Folder")
dashFolder.Id = 1
dashFolder.Uid = "uid-abc"
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = dashFolder
return nil
})
Convey("When get folder by id should return folder", func() {
f, _ := service.GetFolderById(1)
So(f.Id, ShouldEqual, dashFolder.Id)
So(f.Uid, ShouldEqual, dashFolder.Uid)
So(f.Title, ShouldEqual, dashFolder.Title)
})
Convey("When get folder by uid should not return access denied error", func() {
f, _ := service.GetFolderByUid("uid")
So(f.Id, ShouldEqual, dashFolder.Id)
So(f.Uid, ShouldEqual, dashFolder.Uid)
So(f.Title, ShouldEqual, dashFolder.Title)
})
Reset(func() {
guardian.New = origNewGuardian
})
})
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment