mirror of
https://github.com/grafana/grafana.git
synced 2026-08-11 21:54:52 -05:00
Nested Folder: Enforce maximum nested folder depth (#59213)
* Nested Folder: Enforce maximum nested folder depth * Cleanup * Fix logging
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/events"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -206,6 +205,8 @@ func (s *Service) getFolderByTitle(ctx context.Context, user *user.SignedInUser,
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
logger := s.log.FromContext(ctx)
|
||||
|
||||
dashFolder := models.NewDashboardFolder(cmd.Title)
|
||||
dashFolder.OrgId = cmd.OrgID
|
||||
|
||||
@@ -270,36 +271,28 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
|
||||
}
|
||||
|
||||
if permissionErr != nil {
|
||||
s.log.Error("Could not make user admin", "folder", createdFolder.Title, "user", userID, "error", permissionErr)
|
||||
logger.Error("Could not make user admin", "folder", createdFolder.Title, "user", userID, "error", permissionErr)
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||
var description string
|
||||
if dash.Data != nil {
|
||||
description = dash.Data.Get("description").MustString()
|
||||
}
|
||||
|
||||
parentUID := folder.RootFolderUID
|
||||
if cmd.ParentUID != "" {
|
||||
parentUID = cmd.ParentUID
|
||||
}
|
||||
_, err := s.store.Create(ctx, folder.CreateFolderCommand{
|
||||
cmd := &folder.CreateFolderCommand{
|
||||
// TODO: Today, if a UID isn't specified, the dashboard store
|
||||
// generates a new UID. The new folder store will need to do this as
|
||||
// well, but for now we take the UID from the newly created folder.
|
||||
UID: dash.Uid,
|
||||
OrgID: cmd.OrgID,
|
||||
Title: cmd.Title,
|
||||
Description: description,
|
||||
ParentUID: parentUID,
|
||||
})
|
||||
if err != nil {
|
||||
Description: cmd.Description,
|
||||
ParentUID: cmd.ParentUID,
|
||||
}
|
||||
if err := s.nestedFolderCreate(ctx, cmd); err != nil {
|
||||
// We'll log the error and also roll back the previously-created
|
||||
// (legacy) folder.
|
||||
s.log.Error("error saving folder to nested folder store", err)
|
||||
err = s.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: createdFolder.UID, OrgID: cmd.OrgID, ForceDeleteRules: true})
|
||||
if err != nil {
|
||||
s.log.Error("error deleting folder after failed save to nested folder store", err)
|
||||
logger.Error("error saving folder to nested folder store", "error", err)
|
||||
// do not shallow create error if the legacy folder delete fails
|
||||
deleteErr := s.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: createdFolder.UID, OrgID: cmd.OrgID, ForceDeleteRules: true, SignedInUser: user})
|
||||
if deleteErr != nil {
|
||||
logger.Error("error deleting folder after failed save to nested folder store", "error", err)
|
||||
}
|
||||
return folder.FromDashboard(dash), err
|
||||
}
|
||||
@@ -346,6 +339,8 @@ func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int
|
||||
}
|
||||
|
||||
func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*folder.Folder, error) {
|
||||
logger := s.log.FromContext(ctx)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid}
|
||||
_, err := s.dashboardStore.GetDashboard(ctx, &query)
|
||||
if err != nil {
|
||||
@@ -392,30 +387,31 @@ func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, org
|
||||
UID: dash.Uid,
|
||||
OrgID: orgID,
|
||||
}); err != nil {
|
||||
s.log.Error("failed to publish FolderTitleUpdated event", "folder", foldr.Title, "user", user.UserID, "error", err)
|
||||
logger.Error("failed to publish FolderTitleUpdated event", "folder", foldr.Title, "user", user.UserID, "error", err)
|
||||
}
|
||||
}
|
||||
return foldr, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
|
||||
logger := s.log.FromContext(ctx)
|
||||
if cmd.SignedInUser == nil {
|
||||
return folder.ErrBadRequest.Errorf("missing signed in user")
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||
err := s.Delete(ctx, cmd)
|
||||
if err != nil {
|
||||
s.log.Error("the delete folder on folder table failed with err: ", err.Error())
|
||||
logger.Error("the delete folder on folder table failed with err: ", "error", err)
|
||||
}
|
||||
}
|
||||
user, err := appcontext.User(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, cmd.OrgID, cmd.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
guard := guardian.New(ctx, dashFolder.ID, cmd.OrgID, user)
|
||||
guard := guardian.New(ctx, dashFolder.ID, cmd.OrgID, cmd.SignedInUser)
|
||||
if canSave, err := guard.CanDelete(); err != nil || !canSave {
|
||||
if err != nil {
|
||||
return toFolderError(err)
|
||||
@@ -498,6 +494,29 @@ func (s *Service) MakeUserAdmin(ctx context.Context, orgID int64, userID, folder
|
||||
return s.dashboardService.MakeUserAdmin(ctx, orgID, userID, folderID, setViewAndEditPermissions)
|
||||
}
|
||||
|
||||
func (s *Service) nestedFolderCreate(ctx context.Context, cmd *folder.CreateFolderCommand) error {
|
||||
if cmd.ParentUID != "" {
|
||||
if err := s.validateParent(ctx, cmd.OrgID, cmd.ParentUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := s.store.Create(ctx, *cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) validateParent(ctx context.Context, orgID int64, parentUID string) error {
|
||||
ancestors, err := s.store.GetParents(ctx, folder.GetParentsQuery{UID: parentUID, OrgID: orgID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ancestors) == folder.MaxNestedFolderDepth {
|
||||
return folder.ErrMaximumDepthReached
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toFolderError(err error) error {
|
||||
if errors.Is(err, dashboards.ErrDashboardTitleEmpty) {
|
||||
return dashboards.ErrFolderTitleEmpty
|
||||
|
||||
Reference in New Issue
Block a user