diff --git a/pkg/services/folder/folderimpl/dashboard_folder_store.go b/pkg/services/folder/folderimpl/dashboard_folder_store.go index d498e97176e..695ecf93b08 100644 --- a/pkg/services/folder/folderimpl/dashboard_folder_store.go +++ b/pkg/services/folder/folderimpl/dashboard_folder_store.go @@ -2,6 +2,7 @@ package folderimpl import ( "context" + "strings" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" @@ -84,3 +85,38 @@ func (d *DashboardFolderStoreImpl) GetFolderByUID(ctx context.Context, orgID int } return dashboards.FromDashboard(&dashboard), nil } + +func (d *DashboardFolderStoreImpl) GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*folder.Folder, error) { + m := make(map[string]*folder.Folder, len(uids)) + var folders []*folder.Folder + if err := d.store.WithDbSession(ctx, func(sess *db.Session) error { + b := strings.Builder{} + args := make([]interface{}, 0, len(uids)+1) + + b.WriteString("SELECT * FROM dashboard WHERE org_id=? ") + args = append(args, orgID) + for i, uid := range uids { + if i == 0 { + b.WriteString(" AND (") + } + + if i > 0 { + b.WriteString(" OR ") + } + b.WriteString(" uid=? ") + args = append(args, uid) + + if i == len(uids)-1 { + b.WriteString(")") + } + } + return sess.SQL(b.String(), args...).Find(&folders) + }); err != nil { + return nil, err + } + + for _, f := range folders { + m[f.UID] = f + } + return m, nil +} diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 6365919e076..30dd5f34322 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -184,14 +184,25 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery) return nil, err } + childrenUIDs := make([]string, 0, len(children)) + for _, f := range children { + childrenUIDs = append(childrenUIDs, f.UID) + } + + dashFolders, err := s.dashboardFolderStore.GetFolders(ctx, cmd.OrgID, childrenUIDs) + if err != nil { + return nil, folder.ErrInternal.Errorf("failed to fetch subfolders from dashboard store: %w", err) + } + filtered := make([]*folder.Folder, 0, len(children)) for _, f := range children { // fetch folder from dashboard store - dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, f.OrgID, f.UID) - if err != nil { - s.log.Error("failed to fetch folder by UID from dashboard store", "uid", f.UID, "error", err) + dashFolder, ok := dashFolders[f.UID] + if !ok { + s.log.Error("failed to fetch folder by UID from dashboard store", "uid", f.UID) continue } + // always expose the dashboard store sequential ID f.ID = dashFolder.ID @@ -500,9 +511,14 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e result = append(result, subfolders...) } + dashFolders, err := s.dashboardFolderStore.GetFolders(ctx, cmd.OrgID, result) + if err != nil { + return folder.ErrInternal.Errorf("failed to fetch subfolders from dashboard store: %w", err) + } + for _, folder := range result { - dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, folder) - if err != nil { + dashFolder, ok := dashFolders[folder] + if !ok { return err } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index c866cb5f9bd..f9d85b577e9 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -240,7 +240,7 @@ func TestIntegrationFolderService(t *testing.T) { f := folder.NewFolder(util.GenerateShortUID(), "") f.ID = rand.Int63() f.UID = util.GenerateShortUID() - folderStore.On("GetFolderByUID", mock.Anything, orgID, f.UID).Return(f, nil) + folderStore.On("GetFolders", mock.Anything, orgID, []string{f.UID}).Return(map[string]*folder.Folder{f.UID: f}, nil) var actualCmd *dashboards.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { diff --git a/pkg/services/folder/foldertest/folder_store_mock.go b/pkg/services/folder/foldertest/folder_store_mock.go index 7f9ad2502b3..aa5b782a903 100644 --- a/pkg/services/folder/foldertest/folder_store_mock.go +++ b/pkg/services/folder/foldertest/folder_store_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.32.0. DO NOT EDIT. package foldertest @@ -19,6 +19,10 @@ func (_m *FakeFolderStore) GetFolderByID(ctx context.Context, orgID int64, id in ret := _m.Called(ctx, orgID, id) var r0 *folder.Folder + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (*folder.Folder, error)); ok { + return rf(ctx, orgID, id) + } if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *folder.Folder); ok { r0 = rf(ctx, orgID, id) } else { @@ -27,7 +31,6 @@ func (_m *FakeFolderStore) GetFolderByID(ctx context.Context, orgID int64, id in } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { r1 = rf(ctx, orgID, id) } else { @@ -42,6 +45,10 @@ func (_m *FakeFolderStore) GetFolderByTitle(ctx context.Context, orgID int64, ti ret := _m.Called(ctx, orgID, title) var r0 *folder.Folder + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*folder.Folder, error)); ok { + return rf(ctx, orgID, title) + } if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok { r0 = rf(ctx, orgID, title) } else { @@ -50,7 +57,6 @@ func (_m *FakeFolderStore) GetFolderByTitle(ctx context.Context, orgID int64, ti } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { r1 = rf(ctx, orgID, title) } else { @@ -65,6 +71,10 @@ func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid ret := _m.Called(ctx, orgID, uid) var r0 *folder.Folder + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*folder.Folder, error)); ok { + return rf(ctx, orgID, uid) + } if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok { r0 = rf(ctx, orgID, uid) } else { @@ -73,7 +83,6 @@ func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { r1 = rf(ctx, orgID, uid) } else { @@ -83,13 +92,38 @@ func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid return r0, r1 } -type mockConstructorTestingTNewFakeFolderStore interface { - mock.TestingT - Cleanup(func()) +// GetFolders provides a mock function with given fields: ctx, orgID, uids +func (_m *FakeFolderStore) GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*folder.Folder, error) { + ret := _m.Called(ctx, orgID, uids) + + var r0 map[string]*folder.Folder + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, []string) (map[string]*folder.Folder, error)); ok { + return rf(ctx, orgID, uids) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, []string) map[string]*folder.Folder); ok { + r0 = rf(ctx, orgID, uids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*folder.Folder) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, []string) error); ok { + r1 = rf(ctx, orgID, uids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // NewFakeFolderStore creates a new instance of FakeFolderStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewFakeFolderStore(t mockConstructorTestingTNewFakeFolderStore) *FakeFolderStore { +// The first argument is typically a *testing.T value. +func NewFakeFolderStore(t interface { + mock.TestingT + Cleanup(func()) +}) *FakeFolderStore { mock := &FakeFolderStore{} mock.Mock.Test(t) diff --git a/pkg/services/folder/service.go b/pkg/services/folder/service.go index 74b10c61fe8..90dcd7b7f83 100644 --- a/pkg/services/folder/service.go +++ b/pkg/services/folder/service.go @@ -39,4 +39,6 @@ type FolderStore interface { GetFolderByUID(ctx context.Context, orgID int64, uid string) (*Folder, error) // GetFolderByID retrieves a folder by its ID GetFolderByID(ctx context.Context, orgID int64, id int64) (*Folder, error) + // GetFolders returns all folders for the given orgID and UIDs. + GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*Folder, error) }