@ -13,6 +13,8 @@ import (
"github.com/grafana/dskit/concurrency"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/slices"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@ -20,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
@ -44,12 +47,14 @@ type Service struct {
dashboardFolderStore folder . FolderStore
features featuremgmt . FeatureToggles
accessControl accesscontrol . AccessControl
// bus is currently used to publish event in case of title change
// bus is currently used to publish event in case of folder full path change.
// For example when a folder is moved to another folder or when a folder is renamed.
bus bus . Bus
mutex sync . RWMutex
registry map [ string ] folder . RegistryService
metrics * foldersMetrics
tracer tracing . Tracer
}
func ProvideService (
@ -61,6 +66,7 @@ func ProvideService(
features featuremgmt . FeatureToggles ,
supportBundles supportbundles . Service ,
r prometheus . Registerer ,
tracer tracing . Tracer ,
) folder . Service {
store := ProvideStore ( db )
srv := & Service {
@ -74,6 +80,7 @@ func ProvideService(
db : db ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( r ) ,
tracer : tracer ,
}
srv . DBMigration ( db )
@ -655,6 +662,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
}
func ( s * Service ) Update ( ctx context . Context , cmd * folder . UpdateFolderCommand ) ( * folder . Folder , error ) {
ctx , span := s . tracer . Start ( ctx , "folder.Update" )
defer span . End ( )
if cmd . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
@ -679,14 +689,8 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (
if cmd . NewTitle != nil {
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
if err := s . bus . Publish ( ctx , & events . FolderTitleUpdated {
Timestamp : foldr . Updated ,
Title : foldr . Title ,
ID : dashFolder . ID , // nolint:staticcheck
UID : dashFolder . UID ,
OrgID : cmd . OrgID ,
} ) ; err != nil {
s . log . ErrorContext ( ctx , "failed to publish FolderTitleUpdated event" , "folder" , foldr . Title , "user" , cmd . SignedInUser . GetID ( ) , "error" , err )
if err := s . publishFolderFullPathUpdatedEvent ( ctx , foldr . Updated , cmd . OrgID , cmd . UID ) ; err != nil {
return err
}
}
@ -873,6 +877,9 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
}
func ( s * Service ) Move ( ctx context . Context , cmd * folder . MoveFolderCommand ) ( * folder . Folder , error ) {
ctx , span := s . tracer . Start ( ctx , "folder.Move" )
defer span . End ( )
if cmd . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
@ -947,6 +954,10 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
return folder . ErrInternal . Errorf ( "failed to move legacy folder: %w" , err )
}
if err := s . publishFolderFullPathUpdatedEvent ( ctx , f . Updated , cmd . OrgID , cmd . UID ) ; err != nil {
return err
}
return nil
} ) ; err != nil {
return nil , err
@ -954,6 +965,36 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
return f , nil
}
func ( s * Service ) publishFolderFullPathUpdatedEvent ( ctx context . Context , timestamp time . Time , orgID int64 , folderUID string ) error {
ctx , span := s . tracer . Start ( ctx , "folder.publishFolderFullPathUpdatedEvent" )
defer span . End ( )
descFolders , err := s . store . GetDescendants ( ctx , orgID , folderUID )
if err != nil {
s . log . ErrorContext ( ctx , "Failed to get descendants of the folder" , "folderUID" , folderUID , "orgID" , orgID , "error" , err )
return err
}
uids := make ( [ ] string , 0 , len ( descFolders ) + 1 )
uids = append ( uids , folderUID )
for _ , f := range descFolders {
uids = append ( uids , f . UID )
}
span . AddEvent ( "found folder descendants" , trace . WithAttributes (
attribute . Int64 ( "folders" , int64 ( len ( uids ) ) ) ,
) )
if err := s . bus . Publish ( ctx , & events . FolderFullPathUpdated {
Timestamp : timestamp ,
UIDs : uids ,
OrgID : orgID ,
} ) ; err != nil {
s . log . ErrorContext ( ctx , "Failed to publish FolderFullPathUpdated event" , "folderUID" , folderUID , "orgID" , orgID , "descendantsUIDs" , uids , "error" , err )
return err
}
return nil
}
func ( s * Service ) canMove ( ctx context . Context , cmd * folder . MoveFolderCommand ) ( bool , error ) {
// Check that the user is allowed to move the folder to the destination folder
var evaluator accesscontrol . Evaluator