Nested folders: Fetch multiple folders from dashboard folder store (#72464)

This commit is contained in:
Sofia Papagiannaki
2023-08-01 11:04:44 +03:00
committed by GitHub
parent 7612f3d955
commit 1869da1d86
5 changed files with 102 additions and 14 deletions

View File

@@ -2,6 +2,7 @@ package folderimpl
import ( import (
"context" "context"
"strings"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards" "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 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
}

View File

@@ -184,14 +184,25 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery)
return nil, err 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)) filtered := make([]*folder.Folder, 0, len(children))
for _, f := range children { for _, f := range children {
// fetch folder from dashboard store // fetch folder from dashboard store
dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, f.OrgID, f.UID) dashFolder, ok := dashFolders[f.UID]
if err != nil { if !ok {
s.log.Error("failed to fetch folder by UID from dashboard store", "uid", f.UID, "error", err) s.log.Error("failed to fetch folder by UID from dashboard store", "uid", f.UID)
continue continue
} }
// always expose the dashboard store sequential ID // always expose the dashboard store sequential ID
f.ID = dashFolder.ID f.ID = dashFolder.ID
@@ -500,9 +511,14 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
result = append(result, subfolders...) result = append(result, subfolders...)
} }
for _, folder := range result { dashFolders, err := s.dashboardFolderStore.GetFolders(ctx, cmd.OrgID, result)
dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, folder)
if err != nil { if err != nil {
return folder.ErrInternal.Errorf("failed to fetch subfolders from dashboard store: %w", err)
}
for _, folder := range result {
dashFolder, ok := dashFolders[folder]
if !ok {
return err return err
} }

View File

@@ -240,7 +240,7 @@ func TestIntegrationFolderService(t *testing.T) {
f := folder.NewFolder(util.GenerateShortUID(), "") f := folder.NewFolder(util.GenerateShortUID(), "")
f.ID = rand.Int63() f.ID = rand.Int63()
f.UID = util.GenerateShortUID() 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 var actualCmd *dashboards.DeleteDashboardCommand
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {

View File

@@ -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 package foldertest
@@ -19,6 +19,10 @@ func (_m *FakeFolderStore) GetFolderByID(ctx context.Context, orgID int64, id in
ret := _m.Called(ctx, orgID, id) ret := _m.Called(ctx, orgID, id)
var r0 *folder.Folder 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 { if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *folder.Folder); ok {
r0 = rf(ctx, orgID, id) r0 = rf(ctx, orgID, id)
} else { } 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 { if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok {
r1 = rf(ctx, orgID, id) r1 = rf(ctx, orgID, id)
} else { } else {
@@ -42,6 +45,10 @@ func (_m *FakeFolderStore) GetFolderByTitle(ctx context.Context, orgID int64, ti
ret := _m.Called(ctx, orgID, title) ret := _m.Called(ctx, orgID, title)
var r0 *folder.Folder 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 { if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok {
r0 = rf(ctx, orgID, title) r0 = rf(ctx, orgID, title)
} else { } 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 { if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
r1 = rf(ctx, orgID, title) r1 = rf(ctx, orgID, title)
} else { } else {
@@ -65,6 +71,10 @@ func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid
ret := _m.Called(ctx, orgID, uid) ret := _m.Called(ctx, orgID, uid)
var r0 *folder.Folder 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 { if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok {
r0 = rf(ctx, orgID, uid) r0 = rf(ctx, orgID, uid)
} else { } 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 { if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
r1 = rf(ctx, orgID, uid) r1 = rf(ctx, orgID, uid)
} else { } else {
@@ -83,13 +92,38 @@ func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid
return r0, r1 return r0, r1
} }
type mockConstructorTestingTNewFakeFolderStore interface { // GetFolders provides a mock function with given fields: ctx, orgID, uids
mock.TestingT func (_m *FakeFolderStore) GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*folder.Folder, error) {
Cleanup(func()) 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. // 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 := &FakeFolderStore{}
mock.Mock.Test(t) mock.Mock.Test(t)

View File

@@ -39,4 +39,6 @@ type FolderStore interface {
GetFolderByUID(ctx context.Context, orgID int64, uid string) (*Folder, error) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*Folder, error)
// GetFolderByID retrieves a folder by its ID // GetFolderByID retrieves a folder by its ID
GetFolderByID(ctx context.Context, orgID int64, id int64) (*Folder, error) 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)
} }