grafana/pkg/services/dashboards/folder_service.go
2021-12-28 17:36:22 +01:00

277 lines
7.5 KiB
Go

package dashboards
import (
"context"
"errors"
"strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
)
// FolderService is a service for operating on folders.
type FolderService interface {
GetFolders(ctx context.Context, limit int64, page int64) ([]*models.Folder, error)
GetFolderByID(ctx context.Context, id int64) (*models.Folder, error)
GetFolderByUID(ctx context.Context, uid string) (*models.Folder, error)
GetFolderByTitle(ctx context.Context, title string) (*models.Folder, error)
CreateFolder(ctx context.Context, title, uid string) (*models.Folder, error)
UpdateFolder(ctx context.Context, uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(ctx context.Context, uid string, forceDeleteRules bool) (*models.Folder, error)
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
}
// NewFolderService is a factory for creating a new folder service.
var NewFolderService = func(orgID int64, user *models.SignedInUser, store dashboards.Store) FolderService {
return &dashboardServiceImpl{
orgId: orgID,
user: user,
dashboardStore: store,
}
}
func (dr *dashboardServiceImpl) GetFolders(ctx context.Context, limit int64, page int64) ([]*models.Folder, error) {
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,
Page: page,
}
if err := bus.Dispatch(ctx, &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(ctx context.Context, id int64) (*models.Folder, error) {
if id == 0 {
return &models.Folder{Id: id, Title: "General"}, nil
}
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, 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(ctx context.Context, uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, 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) GetFolderByTitle(ctx context.Context, title string) (*models.Folder, error) {
dashFolder, err := dr.dashboardStore.GetFolderByTitle(dr.orgId, title)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, 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(ctx context.Context, title, uid string) (*models.Folder, error) {
dashFolder := models.NewDashboardFolder(title)
dashFolder.OrgId = dr.orgId
dashFolder.SetUid(strings.TrimSpace(uid))
userID := dr.user.UserId
if userID == 0 {
userID = -1
}
dashFolder.CreatedBy = userID
dashFolder.UpdatedBy = userID
dashFolder.UpdateSlug()
dto := &SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: dr.orgId,
User: dr.user,
}
saveDashboardCmd, err := dr.buildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return nil, toFolderError(err)
}
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return nil, toFolderError(err)
}
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) UpdateFolder(ctx context.Context, existingUid string, cmd *models.UpdateFolderCommand) error {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: existingUid}
dashFolder, err := getFolder(ctx, 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(ctx, dto, false, false)
if err != nil {
return toFolderError(err)
}
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return toFolderError(err)
}
query = models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return toFolderError(err)
}
cmd.Result = dashToFolder(dashFolder)
return nil
}
func (dr *dashboardServiceImpl) DeleteFolder(ctx context.Context, uid string, forceDeleteRules bool) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
guardian := guardian.New(ctx, 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, ForceDeleteFolderRules: forceDeleteRules}
if err := bus.Dispatch(ctx, &deleteCmd); err != nil {
return nil, toFolderError(err)
}
return dashToFolder(dashFolder), nil
}
func getFolder(ctx context.Context, query models.GetDashboardQuery) (*models.Dashboard, error) {
if err := bus.Dispatch(ctx, &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 errors.Is(err, models.ErrDashboardTitleEmpty) {
return models.ErrFolderTitleEmpty
}
if errors.Is(err, models.ErrDashboardUpdateAccessDenied) {
return models.ErrFolderAccessDenied
}
if errors.Is(err, models.ErrDashboardWithSameNameInFolderExists) {
return models.ErrFolderSameNameExists
}
if errors.Is(err, models.ErrDashboardWithSameUIDExists) {
return models.ErrFolderWithSameUIDExists
}
if errors.Is(err, models.ErrDashboardVersionMismatch) {
return models.ErrFolderVersionMismatch
}
if errors.Is(err, models.ErrDashboardNotFound) {
return models.ErrFolderNotFound
}
if errors.Is(err, models.ErrDashboardFailedGenerateUniqueUid) {
err = models.ErrFolderFailedGenerateUniqueUid
}
return err
}