mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: Add API endpoint for descendant count in a folder (#66550)
* Add CountInFolder to RegistryService interface * Add folder children counts api route * Update fake GetFolderChildrenCounts * Add test for getting folder children counts * Add validation to folder children counts handler * Update openapi specs * Update pkg/services/folder/folderimpl/folder.go Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> --------- Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
This commit is contained in:
@@ -623,6 +623,28 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetChildrenCounts(ctx context.Context, cmd *folder.GetChildrenCountsQuery) (folder.ChildrenCounts, error) {
|
||||
if cmd.SignedInUser == nil {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed-in user")
|
||||
}
|
||||
if *cmd.UID == "" {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing UID")
|
||||
}
|
||||
if cmd.OrgID < 1 {
|
||||
return nil, folder.ErrBadRequest.Errorf("invalid orgID")
|
||||
}
|
||||
|
||||
countsMap := make(folder.ChildrenCounts, len(s.registry))
|
||||
for _, v := range s.registry {
|
||||
c, err := v.CountInFolder(ctx, cmd.OrgID, *cmd.UID, cmd.SignedInUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
countsMap[v.Kind()] += c
|
||||
}
|
||||
return countsMap, nil
|
||||
}
|
||||
|
||||
// MakeUserAdmin is copy of DashboardServiceImpl.MakeUserAdmin
|
||||
func (s *Service) MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error {
|
||||
rtEditor := org.RoleEditor
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
@@ -930,6 +932,65 @@ func TestNestedFolderService(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetChildrenCounts(t *testing.T) {
|
||||
g := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true})
|
||||
t.Cleanup(func() {
|
||||
guardian.New = g
|
||||
})
|
||||
|
||||
folderId := rand.Int63()
|
||||
folderUID := util.GenerateShortUID()
|
||||
f := folder.NewFolder("Folder", "")
|
||||
f.ID = folderId
|
||||
f.UID = folderUID
|
||||
f.OrgID = orgID
|
||||
|
||||
nestedFolderStore := NewFakeStore()
|
||||
nestedFolderStore.ExpectedFolder = f
|
||||
|
||||
dashStore := dashboards.FakeDashboardStore{}
|
||||
dashboardCount := int64(2)
|
||||
countCmd := dashboards.CountDashboardsInFolderRequest{
|
||||
FolderID: f.ID,
|
||||
OrgID: f.OrgID,
|
||||
}
|
||||
dashStore.On("CountDashboardsInFolder", mock.Anything, &countCmd).Return(dashboardCount, nil)
|
||||
|
||||
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
|
||||
dashboardFolderStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(f, nil)
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
folderService := &Service{
|
||||
cfg: cfg,
|
||||
store: nestedFolderStore,
|
||||
dashboardStore: &dashStore,
|
||||
dashboardFolderStore: dashboardFolderStore,
|
||||
features: features,
|
||||
log: log.New("test-folder-service"),
|
||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
}
|
||||
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
_, err := service.ProvideDashboardServiceImpl(cfg, &dashStore, dashboardFolderStore, nil, features, folderPermissions, dashboardPermissions, ac, folderService)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID}
|
||||
ctx := appcontext.WithUser(context.Background(), &signedInUser)
|
||||
res, err := folderService.GetChildrenCounts(ctx, &folder.GetChildrenCountsQuery{
|
||||
SignedInUser: usr,
|
||||
UID: &folderUID,
|
||||
OrgID: orgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res["dashboard"], dashboardCount)
|
||||
}
|
||||
|
||||
func CreateSubtreeInStore(t *testing.T, store *sqlStore, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []string {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
)
|
||||
|
||||
type FakeService struct {
|
||||
ExpectedFolders []*folder.Folder
|
||||
ExpectedFolder *folder.Folder
|
||||
ExpectedError error
|
||||
ExpectedFolders []*folder.Folder
|
||||
ExpectedFolder *folder.Folder
|
||||
ExpectedError error
|
||||
ExpectedChildrenCounts map[string]int64
|
||||
}
|
||||
|
||||
func NewFakeService() *FakeService {
|
||||
@@ -49,3 +50,7 @@ func (s *FakeService) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (
|
||||
func (s *FakeService) RegisterService(service folder.RegistryService) error {
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetChildrenCounts(ctx context.Context, cmd *folder.GetChildrenCountsQuery) (folder.ChildrenCounts, error) {
|
||||
return s.ExpectedChildrenCounts, s.ExpectedError
|
||||
}
|
||||
|
||||
@@ -160,3 +160,14 @@ type HasEditPermissionInFoldersQuery struct {
|
||||
type HasAdminPermissionInDashboardsOrFoldersQuery struct {
|
||||
SignedInUser *user.SignedInUser
|
||||
}
|
||||
|
||||
// GetChildrenCountsQuery captures the information required by the folder service
|
||||
// to return the count of children in a folder.
|
||||
type GetChildrenCountsQuery struct {
|
||||
UID *string
|
||||
OrgID int64
|
||||
|
||||
SignedInUser *user.SignedInUser `json:"-"`
|
||||
}
|
||||
|
||||
type ChildrenCounts map[string]int64
|
||||
|
||||
@@ -2,9 +2,12 @@ package folder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
type RegistryService interface {
|
||||
DeleteInFolder(ctx context.Context, orgID int64, UID string) error
|
||||
DeleteInFolder(ctx context.Context, orgID int64, uid string) error
|
||||
CountInFolder(ctx context.Context, orgID int64, uid string, user *user.SignedInUser) (int64, error)
|
||||
Kind() string
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type Service interface {
|
||||
// Move changes a folder's parent folder to the requested new parent.
|
||||
Move(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error)
|
||||
RegisterService(service RegistryService) error
|
||||
GetChildrenCounts(ctx context.Context, cmd *GetChildrenCountsQuery) (ChildrenCounts, error)
|
||||
}
|
||||
|
||||
// FolderStore is a folder store.
|
||||
|
||||
Reference in New Issue
Block a user