mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nested folders: Fetch multiple folders from dashboard folder store (#72464)
This commit is contained in:
committed by
GitHub
parent
7612f3d955
commit
1869da1d86
@@ -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
|
||||||
|
}
|
||||||
|
@@ -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...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
for _, folder := range result {
|
||||||
dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, folder)
|
dashFolder, ok := dashFolders[folder]
|
||||||
if err != nil {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user