2022-10-11 03:47:53 +08:00
package folderimpl
2018-02-20 20:55:43 +08:00
import (
2021-09-14 22:08:04 +08:00
"context"
2020-11-19 21:47:17 +08:00
"errors"
2022-11-25 02:28:53 +08:00
"fmt"
2023-12-16 01:34:08 +08:00
"runtime"
2021-03-17 23:06:10 +08:00
"strings"
2023-04-14 17:17:23 +08:00
"sync"
2023-12-05 23:13:31 +08:00
"time"
2020-11-19 21:47:17 +08:00
2024-01-24 21:15:32 +08:00
"github.com/grafana/dskit/concurrency"
2023-12-05 23:13:31 +08:00
"github.com/prometheus/client_golang/prometheus"
2023-11-08 22:28:49 +08:00
"golang.org/x/exp/slices"
2022-06-18 01:10:49 +08:00
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
2022-11-08 21:59:55 +08:00
"github.com/grafana/grafana/pkg/infra/db"
2022-02-16 21:15:44 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2024-01-25 18:10:35 +08:00
"github.com/grafana/grafana/pkg/infra/metrics"
2022-03-10 19:58:18 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2023-08-30 22:51:18 +08:00
"github.com/grafana/grafana/pkg/services/auth/identity"
2022-02-16 21:15:44 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-03-24 02:40:22 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-10-11 03:47:53 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2018-02-20 20:55:43 +08:00
"github.com/grafana/grafana/pkg/services/guardian"
2024-01-23 00:04:18 +08:00
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
2023-04-27 23:00:09 +08:00
"github.com/grafana/grafana/pkg/services/store/entity"
2022-03-10 19:58:18 +08:00
"github.com/grafana/grafana/pkg/setting"
2023-01-30 16:21:27 +08:00
"github.com/grafana/grafana/pkg/util"
2018-02-20 20:55:43 +08:00
)
2022-10-11 03:47:53 +08:00
type Service struct {
2023-03-15 16:51:37 +08:00
store store
db db . DB
2023-01-20 00:38:07 +08:00
log log . Logger
cfg * setting . Cfg
dashboardStore dashboards . Store
2023-02-01 21:43:21 +08:00
dashboardFolderStore folder . FolderStore
2023-01-20 00:38:07 +08:00
features featuremgmt . FeatureToggles
accessControl accesscontrol . AccessControl
2022-06-18 01:10:49 +08:00
2023-02-08 23:16:53 +08:00
// bus is currently used to publish event in case of title change
2022-06-18 01:10:49 +08:00
bus bus . Bus
2023-04-14 17:17:23 +08:00
mutex sync . RWMutex
registry map [ string ] folder . RegistryService
2023-12-05 23:13:31 +08:00
metrics * foldersMetrics
2018-02-20 20:55:43 +08:00
}
2022-10-11 03:47:53 +08:00
func ProvideService (
ac accesscontrol . AccessControl ,
bus bus . Bus ,
cfg * setting . Cfg ,
dashboardStore dashboards . Store ,
2023-02-01 21:43:21 +08:00
folderStore folder . FolderStore ,
2022-11-08 21:59:55 +08:00
db db . DB , // DB for the (new) nested folder store
2022-11-23 19:16:00 +08:00
features featuremgmt . FeatureToggles ,
2023-12-05 23:13:31 +08:00
r prometheus . Registerer ,
2022-10-11 03:47:53 +08:00
) folder . Service {
2024-01-23 00:03:30 +08:00
store := ProvideStore ( db , cfg )
2023-02-08 00:27:20 +08:00
srv := & Service {
2023-01-20 00:38:07 +08:00
cfg : cfg ,
log : log . New ( "folder-service" ) ,
dashboardStore : dashboardStore ,
dashboardFolderStore : folderStore ,
store : store ,
features : features ,
accessControl : ac ,
bus : bus ,
2023-03-15 16:51:37 +08:00
db : db ,
2023-04-14 17:17:23 +08:00
registry : make ( map [ string ] folder . RegistryService ) ,
2023-12-05 23:13:31 +08:00
metrics : newFoldersMetrics ( r ) ,
2018-02-20 20:55:43 +08:00
}
2024-01-23 00:04:18 +08:00
srv . DBMigration ( db )
2023-01-26 16:21:10 +08:00
2023-03-03 23:56:33 +08:00
ac . RegisterScopeAttributeResolver ( dashboards . NewFolderNameScopeResolver ( folderStore , srv ) )
ac . RegisterScopeAttributeResolver ( dashboards . NewFolderIDScopeResolver ( folderStore , srv ) )
2023-03-02 21:09:57 +08:00
ac . RegisterScopeAttributeResolver ( dashboards . NewFolderUIDScopeResolver ( srv ) )
2023-02-08 00:27:20 +08:00
return srv
2022-11-12 22:51:46 +08:00
}
2024-01-23 00:04:18 +08:00
func ( s * Service ) DBMigration ( db db . DB ) {
s . log . Debug ( "syncing dashboard and folder tables started" )
ctx := context . Background ( )
err := db . WithDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
var err error
if db . GetDialect ( ) . DriverName ( ) == migrator . SQLite {
_ , err = sess . Exec ( `
INSERT INTO folder ( uid , org_id , title , created , updated )
SELECT uid , org_id , title , created , updated FROM dashboard WHERE is_folder = 1
ON CONFLICT DO UPDATE SET title = excluded . title , updated = excluded . updated
` )
} else if db . GetDialect ( ) . DriverName ( ) == migrator . Postgres {
_ , err = sess . Exec ( `
INSERT INTO folder ( uid , org_id , title , created , updated )
SELECT uid , org_id , title , created , updated FROM dashboard WHERE is_folder = true
ON CONFLICT ( uid , org_id ) DO UPDATE SET title = excluded . title , updated = excluded . updated
` )
} else {
_ , err = sess . Exec ( `
INSERT INTO folder ( uid , org_id , title , created , updated )
SELECT * FROM ( SELECT uid , org_id , title , created , updated FROM dashboard WHERE is_folder = 1 ) AS derived
ON DUPLICATE KEY UPDATE title = derived . title , updated = derived . updated
` )
}
if err != nil {
return err
}
_ , err = sess . Exec ( `
DELETE FROM folder WHERE NOT EXISTS
( SELECT 1 FROM dashboard WHERE dashboard . uid = folder . uid AND dashboard . org_id = folder . org_id AND dashboard . is_folder = true )
` )
return err
} )
if err != nil {
s . log . Error ( "DB migration on folder service start failed." , "err" , err )
}
s . log . Debug ( "syncing dashboard and folder tables finished" )
}
2024-01-25 15:27:13 +08:00
func ( s * Service ) GetFolders ( ctx context . Context , q folder . GetFoldersQuery ) ( [ ] * folder . Folder , error ) {
if q . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
qry := NewGetFoldersQuery ( q )
permissions := q . SignedInUser . GetPermissions ( )
folderPermissions := permissions [ dashboards . ActionFoldersRead ]
qry . ancestorUIDs = make ( [ ] string , 0 , len ( folderPermissions ) )
2024-01-27 01:12:45 +08:00
if len ( folderPermissions ) == 0 && ! q . SignedInUser . GetIsGrafanaAdmin ( ) {
return nil , nil
}
2024-01-25 15:27:13 +08:00
for _ , p := range folderPermissions {
if p == dashboards . ScopeFoldersAll {
// no need to query for folders with permissions
// the user has permission to access all folders
qry . ancestorUIDs = nil
break
}
if folderUid , found := strings . CutPrefix ( p , dashboards . ScopeFoldersPrefix ) ; found {
if ! slices . Contains ( qry . ancestorUIDs , folderUid ) {
qry . ancestorUIDs = append ( qry . ancestorUIDs , folderUid )
}
}
}
dashFolders , err := s . store . GetFolders ( ctx , qry )
if err != nil {
return nil , folder . ErrInternal . Errorf ( "failed to fetch subfolders: %w" , err )
}
return dashFolders , nil
}
2024-01-18 22:12:49 +08:00
func ( s * Service ) Get ( ctx context . Context , q * folder . GetFolderQuery ) ( * folder . Folder , error ) {
if q . SignedInUser == nil {
2022-11-23 17:13:47 +08:00
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
2022-11-11 21:28:24 +08:00
}
2024-01-18 22:12:49 +08:00
if s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) && q . UID != nil && * q . UID == folder . SharedWithMeFolderUID {
2023-12-05 23:13:31 +08:00
return folder . SharedWithMeFolder . WithURL ( ) , nil
}
2023-01-06 22:04:17 +08:00
var dashFolder * folder . Folder
var err error
switch {
2024-01-18 22:12:49 +08:00
case q . UID != nil && * q . UID != "" :
dashFolder , err = s . getFolderByUID ( ctx , q . OrgID , * q . UID )
2022-11-24 21:59:47 +08:00
if err != nil {
return nil , err
}
2023-11-15 23:30:00 +08:00
// nolint:staticcheck
2024-01-18 22:12:49 +08:00
case q . ID != nil :
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2024-01-18 22:12:49 +08:00
dashFolder , err = s . getFolderByID ( ctx , * q . ID , q . OrgID )
2022-12-15 22:34:17 +08:00
if err != nil {
return nil , err
}
2024-01-18 22:12:49 +08:00
case q . Title != nil :
2024-01-25 17:29:56 +08:00
dashFolder , err = s . getFolderByTitle ( ctx , q . OrgID , * q . Title , q . ParentUID )
2023-01-06 22:04:17 +08:00
if err != nil {
return nil , err
2022-11-11 21:28:24 +08:00
}
2023-01-06 22:04:17 +08:00
default :
return nil , folder . ErrBadRequest . Errorf ( "either on of UID, ID, Title fields must be present" )
}
2022-11-24 21:59:47 +08:00
2023-02-08 23:16:53 +08:00
if dashFolder . IsGeneral ( ) {
return dashFolder , nil
}
2023-01-06 22:04:17 +08:00
// do not get guardian by the folder ID because it differs from the nested folder ID
// and the legacy folder ID has been associated with the permissions:
// use the folde UID instead that is the same for both
2024-01-18 22:12:49 +08:00
g , err := guardian . NewByFolder ( ctx , dashFolder , dashFolder . OrgID , q . SignedInUser )
2023-01-06 22:04:17 +08:00
if err != nil {
return nil , err
2022-11-11 21:28:24 +08:00
}
2023-01-06 22:04:17 +08:00
if canView , err := g . CanView ( ) ; err != nil || ! canView {
if err != nil {
return nil , toFolderError ( err )
}
return nil , dashboards . ErrFolderAccessDenied
}
2023-11-15 04:50:27 +08:00
if ! s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) {
2023-02-09 20:19:08 +08:00
return dashFolder , nil
}
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-15 23:30:00 +08:00
// nolint:staticcheck
2024-01-18 22:12:49 +08:00
if q . ID != nil {
q . ID = nil
q . UID = & dashFolder . UID
2023-02-09 20:19:08 +08:00
}
2024-01-18 22:12:49 +08:00
f , err := s . store . Get ( ctx , * q )
2023-02-09 20:19:08 +08:00
if err != nil {
return nil , err
}
2023-01-06 22:04:17 +08:00
// always expose the dashboard store sequential ID
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-01-06 22:04:17 +08:00
f . ID = dashFolder . ID
2023-05-16 21:41:14 +08:00
f . Version = dashFolder . Version
2023-01-06 22:04:17 +08:00
return f , err
2022-11-11 21:28:24 +08:00
}
2023-12-16 01:34:08 +08:00
func ( s * Service ) GetChildren ( ctx context . Context , q * folder . GetChildrenQuery ) ( [ ] * folder . Folder , error ) {
2024-01-24 21:15:32 +08:00
defer func ( t time . Time ) {
parent := q . UID
if q . UID != folder . SharedWithMeFolderUID {
parent = "folder"
}
s . metrics . foldersGetChildrenRequestsDuration . WithLabelValues ( parent ) . Observe ( time . Since ( t ) . Seconds ( ) )
} ( time . Now ( ) )
2023-12-16 01:34:08 +08:00
if q . SignedInUser == nil {
2022-12-19 16:52:04 +08:00
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
2023-12-16 01:34:08 +08:00
if s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) && q . UID == folder . SharedWithMeFolderUID {
return s . GetSharedWithMe ( ctx , q )
2023-12-05 23:13:31 +08:00
}
2023-12-16 01:34:08 +08:00
if q . UID == "" {
return s . getRootFolders ( ctx , q )
}
2023-07-08 02:26:01 +08:00
2023-12-16 01:34:08 +08:00
// we only need to check access to the folder
// if the parent is accessible then the subfolders are accessible as well (due to inheritance)
g , err := guardian . NewByUID ( ctx , q . UID , q . OrgID , q . SignedInUser )
if err != nil {
return nil , err
}
2023-07-08 02:26:01 +08:00
2023-12-16 01:34:08 +08:00
canView , err := g . CanView ( )
if err != nil {
return nil , err
}
if ! canView {
return nil , dashboards . ErrFolderAccessDenied
2023-07-08 02:26:01 +08:00
}
2023-12-16 01:34:08 +08:00
children , err := s . store . GetChildren ( ctx , * q )
2023-01-23 20:09:09 +08:00
if err != nil {
2018-02-20 20:55:43 +08:00
return nil , err
}
2023-08-01 16:04:44 +08:00
childrenUIDs := make ( [ ] string , 0 , len ( children ) )
for _ , f := range children {
childrenUIDs = append ( childrenUIDs , f . UID )
}
2023-12-16 01:34:08 +08:00
dashFolders , err := s . dashboardFolderStore . GetFolders ( ctx , q . OrgID , childrenUIDs )
2023-08-01 16:04:44 +08:00
if err != nil {
return nil , folder . ErrInternal . Errorf ( "failed to fetch subfolders from dashboard store: %w" , err )
}
2023-01-23 20:09:09 +08:00
for _ , f := range children {
// fetch folder from dashboard store
2023-08-01 16:04:44 +08:00
dashFolder , ok := dashFolders [ f . UID ]
if ! ok {
s . log . Error ( "failed to fetch folder by UID from dashboard store" , "uid" , f . UID )
2023-01-23 20:09:09 +08:00
continue
}
2023-08-01 16:04:44 +08:00
2023-07-08 02:26:01 +08:00
// always expose the dashboard store sequential ID
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-07-08 02:26:01 +08:00
f . ID = dashFolder . ID
2023-12-16 01:34:08 +08:00
}
2023-07-08 02:26:01 +08:00
2023-12-16 01:34:08 +08:00
return children , nil
}
2018-02-20 20:55:43 +08:00
2023-12-16 01:34:08 +08:00
func ( s * Service ) getRootFolders ( ctx context . Context , q * folder . GetChildrenQuery ) ( [ ] * folder . Folder , error ) {
permissions := q . SignedInUser . GetPermissions ( )
folderPermissions := permissions [ dashboards . ActionFoldersRead ]
folderPermissions = append ( folderPermissions , permissions [ dashboards . ActionDashboardsRead ] ... )
q . FolderUIDs = make ( [ ] string , 0 , len ( folderPermissions ) )
for _ , p := range folderPermissions {
if p == dashboards . ScopeFoldersAll {
// no need to query for folders with permissions
// the user has permission to access all folders
q . FolderUIDs = nil
break
2023-01-23 20:09:09 +08:00
}
2023-12-16 01:34:08 +08:00
if folderUid , found := strings . CutPrefix ( p , dashboards . ScopeFoldersPrefix ) ; found {
if ! slices . Contains ( q . FolderUIDs , folderUid ) {
q . FolderUIDs = append ( q . FolderUIDs , folderUid )
}
2023-05-10 21:05:53 +08:00
}
2023-12-16 01:34:08 +08:00
}
children , err := s . store . GetChildren ( ctx , * q )
if err != nil {
return nil , err
}
childrenUIDs := make ( [ ] string , 0 , len ( children ) )
for _ , f := range children {
childrenUIDs = append ( childrenUIDs , f . UID )
}
dashFolders , err := s . dashboardFolderStore . GetFolders ( ctx , q . OrgID , childrenUIDs )
if err != nil {
return nil , folder . ErrInternal . Errorf ( "failed to fetch subfolders from dashboard store: %w" , err )
}
if err := concurrency . ForEachJob ( ctx , len ( children ) , runtime . NumCPU ( ) , func ( ctx context . Context , i int ) error {
f := children [ i ]
// fetch folder from dashboard store
dashFolder , ok := dashFolders [ f . UID ]
if ! ok {
s . log . Error ( "failed to fetch folder by UID from dashboard store" , "orgID" , f . OrgID , "uid" , f . UID )
2023-01-23 20:09:09 +08:00
}
2023-12-16 01:34:08 +08:00
// always expose the dashboard store sequential ID
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-12-16 01:34:08 +08:00
// nolint:staticcheck
f . ID = dashFolder . ID
return nil
} ) ; err != nil {
return nil , folder . ErrInternal . Errorf ( "failed to assign folder sequential ID: %w" , err )
2018-02-20 20:55:43 +08:00
}
2023-12-16 01:34:08 +08:00
// add "shared with me" folder on the 1st page
if ( q . Page == 0 || q . Page == 1 ) && len ( q . FolderUIDs ) != 0 {
2023-12-29 18:35:15 +08:00
children = append ( [ ] * folder . Folder { & folder . SharedWithMeFolder } , children ... )
2023-12-05 23:13:31 +08:00
}
2023-12-16 01:34:08 +08:00
return children , nil
2018-02-20 20:55:43 +08:00
}
2023-11-08 22:28:49 +08:00
// GetSharedWithMe returns folders available to user, which cannot be accessed from the root folders
2024-01-18 22:12:49 +08:00
func ( s * Service ) GetSharedWithMe ( ctx context . Context , q * folder . GetChildrenQuery ) ( [ ] * folder . Folder , error ) {
2023-12-05 23:13:31 +08:00
start := time . Now ( )
2024-01-18 22:12:49 +08:00
availableNonRootFolders , err := s . getAvailableNonRootFolders ( ctx , q . OrgID , q . SignedInUser )
2023-11-08 22:28:49 +08:00
if err != nil {
2023-12-05 23:13:31 +08:00
s . metrics . sharedWithMeFetchFoldersRequestsDuration . WithLabelValues ( "failure" ) . Observe ( time . Since ( start ) . Seconds ( ) )
2023-11-08 22:28:49 +08:00
return nil , folder . ErrInternal . Errorf ( "failed to fetch subfolders to which the user has explicit access: %w" , err )
}
2024-01-18 22:12:49 +08:00
rootFolders , err := s . GetChildren ( ctx , & folder . GetChildrenQuery { UID : "" , OrgID : q . OrgID , SignedInUser : q . SignedInUser } )
2023-11-08 22:28:49 +08:00
if err != nil {
2023-12-05 23:13:31 +08:00
s . metrics . sharedWithMeFetchFoldersRequestsDuration . WithLabelValues ( "failure" ) . Observe ( time . Since ( start ) . Seconds ( ) )
2023-11-08 22:28:49 +08:00
return nil , folder . ErrInternal . Errorf ( "failed to fetch root folders to which the user has access: %w" , err )
}
2024-01-25 20:02:32 +08:00
availableNonRootFolders = s . deduplicateAvailableFolders ( ctx , availableNonRootFolders , rootFolders , q . OrgID )
2023-12-05 23:13:31 +08:00
s . metrics . sharedWithMeFetchFoldersRequestsDuration . WithLabelValues ( "success" ) . Observe ( time . Since ( start ) . Seconds ( ) )
2023-11-08 22:28:49 +08:00
return availableNonRootFolders , nil
}
func ( s * Service ) getAvailableNonRootFolders ( ctx context . Context , orgID int64 , user identity . Requester ) ( [ ] * folder . Folder , error ) {
permissions := user . GetPermissions ( )
2023-12-05 23:13:31 +08:00
folderPermissions := permissions [ dashboards . ActionFoldersRead ]
folderPermissions = append ( folderPermissions , permissions [ dashboards . ActionDashboardsRead ] ... )
2023-11-08 22:28:49 +08:00
nonRootFolders := make ( [ ] * folder . Folder , 0 )
2023-12-16 01:34:08 +08:00
folderUids := make ( [ ] string , 0 , len ( folderPermissions ) )
2023-11-08 22:28:49 +08:00
for _ , p := range folderPermissions {
2023-12-05 23:13:31 +08:00
if folderUid , found := strings . CutPrefix ( p , dashboards . ScopeFoldersPrefix ) ; found {
2023-11-08 22:28:49 +08:00
if ! slices . Contains ( folderUids , folderUid ) {
folderUids = append ( folderUids , folderUid )
}
}
}
if len ( folderUids ) == 0 {
return nonRootFolders , nil
}
2024-01-25 20:02:32 +08:00
dashFolders , err := s . GetFolders ( ctx , folder . GetFoldersQuery {
UIDs : folderUids ,
OrgID : orgID ,
SignedInUser : user ,
WithFullpathUIDs : true ,
} )
2023-11-08 22:28:49 +08:00
if err != nil {
return nil , folder . ErrInternal . Errorf ( "failed to fetch subfolders: %w" , err )
}
for _ , f := range dashFolders {
if f . ParentUID != "" {
nonRootFolders = append ( nonRootFolders , f )
}
}
return nonRootFolders , nil
}
2024-01-25 20:02:32 +08:00
func ( s * Service ) deduplicateAvailableFolders ( ctx context . Context , folders [ ] * folder . Folder , rootFolders [ ] * folder . Folder , orgID int64 ) [ ] * folder . Folder {
2023-11-08 22:28:49 +08:00
allFolders := append ( folders , rootFolders ... )
foldersDedup := make ( [ ] * folder . Folder , 0 )
2024-01-25 20:02:32 +08:00
2023-11-08 22:28:49 +08:00
for _ , f := range folders {
isSubfolder := slices . ContainsFunc ( allFolders , func ( folder * folder . Folder ) bool {
return f . ParentUID == folder . UID
} )
if ! isSubfolder {
2024-01-25 20:02:32 +08:00
// Get parents UIDs
parentUIDs := make ( [ ] string , 0 )
pathUIDs := strings . Split ( f . FullpathUIDs , "/" )
for _ , p := range pathUIDs {
if p != "" && p != f . UID {
parentUIDs = append ( parentUIDs , p )
}
2023-11-08 22:28:49 +08:00
}
2024-01-25 20:02:32 +08:00
for _ , parentUID := range parentUIDs {
2023-11-08 22:28:49 +08:00
contains := slices . ContainsFunc ( allFolders , func ( f * folder . Folder ) bool {
2024-01-25 20:02:32 +08:00
return f . UID == parentUID
2023-11-08 22:28:49 +08:00
} )
if contains {
isSubfolder = true
break
}
}
}
if ! isSubfolder {
foldersDedup = append ( foldersDedup , f )
}
}
return foldersDedup
}
2023-01-26 16:21:10 +08:00
func ( s * Service ) GetParents ( ctx context . Context , q folder . GetParentsQuery ) ( [ ] * folder . Folder , error ) {
2023-11-15 04:50:27 +08:00
if ! s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) {
2023-01-26 16:21:10 +08:00
return nil , nil
}
2023-12-05 23:13:31 +08:00
if q . UID == folder . SharedWithMeFolderUID {
return [ ] * folder . Folder { & folder . SharedWithMeFolder } , nil
}
2023-01-26 16:21:10 +08:00
return s . store . GetParents ( ctx , q )
}
2023-02-09 20:19:08 +08:00
func ( s * Service ) getFolderByID ( ctx context . Context , id int64 , orgID int64 ) ( * folder . Folder , error ) {
2021-07-01 16:40:38 +08:00
if id == 0 {
2023-02-08 23:16:53 +08:00
return & folder . GeneralFolder , nil
2021-07-01 16:40:38 +08:00
}
2022-10-26 22:15:14 +08:00
2023-02-09 20:19:08 +08:00
return s . dashboardFolderStore . GetFolderByID ( ctx , orgID , id )
2018-02-20 20:55:43 +08:00
}
2023-02-09 20:19:08 +08:00
func ( s * Service ) getFolderByUID ( ctx context . Context , orgID int64 , uid string ) ( * folder . Folder , error ) {
return s . dashboardFolderStore . GetFolderByUID ( ctx , orgID , uid )
2021-04-01 16:11:45 +08:00
}
2024-01-25 17:29:56 +08:00
func ( s * Service ) getFolderByTitle ( ctx context . Context , orgID int64 , title string , parentUID * string ) ( * folder . Folder , error ) {
return s . dashboardFolderStore . GetFolderByTitle ( ctx , orgID , title , parentUID )
2018-02-20 20:55:43 +08:00
}
2022-11-10 17:41:03 +08:00
func ( s * Service ) Create ( ctx context . Context , cmd * folder . CreateFolderCommand ) ( * folder . Folder , error ) {
2022-11-23 22:44:45 +08:00
logger := s . log . FromContext ( ctx )
2023-09-06 17:16:10 +08:00
if cmd . SignedInUser == nil || cmd . SignedInUser . IsNil ( ) {
2023-03-20 19:04:22 +08:00
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
2023-10-24 15:04:45 +08:00
dashFolder := dashboards . NewDashboardFolder ( cmd . Title )
dashFolder . OrgID = cmd . OrgID
2023-11-15 04:50:27 +08:00
if s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) && cmd . ParentUID != "" {
2023-03-20 19:04:22 +08:00
// Check that the user is allowed to create a subfolder in this folder
evaluator := accesscontrol . EvalPermission ( dashboards . ActionFoldersWrite , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( cmd . ParentUID ) )
hasAccess , evalErr := s . accessControl . Evaluate ( ctx , cmd . SignedInUser , evaluator )
if evalErr != nil {
return nil , evalErr
}
if ! hasAccess {
return nil , dashboards . ErrFolderAccessDenied
}
2023-10-24 15:04:45 +08:00
dashFolder . FolderUID = cmd . ParentUID
2023-03-20 19:04:22 +08:00
}
2023-12-05 23:13:31 +08:00
if s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) && cmd . UID == folder . SharedWithMeFolderUID {
return nil , folder . ErrBadRequest . Errorf ( "cannot create folder with UID %s" , folder . SharedWithMeFolderUID )
}
2022-11-10 17:41:03 +08:00
trimmedUID := strings . TrimSpace ( cmd . UID )
2022-03-30 21:14:26 +08:00
if trimmedUID == accesscontrol . GeneralFolderUID {
2022-06-30 21:31:54 +08:00
return nil , dashboards . ErrFolderInvalidUID
2022-03-30 21:14:26 +08:00
}
2023-01-16 23:33:55 +08:00
dashFolder . SetUID ( trimmedUID )
2022-11-10 17:41:03 +08:00
2022-11-23 17:13:47 +08:00
user := cmd . SignedInUser
2023-09-06 19:37:54 +08:00
userID := int64 ( 0 )
var err error
2023-08-30 22:51:18 +08:00
namespaceID , userIDstr := user . GetNamespacedID ( )
2023-09-06 19:37:54 +08:00
if namespaceID != identity . NamespaceUser && namespaceID != identity . NamespaceServiceAccount {
s . log . Debug ( "User does not belong to a user or service account namespace, using 0 as user ID" , "namespaceID" , namespaceID , "userID" , userIDstr )
} else {
userID , err = identity . IntIdentifier ( namespaceID , userIDstr )
if err != nil {
s . log . Debug ( "failed to parse user ID" , "namespaceID" , namespaceID , "userID" , userIDstr , "error" , err )
}
2023-08-30 22:51:18 +08:00
}
2021-03-17 23:06:10 +08:00
if userID == 0 {
userID = - 1
}
dashFolder . CreatedBy = userID
dashFolder . UpdatedBy = userID
dashFolder . UpdateSlug ( )
2018-02-20 20:55:43 +08:00
2022-02-16 21:15:44 +08:00
dto := & dashboards . SaveDashboardDTO {
2018-02-20 20:55:43 +08:00
Dashboard : dashFolder ,
2023-01-16 23:33:55 +08:00
OrgID : cmd . OrgID ,
2022-02-16 21:15:44 +08:00
User : user ,
2018-02-20 20:55:43 +08:00
}
2023-10-24 15:04:45 +08:00
saveDashboardCmd , err := s . buildSaveDashboardCommand ( ctx , dto )
2018-02-20 20:55:43 +08:00
if err != nil {
2021-03-17 23:06:10 +08:00
return nil , toFolderError ( err )
2018-02-20 20:55:43 +08:00
}
2023-11-22 05:06:20 +08:00
var nestedFolder * folder . Folder
var dash * dashboards . Dashboard
err = s . db . InTransaction ( ctx , func ( ctx context . Context ) error {
if dash , err = s . dashboardStore . SaveDashboard ( ctx , * saveDashboardCmd ) ; err != nil {
return toFolderError ( err )
}
2018-02-20 20:55:43 +08:00
2023-11-22 05:06:20 +08:00
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 ,
2024-01-23 00:03:30 +08:00
Title : dashFolder . Title ,
2023-11-22 05:06:20 +08:00
Description : cmd . Description ,
ParentUID : cmd . ParentUID ,
}
2018-02-20 20:55:43 +08:00
2023-11-22 05:06:20 +08:00
if nestedFolder , err = s . nestedFolderCreate ( ctx , cmd ) ; err != nil {
logger . Error ( "error saving folder to nested folder store" , "error" , err )
return err
2022-11-08 21:59:55 +08:00
}
2023-11-22 05:06:20 +08:00
return nil
} )
if err != nil {
return nil , err
2022-11-08 21:59:55 +08:00
}
2022-11-24 21:59:47 +08:00
2023-01-16 23:33:55 +08:00
f := dashboards . FromDashboard ( dash )
2022-11-24 21:59:47 +08:00
if nestedFolder != nil && nestedFolder . ParentUID != "" {
f . ParentUID = nestedFolder . ParentUID
}
return f , nil
2018-02-20 20:55:43 +08:00
}
2022-12-20 21:00:33 +08:00
func ( s * Service ) Update ( ctx context . Context , cmd * folder . UpdateFolderCommand ) ( * folder . Folder , error ) {
2023-11-10 20:03:00 +08:00
logger := s . log . FromContext ( ctx )
2022-12-20 21:00:33 +08:00
if cmd . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
user := cmd . SignedInUser
2023-11-22 05:06:20 +08:00
var dashFolder , foldr * folder . Folder
var err error
err = s . db . InTransaction ( ctx , func ( ctx context . Context ) error {
if dashFolder , err = s . legacyUpdate ( ctx , cmd ) ; err != nil {
return err
}
2022-11-10 21:28:55 +08:00
2023-11-22 05:06:20 +08:00
if foldr , err = s . store . Update ( ctx , folder . UpdateFolderCommand {
UID : cmd . UID ,
OrgID : cmd . OrgID ,
2024-01-23 00:03:30 +08:00
NewTitle : & dashFolder . Title ,
2023-11-22 05:06:20 +08:00
NewDescription : cmd . NewDescription ,
SignedInUser : user ,
} ) ; err != nil {
return err
2023-11-10 20:03:00 +08:00
}
2023-11-22 05:06:20 +08:00
if cmd . NewTitle != nil {
namespace , id := cmd . SignedInUser . GetNamespacedID ( )
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-22 05:06:20 +08:00
if err := s . bus . Publish ( context . Background ( ) , & events . FolderTitleUpdated {
Timestamp : foldr . Updated ,
Title : foldr . Title ,
ID : dashFolder . ID , // nolint:staticcheck
UID : dashFolder . UID ,
OrgID : cmd . OrgID ,
} ) ; err != nil {
logger . Error ( "failed to publish FolderTitleUpdated event" , "folder" , foldr . Title , "user" , id , "namespace" , namespace , "error" , err )
return err
}
}
return nil
} )
if err != nil {
logger . Error ( "folder update failed" , "folderUID" , cmd . UID , "error" , err )
2023-01-06 22:04:17 +08:00
return nil , err
}
2023-11-22 05:06:20 +08:00
if ! s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) {
return dashFolder , nil
}
2023-01-06 22:04:17 +08:00
// always expose the dashboard store sequential ID
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-01-06 22:04:17 +08:00
foldr . ID = dashFolder . ID
2023-05-16 21:41:14 +08:00
foldr . Version = dashFolder . Version
2023-01-06 22:04:17 +08:00
2022-11-10 21:28:55 +08:00
return foldr , nil
}
2022-12-20 21:00:33 +08:00
func ( s * Service ) legacyUpdate ( ctx context . Context , cmd * folder . UpdateFolderCommand ) ( * folder . Folder , error ) {
2022-11-23 22:44:45 +08:00
logger := s . log . FromContext ( ctx )
2023-01-16 23:33:55 +08:00
query := dashboards . GetDashboardQuery { OrgID : cmd . OrgID , UID : cmd . UID }
2023-01-25 17:36:26 +08:00
queryResult , err := s . dashboardStore . GetDashboard ( ctx , & query )
2022-11-10 21:28:55 +08:00
if err != nil {
return nil , toFolderError ( err )
2018-02-20 20:55:43 +08:00
}
2023-01-25 17:36:26 +08:00
dashFolder := queryResult
2023-10-24 15:04:45 +08:00
if cmd . NewParentUID != nil {
dashFolder . FolderUID = * cmd . NewParentUID
}
2022-03-24 02:40:22 +08:00
if ! dashFolder . IsFolder {
2022-11-10 21:28:55 +08:00
return nil , dashboards . ErrFolderNotFound
2022-03-24 02:40:22 +08:00
}
2022-12-20 21:00:33 +08:00
if cmd . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
2023-10-06 21:02:34 +08:00
var userID int64
namespace , id := cmd . SignedInUser . GetNamespacedID ( )
if namespace == identity . NamespaceUser || namespace == identity . NamespaceServiceAccount {
userID , err = identity . IntIdentifier ( namespace , id )
if err != nil {
logger . Error ( "failed to parse user ID" , "namespace" , namespace , "userID" , id , "error" , err )
}
}
prepareForUpdate ( dashFolder , cmd . OrgID , userID , cmd )
2018-02-20 20:55:43 +08:00
2022-02-16 21:15:44 +08:00
dto := & dashboards . SaveDashboardDTO {
2018-02-20 20:55:43 +08:00
Dashboard : dashFolder ,
2023-01-16 23:33:55 +08:00
OrgID : cmd . OrgID ,
2022-12-20 21:00:33 +08:00
User : cmd . SignedInUser ,
2018-02-20 20:55:43 +08:00
Overwrite : cmd . Overwrite ,
}
2023-10-24 15:04:45 +08:00
saveDashboardCmd , err := s . buildSaveDashboardCommand ( ctx , dto )
2018-02-20 20:55:43 +08:00
if err != nil {
2022-11-10 21:28:55 +08:00
return nil , toFolderError ( err )
2018-02-20 20:55:43 +08:00
}
2022-10-11 03:47:53 +08:00
dash , err := s . dashboardStore . SaveDashboard ( ctx , * saveDashboardCmd )
2018-02-20 20:55:43 +08:00
if err != nil {
2022-11-10 21:28:55 +08:00
return nil , toFolderError ( err )
2018-02-20 20:55:43 +08:00
}
2022-11-11 21:28:24 +08:00
var foldr * folder . Folder
2023-01-20 00:38:07 +08:00
foldr , err = s . dashboardFolderStore . GetFolderByID ( ctx , cmd . OrgID , dash . ID )
2018-02-20 20:55:43 +08:00
if err != nil {
2022-11-10 21:28:55 +08:00
return nil , err
2018-02-20 20:55:43 +08:00
}
2022-06-18 01:10:49 +08:00
2022-11-10 21:28:55 +08:00
return foldr , nil
2018-02-20 20:55:43 +08:00
}
2022-12-20 21:00:33 +08:00
// prepareForUpdate updates an existing dashboard model from command into model for folder update
2023-01-16 23:33:55 +08:00
func prepareForUpdate ( dashFolder * dashboards . Dashboard , orgId int64 , userId int64 , cmd * folder . UpdateFolderCommand ) {
dashFolder . OrgID = orgId
2022-12-20 21:00:33 +08:00
title := dashFolder . Title
if cmd . NewTitle != nil && * cmd . NewTitle != "" {
title = * cmd . NewTitle
}
dashFolder . Title = strings . TrimSpace ( title )
dashFolder . Data . Set ( "title" , dashFolder . Title )
dashFolder . SetVersion ( cmd . Version )
dashFolder . IsFolder = true
if userId == 0 {
userId = - 1
}
dashFolder . UpdatedBy = userId
dashFolder . UpdateSlug ( )
}
2022-12-20 23:38:09 +08:00
func ( s * Service ) Delete ( ctx context . Context , cmd * folder . DeleteFolderCommand ) error {
2022-11-23 22:44:45 +08:00
logger := s . log . FromContext ( ctx )
if cmd . SignedInUser == nil {
return folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
2023-04-14 17:17:23 +08:00
if cmd . UID == "" {
return folder . ErrBadRequest . Errorf ( "missing UID" )
}
if cmd . OrgID < 1 {
return folder . ErrBadRequest . Errorf ( "invalid orgID" )
}
2023-07-08 02:26:01 +08:00
guard , err := guardian . NewByUID ( ctx , cmd . UID , cmd . OrgID , cmd . SignedInUser )
if err != nil {
return err
}
if canSave , err := guard . CanDelete ( ) ; err != nil || ! canSave {
if err != nil {
return toFolderError ( err )
}
return dashboards . ErrFolderAccessDenied
}
2024-01-31 00:26:34 +08:00
folders := [ ] string { cmd . UID }
2023-07-08 02:26:01 +08:00
err = s . db . InTransaction ( ctx , func ( ctx context . Context ) error {
2024-01-31 00:26:34 +08:00
descendants , err := s . nestedFolderDelete ( ctx , cmd )
2022-11-23 22:44:45 +08:00
2023-11-10 20:03:00 +08:00
if err != nil {
logger . Error ( "the delete folder on folder table failed with err: " , "error" , err )
return err
2022-11-10 16:42:32 +08:00
}
2024-01-31 00:26:34 +08:00
folders = append ( folders , descendants ... )
2022-11-10 16:42:32 +08:00
2024-01-31 00:26:34 +08:00
if cmd . ForceDeleteRules {
if err := s . deleteChildrenInFolder ( ctx , cmd . OrgID , folders , cmd . SignedInUser ) ; err != nil {
return err
2023-03-15 16:51:37 +08:00
}
2024-01-31 00:26:34 +08:00
} else {
alertRuleSrv , ok := s . registry [ entity . StandardKindAlertRule ]
if ! ok {
return folder . ErrInternal . Errorf ( "no alert rule service found in registry" )
2023-04-14 17:17:23 +08:00
}
2024-01-31 00:26:34 +08:00
alertRulesInFolder , err := alertRuleSrv . CountInFolders ( ctx , cmd . OrgID , folders , cmd . SignedInUser )
if err != nil {
s . log . Error ( "failed to count alert rules in folder" , "error" , err )
2023-03-15 16:51:37 +08:00
return err
}
2024-01-31 00:26:34 +08:00
if alertRulesInFolder > 0 {
return folder . ErrFolderNotEmpty . Errorf ( "folder contains %d alert rules" , alertRulesInFolder )
}
}
if err = s . legacyDelete ( ctx , cmd , folders ) ; err != nil {
return err
2018-02-20 20:55:43 +08:00
}
2024-01-31 00:26:34 +08:00
2023-03-15 16:51:37 +08:00
return nil
} )
2018-02-20 20:55:43 +08:00
2023-03-15 16:51:37 +08:00
return err
2022-12-20 23:38:09 +08:00
}
2024-01-31 00:26:34 +08:00
func ( s * Service ) deleteChildrenInFolder ( ctx context . Context , orgID int64 , folderUIDs [ ] string , user identity . Requester ) error {
2023-04-14 17:17:23 +08:00
for _ , v := range s . registry {
2024-01-31 00:26:34 +08:00
if err := v . DeleteInFolders ( ctx , orgID , folderUIDs , user ) ; err != nil {
2023-04-14 17:17:23 +08:00
return err
}
}
return nil
}
2024-01-31 00:26:34 +08:00
func ( s * Service ) legacyDelete ( ctx context . Context , cmd * folder . DeleteFolderCommand , folderUIDs [ ] string ) error {
// TODO use bulk delete
for _ , folderUID := range folderUIDs {
deleteCmd := dashboards . DeleteDashboardCommand { OrgID : cmd . OrgID , UID : folderUID , ForceDeleteFolderRules : cmd . ForceDeleteRules }
2022-03-22 21:36:50 +08:00
2024-01-31 00:26:34 +08:00
if err := s . dashboardStore . DeleteDashboard ( ctx , & deleteCmd ) ; err != nil {
return toFolderError ( err )
}
2018-02-20 20:55:43 +08:00
}
2022-11-10 16:42:32 +08:00
return nil
2018-02-20 20:55:43 +08:00
}
2022-11-08 18:33:13 +08:00
func ( s * Service ) Move ( ctx context . Context , cmd * folder . MoveFolderCommand ) ( * folder . Folder , error ) {
2022-11-23 17:13:47 +08:00
if cmd . SignedInUser == nil {
return nil , folder . ErrBadRequest . Errorf ( "missing signed in user" )
}
2023-03-20 19:04:22 +08:00
// Check that the user is allowed to move the folder to the destination folder
2023-07-10 20:14:21 +08:00
var evaluator accesscontrol . Evaluator
if cmd . NewParentUID != "" {
evaluator = accesscontrol . EvalPermission ( dashboards . ActionFoldersWrite , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( cmd . NewParentUID ) )
2023-03-20 19:04:22 +08:00
} else {
2023-07-10 20:14:21 +08:00
// Evaluate folder creation permission when moving folder to the root level
evaluator = accesscontrol . EvalPermission ( dashboards . ActionFoldersCreate )
}
hasAccess , evalErr := s . accessControl . Evaluate ( ctx , cmd . SignedInUser , evaluator )
if evalErr != nil {
return nil , evalErr
}
if ! hasAccess {
return nil , dashboards . ErrFolderAccessDenied
2022-12-08 21:49:17 +08:00
}
// here we get the folder, we need to get the height of current folder
// and the depth of the new parent folder, the sum can't bypass 8
2022-12-20 21:00:33 +08:00
folderHeight , err := s . store . GetHeight ( ctx , cmd . UID , cmd . OrgID , & cmd . NewParentUID )
2022-12-08 21:49:17 +08:00
if err != nil {
return nil , err
}
2022-12-20 23:38:09 +08:00
parents , err := s . store . GetParents ( ctx , folder . GetParentsQuery { UID : cmd . NewParentUID , OrgID : cmd . OrgID } )
2022-12-08 21:49:17 +08:00
if err != nil {
return nil , err
}
2023-11-15 17:25:40 +08:00
// height of the folder that is being moved + this current folder itself + depth of the NewParent folder should be less than or equal MaxNestedFolderDepth
if folderHeight + len ( parents ) + 1 > folder . MaxNestedFolderDepth {
2023-04-20 22:47:51 +08:00
return nil , folder . ErrMaximumDepthReached . Errorf ( "failed to move folder" )
2022-12-08 21:49:17 +08:00
}
// if the current folder is already a parent of newparent, we should return error
for _ , parent := range parents {
2022-12-20 21:00:33 +08:00
if parent . UID == cmd . UID {
2023-10-24 15:04:45 +08:00
return nil , folder . ErrCircularReference . Errorf ( "failed to move folder" )
2022-12-08 21:49:17 +08:00
}
}
2023-04-04 02:24:36 +08:00
newParentUID := ""
if cmd . NewParentUID != "" {
newParentUID = cmd . NewParentUID
}
2023-10-24 15:04:45 +08:00
var f * folder . Folder
if err := s . db . InTransaction ( ctx , func ( ctx context . Context ) error {
if f , err = s . store . Update ( ctx , folder . UpdateFolderCommand {
UID : cmd . UID ,
OrgID : cmd . OrgID ,
NewParentUID : & newParentUID ,
SignedInUser : cmd . SignedInUser ,
} ) ; err != nil {
2024-01-25 17:29:56 +08:00
if s . db . GetDialect ( ) . IsUniqueConstraintViolation ( err ) {
return folder . ErrConflict . Errorf ( "%w" , dashboards . ErrFolderSameNameExists )
}
2023-10-24 15:04:45 +08:00
return folder . ErrInternal . Errorf ( "failed to move folder: %w" , err )
}
if _ , err := s . legacyUpdate ( ctx , & folder . UpdateFolderCommand {
UID : cmd . UID ,
OrgID : cmd . OrgID ,
NewParentUID : & newParentUID ,
SignedInUser : cmd . SignedInUser ,
// bypass optimistic locking used for dashboards
Overwrite : true ,
} ) ; err != nil {
2024-01-25 17:29:56 +08:00
if s . db . GetDialect ( ) . IsUniqueConstraintViolation ( err ) {
return folder . ErrConflict . Errorf ( "%w" , dashboards . ErrFolderSameNameExists )
}
2023-10-24 15:04:45 +08:00
return folder . ErrInternal . Errorf ( "failed to move legacy folder: %w" , err )
}
return nil
} ) ; err != nil {
return nil , err
}
return f , nil
2022-11-08 18:33:13 +08:00
}
2023-03-15 16:51:37 +08:00
// nestedFolderDelete inspects the folder referenced by the cmd argument, deletes all the entries for
// its descendant folders (folders which are nested within it either directly or indirectly) from
// the folder store and returns the UIDs for all its descendants.
func ( s * Service ) nestedFolderDelete ( ctx context . Context , cmd * folder . DeleteFolderCommand ) ( [ ] string , error ) {
2022-12-01 18:27:40 +08:00
logger := s . log . FromContext ( ctx )
2024-01-31 00:26:34 +08:00
descendantUIDs := [ ] string { }
2022-11-23 17:13:47 +08:00
if cmd . SignedInUser == nil {
2024-01-31 00:26:34 +08:00
return descendantUIDs , folder . ErrBadRequest . Errorf ( "missing signed in user" )
2022-11-23 17:13:47 +08:00
}
2022-11-10 16:42:32 +08:00
_ , err := s . Get ( ctx , & folder . GetFolderQuery {
2022-11-23 17:13:47 +08:00
UID : & cmd . UID ,
OrgID : cmd . OrgID ,
SignedInUser : cmd . SignedInUser ,
2022-11-08 18:33:13 +08:00
} )
if err != nil {
2024-01-31 00:26:34 +08:00
return descendantUIDs , err
2022-11-10 16:42:32 +08:00
}
2024-01-31 00:26:34 +08:00
descendants , err := s . store . GetDescendants ( ctx , cmd . OrgID , cmd . UID )
2022-11-10 16:42:32 +08:00
if err != nil {
2024-01-31 00:26:34 +08:00
logger . Error ( "failed to get descendant folders" , "error" , err )
return descendantUIDs , err
2022-11-08 18:33:13 +08:00
}
2023-04-14 17:17:23 +08:00
2024-01-31 00:26:34 +08:00
for _ , f := range descendants {
descendantUIDs = append ( descendantUIDs , f . UID )
}
logger . Info ( "deleting folder and its descendants" , "org_id" , cmd . OrgID , "uid" , cmd . UID )
toDelete := append ( descendantUIDs , cmd . UID )
err = s . store . Delete ( ctx , toDelete , cmd . OrgID )
2022-11-08 18:33:13 +08:00
if err != nil {
2022-12-01 18:27:40 +08:00
logger . Info ( "failed deleting folder" , "org_id" , cmd . OrgID , "uid" , cmd . UID , "err" , err )
2024-01-31 00:26:34 +08:00
return descendantUIDs , err
2022-11-08 18:33:13 +08:00
}
2024-01-31 00:26:34 +08:00
return descendantUIDs , nil
2022-11-08 18:33:13 +08:00
}
2024-01-18 22:12:49 +08:00
func ( s * Service ) GetDescendantCounts ( ctx context . Context , q * folder . GetDescendantCountsQuery ) ( folder . DescendantCounts , error ) {
2023-04-27 23:00:09 +08:00
logger := s . log . FromContext ( ctx )
2024-01-18 22:12:49 +08:00
if q . SignedInUser == nil {
2023-04-24 21:57:28 +08:00
return nil , folder . ErrBadRequest . Errorf ( "missing signed-in user" )
}
2024-01-31 00:26:34 +08:00
if q . UID == nil || * q . UID == "" {
2023-04-24 21:57:28 +08:00
return nil , folder . ErrBadRequest . Errorf ( "missing UID" )
}
2024-01-18 22:12:49 +08:00
if q . OrgID < 1 {
2023-04-24 21:57:28 +08:00
return nil , folder . ErrBadRequest . Errorf ( "invalid orgID" )
}
2024-01-31 00:26:34 +08:00
folders := [ ] string { * q . UID }
2023-04-27 23:00:09 +08:00
countsMap := make ( folder . DescendantCounts , len ( s . registry ) + 1 )
2023-11-15 04:50:27 +08:00
if s . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) {
2024-01-31 00:26:34 +08:00
descendantFolders , err := s . store . GetDescendants ( ctx , q . OrgID , * q . UID )
2023-04-24 21:57:28 +08:00
if err != nil {
2024-01-31 00:26:34 +08:00
logger . Error ( "failed to get descendant folders" , "error" , err )
2023-04-24 21:57:28 +08:00
return nil , err
}
2024-01-31 00:26:34 +08:00
for _ , f := range descendantFolders {
folders = append ( folders , f . UID )
2023-04-27 23:00:09 +08:00
}
2024-01-31 00:26:34 +08:00
countsMap [ entity . StandardKindFolder ] = int64 ( len ( descendantFolders ) )
2023-04-27 23:00:09 +08:00
}
2024-01-31 00:26:34 +08:00
for _ , v := range s . registry {
c , err := v . CountInFolders ( ctx , q . OrgID , folders , q . SignedInUser )
2023-04-27 23:00:09 +08:00
if err != nil {
2024-01-31 00:26:34 +08:00
logger . Error ( "failed to count folder descendants" , "error" , err )
2023-04-27 23:00:09 +08:00
return nil , err
}
2024-01-31 00:26:34 +08:00
countsMap [ v . Kind ( ) ] = c
2023-04-27 23:00:09 +08:00
}
2024-01-31 00:26:34 +08:00
return countsMap , nil
2023-04-27 23:00:09 +08:00
}
2023-10-24 15:04:45 +08:00
// buildSaveDashboardCommand is a simplified version on DashboardServiceImpl.buildSaveDashboardCommand
2023-01-18 23:47:59 +08:00
// keeping only the meaningful functionality for folders
2023-10-24 15:04:45 +08:00
func ( s * Service ) buildSaveDashboardCommand ( ctx context . Context , dto * dashboards . SaveDashboardDTO ) ( * dashboards . SaveDashboardCommand , error ) {
2023-01-18 23:47:59 +08:00
dash := dto . Dashboard
dash . OrgID = dto . OrgID
dash . Title = strings . TrimSpace ( dash . Title )
dash . Data . Set ( "title" , dash . Title )
dash . SetUID ( strings . TrimSpace ( dash . UID ) )
if dash . Title == "" {
return nil , dashboards . ErrDashboardTitleEmpty
}
2023-11-16 19:11:35 +08:00
if strings . EqualFold ( dash . Title , dashboards . RootFolderName ) {
return nil , dashboards . ErrDashboardFolderNameExists
2023-01-18 23:47:59 +08:00
}
2023-11-16 19:11:35 +08:00
if dash . FolderUID != "" {
if _ , err := s . dashboardFolderStore . GetFolderByUID ( ctx , dash . OrgID , dash . FolderUID ) ; err != nil {
return nil , err
}
2023-01-18 23:47:59 +08:00
}
if ! util . IsValidShortUID ( dash . UID ) {
return nil , dashboards . ErrDashboardInvalidUid
} else if util . IsShortUIDTooLong ( dash . UID ) {
return nil , dashboards . ErrDashboardUidTooLong
}
2023-03-20 19:04:22 +08:00
_ , err := s . dashboardStore . ValidateDashboardBeforeSave ( ctx , dash , dto . Overwrite )
2023-01-18 23:47:59 +08:00
if err != nil {
return nil , err
}
guard , err := getGuardianForSavePermissionCheck ( ctx , dash , dto . User )
if err != nil {
return nil , err
}
if dash . ID == 0 {
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-15 23:28:50 +08:00
// nolint:staticcheck
2023-01-18 23:47:59 +08:00
if canCreate , err := guard . CanCreate ( dash . FolderID , dash . IsFolder ) ; err != nil || ! canCreate {
if err != nil {
return nil , err
}
return nil , dashboards . ErrDashboardUpdateAccessDenied
}
} else {
if canSave , err := guard . CanSave ( ) ; err != nil || ! canSave {
if err != nil {
return nil , err
}
return nil , dashboards . ErrDashboardUpdateAccessDenied
}
}
2023-09-06 19:37:54 +08:00
userID := int64 ( 0 )
2023-08-30 22:51:18 +08:00
namespaceID , userIDstr := dto . User . GetNamespacedID ( )
2023-09-06 19:37:54 +08:00
if namespaceID != identity . NamespaceUser && namespaceID != identity . NamespaceServiceAccount {
s . log . Warn ( "User does not belong to a user or service account namespace, using 0 as user ID" , "namespaceID" , namespaceID , "userID" , userIDstr )
} else {
userID , err = identity . IntIdentifier ( namespaceID , userIDstr )
if err != nil {
s . log . Warn ( "failed to parse user ID" , "namespaceID" , namespaceID , "userID" , userIDstr , "error" , err )
}
2023-08-30 22:51:18 +08:00
}
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-01-18 23:47:59 +08:00
cmd := & dashboards . SaveDashboardCommand {
Dashboard : dash . Data ,
Message : dto . Message ,
OrgID : dto . OrgID ,
Overwrite : dto . Overwrite ,
2023-08-30 22:51:18 +08:00
UserID : userID ,
2023-11-15 23:28:50 +08:00
FolderID : dash . FolderID , // nolint:staticcheck
2023-10-24 15:04:45 +08:00
FolderUID : dash . FolderUID ,
2023-01-18 23:47:59 +08:00
IsFolder : dash . IsFolder ,
PluginID : dash . PluginID ,
}
if ! dto . UpdatedAt . IsZero ( ) {
cmd . UpdatedAt = dto . UpdatedAt
}
return cmd , nil
}
// getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck()
2023-08-30 22:51:18 +08:00
func getGuardianForSavePermissionCheck ( ctx context . Context , d * dashboards . Dashboard , user identity . Requester ) ( guardian . DashboardGuardian , error ) {
2023-01-18 23:47:59 +08:00
newDashboard := d . ID == 0
if newDashboard {
// if it's a new dashboard/folder check the parent folder permissions
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2023-11-15 23:28:50 +08:00
// nolint:staticcheck
2023-01-18 23:47:59 +08:00
guard , err := guardian . New ( ctx , d . FolderID , d . OrgID , user )
if err != nil {
return nil , err
}
return guard , nil
}
guard , err := guardian . NewByDashboard ( ctx , d , d . OrgID , user )
if err != nil {
return nil , err
}
return guard , nil
2022-02-16 21:15:44 +08:00
}
2022-11-24 21:59:47 +08:00
func ( s * Service ) nestedFolderCreate ( ctx context . Context , cmd * folder . CreateFolderCommand ) ( * folder . Folder , error ) {
2022-11-23 22:44:45 +08:00
if cmd . ParentUID != "" {
2022-12-15 00:07:55 +08:00
if err := s . validateParent ( ctx , cmd . OrgID , cmd . ParentUID , cmd . UID ) ; err != nil {
2022-11-24 21:59:47 +08:00
return nil , err
2022-11-23 22:44:45 +08:00
}
}
2022-11-24 21:59:47 +08:00
return s . store . Create ( ctx , * cmd )
2022-11-23 22:44:45 +08:00
}
2022-12-15 00:07:55 +08:00
func ( s * Service ) validateParent ( ctx context . Context , orgID int64 , parentUID string , UID string ) error {
2022-11-23 22:44:45 +08:00
ancestors , err := s . store . GetParents ( ctx , folder . GetParentsQuery { UID : parentUID , OrgID : orgID } )
if err != nil {
2022-11-25 02:28:53 +08:00
return fmt . Errorf ( "failed to get parents: %w" , err )
2022-11-23 22:44:45 +08:00
}
2023-11-15 17:25:40 +08:00
if len ( ancestors ) >= folder . MaxNestedFolderDepth {
2023-04-20 22:47:51 +08:00
return folder . ErrMaximumDepthReached . Errorf ( "failed to validate parent folder" )
2022-11-23 22:44:45 +08:00
}
2022-12-15 00:07:55 +08:00
// Create folder under itself is not allowed
if parentUID == UID {
return folder . ErrCircularReference
}
// check there is no circular reference
for _ , ancestor := range ancestors {
if ancestor . UID == UID {
return folder . ErrCircularReference
}
}
2022-11-23 22:44:45 +08:00
return nil
}
2018-02-20 20:55:43 +08:00
func toFolderError ( err error ) error {
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardTitleEmpty ) {
return dashboards . ErrFolderTitleEmpty
2018-02-20 20:55:43 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardUpdateAccessDenied ) {
return dashboards . ErrFolderAccessDenied
2018-02-20 20:55:43 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardWithSameNameInFolderExists ) {
return dashboards . ErrFolderSameNameExists
2018-02-20 20:55:43 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardWithSameUIDExists ) {
return dashboards . ErrFolderWithSameUIDExists
2018-02-20 20:55:43 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardVersionMismatch ) {
return dashboards . ErrFolderVersionMismatch
2018-02-20 20:55:43 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return dashboards . ErrFolderNotFound
2018-02-20 20:55:43 +08:00
}
return err
}
2023-04-14 17:17:23 +08:00
func ( s * Service ) RegisterService ( r folder . RegistryService ) error {
s . mutex . Lock ( )
defer s . mutex . Unlock ( )
s . registry [ r . Kind ( ) ] = r
return nil
}