Nested folders: Provide count of all descendant dashboards and folders (#67184)
* Add a method for getting descendant folders * Include dashboard count for descendant folders * Return subfolder count * Replace references to children with descendants * Update openapi specs * Add test for descendant counts * Add logging to GetDescendantCounts
This commit is contained in:
+1
-1
@@ -462,7 +462,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
folderUidRoute.Put("/", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersWrite, uidScope)), routing.Wrap(hs.UpdateFolder))
|
||||
folderUidRoute.Post("/move", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersWrite, uidScope)), routing.Wrap(hs.MoveFolder))
|
||||
folderUidRoute.Delete("/", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersDelete, uidScope)), routing.Wrap(hs.DeleteFolder))
|
||||
folderUidRoute.Get("/counts", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersRead, uidScope)), routing.Wrap(hs.GetFolderChildrenCounts))
|
||||
folderUidRoute.Get("/counts", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersRead, uidScope)), routing.Wrap(hs.GetFolderDescendantCounts))
|
||||
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
|
||||
folderPermissionRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionFoldersPermissionsRead, uidScope)), routing.Wrap(hs.GetFolderPermissionList))
|
||||
|
||||
+9
-9
@@ -299,19 +299,19 @@ func (hs *HTTPServer) DeleteFolder(c *contextmodel.ReqContext) response.Response
|
||||
return response.JSON(http.StatusOK, "")
|
||||
}
|
||||
|
||||
// swagger:route GET /folders/{folder_uid}/counts folders getFolderChildrenCounts
|
||||
// swagger:route GET /folders/{folder_uid}/counts folders getFolderDescendantCounts
|
||||
//
|
||||
// Gets the count of each descendant of a folder by kind. The folder is identified by UID.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getFolderChildrenCountsResponse
|
||||
// 200: getFolderDescendantCountsResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetFolderChildrenCounts(c *contextmodel.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) GetFolderDescendantCounts(c *contextmodel.ReqContext) response.Response {
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
counts, err := hs.folderService.GetChildrenCounts(c.Req.Context(), &folder.GetChildrenCountsQuery{OrgID: c.OrgID, UID: &uid, SignedInUser: c.SignedInUser})
|
||||
counts, err := hs.folderService.GetDescendantCounts(c.Req.Context(), &folder.GetDescendantCountsQuery{OrgID: c.OrgID, UID: &uid, SignedInUser: c.SignedInUser})
|
||||
if err != nil {
|
||||
return apierrors.ToFolderErrorResponse(err)
|
||||
}
|
||||
@@ -546,16 +546,16 @@ type DeleteFolderResponse struct {
|
||||
} `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:parameters getFolderChildrenCounts
|
||||
type GetFolderChildrenCountsParams struct {
|
||||
// swagger:parameters getFolderDescendantCounts
|
||||
type GetFolderDescendantCountsParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
FolderUID string `json:"folder_uid"`
|
||||
}
|
||||
|
||||
// swagger:response getFolderChildrenCountsResponse
|
||||
type GetFolderChildrenCountsResponse struct {
|
||||
// swagger:response getFolderDescendantCountsResponse
|
||||
type GetFolderDescendantCountsResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body folder.ChildrenCounts `json:"body"`
|
||||
Body folder.DescendantCounts `json:"body"`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -623,7 +624,8 @@ 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) {
|
||||
func (s *Service) GetDescendantCounts(ctx context.Context, cmd *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
logger := s.log.FromContext(ctx)
|
||||
if cmd.SignedInUser == nil {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed-in user")
|
||||
}
|
||||
@@ -634,15 +636,47 @@ func (s *Service) GetChildrenCounts(ctx context.Context, cmd *folder.GetChildren
|
||||
return nil, folder.ErrBadRequest.Errorf("invalid orgID")
|
||||
}
|
||||
|
||||
countsMap := make(folder.ChildrenCounts, len(s.registry))
|
||||
result := []string{*cmd.UID}
|
||||
countsMap := make(folder.DescendantCounts, len(s.registry)+1)
|
||||
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||
subfolders, err := s.getNestedFolders(ctx, cmd.OrgID, *cmd.UID)
|
||||
if err != nil {
|
||||
logger.Error("failed to get subfolders", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, subfolders...)
|
||||
countsMap[entity.StandardKindFolder] = int64(len(subfolders))
|
||||
}
|
||||
|
||||
for _, v := range s.registry {
|
||||
c, err := v.CountInFolder(ctx, cmd.OrgID, *cmd.UID, cmd.SignedInUser)
|
||||
for _, folder := range result {
|
||||
c, err := v.CountInFolder(ctx, cmd.OrgID, folder, cmd.SignedInUser)
|
||||
if err != nil {
|
||||
logger.Error("failed to count folder descendants", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
countsMap[v.Kind()] += c
|
||||
}
|
||||
}
|
||||
return countsMap, nil
|
||||
}
|
||||
|
||||
func (s *Service) getNestedFolders(ctx context.Context, orgID int64, uid string) ([]string, error) {
|
||||
result := []string{}
|
||||
folders, err := s.store.GetChildren(ctx, folder.GetChildrenQuery{UID: uid, OrgID: orgID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range folders {
|
||||
result = append(result, f.UID)
|
||||
subfolders, err := s.getNestedFolders(ctx, f.OrgID, f.UID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
countsMap[v.Kind()] += c
|
||||
result = append(result, subfolders...)
|
||||
}
|
||||
return countsMap, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MakeUserAdmin is copy of DashboardServiceImpl.MakeUserAdmin
|
||||
|
||||
@@ -12,7 +12,6 @@ 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"
|
||||
@@ -322,7 +321,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationDeleteNestedFolders(t *testing.T) {
|
||||
func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
@@ -347,6 +346,7 @@ func TestIntegrationDeleteNestedFolders(t *testing.T) {
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
db: db,
|
||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
}
|
||||
|
||||
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||
@@ -358,77 +358,170 @@ func TestIntegrationDeleteNestedFolders(t *testing.T) {
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
|
||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
t.Run("Should get descendant counts", func(t *testing.T) {
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
depth := 5
|
||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
serviceWithFlagOn.store = nestedFolderStore
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "", createCmd)
|
||||
_, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
||||
require.NoError(t, err)
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
|
||||
require.NoError(t, err)
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
|
||||
|
||||
countCmd := folder.GetDescendantCountsQuery{
|
||||
UID: &ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m["folder"], int64(depth-1))
|
||||
require.Equal(t, m["dashboard"], int64(2))
|
||||
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOn.store.Delete(context.Background(), uid, orgID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
t.Run("With nested folder feature flag off", func(t *testing.T) {
|
||||
featuresFlagOff := featuremgmt.WithFeatures()
|
||||
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
||||
|
||||
serviceWithFlagOff := &Service{
|
||||
cfg: cfg,
|
||||
log: log.New("test-folder-service"),
|
||||
dashboardStore: dashStore,
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOff,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
db: db,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
}
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
_, err = service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
|
||||
require.NoError(t, err)
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
|
||||
|
||||
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
|
||||
require.NoError(t, err)
|
||||
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
|
||||
require.NoError(t, err)
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
|
||||
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
|
||||
|
||||
countCmd := folder.GetDescendantCountsQuery{
|
||||
UID: &ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m["folder"], int64(0))
|
||||
require.Equal(t, m["dashboard"], int64(1))
|
||||
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOn.store.Delete(context.Background(), uid, orgID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("With feature flag unset", func(t *testing.T) {
|
||||
featuresFlagOff := featuremgmt.WithFeatures()
|
||||
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
||||
|
||||
serviceWithFlagOff := &Service{
|
||||
cfg: cfg,
|
||||
log: log.New("test-folder-service"),
|
||||
dashboardStore: dashStore,
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOff,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
db: db,
|
||||
}
|
||||
t.Run("Should delete folders", func(t *testing.T) {
|
||||
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "", createCmd)
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "", createCmd)
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
})
|
||||
t.Run("With nested folder feature flag off", func(t *testing.T) {
|
||||
featuresFlagOff := featuremgmt.WithFeatures()
|
||||
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
||||
|
||||
serviceWithFlagOff := &Service{
|
||||
cfg: cfg,
|
||||
log: log.New("test-folder-service"),
|
||||
dashboardStore: dashStore,
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: featuresFlagOff,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
db: db,
|
||||
}
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
|
||||
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "", createCmd)
|
||||
|
||||
deleteCmd := folder.DeleteFolderCommand{
|
||||
UID: ancestorUIDs[0],
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
}
|
||||
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, uid := range ancestorUIDs {
|
||||
// dashboard table
|
||||
_, err := serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||
// folder table
|
||||
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
for _, uid := range ancestorUIDs {
|
||||
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -930,65 +1023,6 @@ 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,10 +7,10 @@ import (
|
||||
)
|
||||
|
||||
type FakeService struct {
|
||||
ExpectedFolders []*folder.Folder
|
||||
ExpectedFolder *folder.Folder
|
||||
ExpectedError error
|
||||
ExpectedChildrenCounts map[string]int64
|
||||
ExpectedFolders []*folder.Folder
|
||||
ExpectedFolder *folder.Folder
|
||||
ExpectedError error
|
||||
ExpectedDescendantCounts map[string]int64
|
||||
}
|
||||
|
||||
func NewFakeService() *FakeService {
|
||||
@@ -51,6 +51,6 @@ 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
|
||||
func (s *FakeService) GetDescendantCounts(ctx context.Context, cmd *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
return s.ExpectedDescendantCounts, s.ExpectedError
|
||||
}
|
||||
|
||||
@@ -155,13 +155,13 @@ 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 {
|
||||
// GetDescendantCountsQuery captures the information required by the folder service
|
||||
// to return the count of descendants (direct and indirect) in a folder.
|
||||
type GetDescendantCountsQuery struct {
|
||||
UID *string
|
||||
OrgID int64
|
||||
|
||||
SignedInUser *user.SignedInUser `json:"-"`
|
||||
}
|
||||
|
||||
type ChildrenCounts map[string]int64
|
||||
type DescendantCounts map[string]int64
|
||||
|
||||
@@ -26,7 +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)
|
||||
GetDescendantCounts(ctx context.Context, cmd *GetDescendantCountsQuery) (DescendantCounts, error)
|
||||
}
|
||||
|
||||
// FolderStore is a folder store.
|
||||
|
||||
+11
-11
@@ -5655,7 +5655,7 @@
|
||||
"folders"
|
||||
],
|
||||
"summary": "Gets the count of each descendant of a folder by kind. The folder is identified by UID.",
|
||||
"operationId": "getFolderChildrenCounts",
|
||||
"operationId": "getFolderDescendantCounts",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
@@ -5666,7 +5666,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/getFolderChildrenCountsResponse"
|
||||
"$ref": "#/responses/getFolderDescendantCountsResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
@@ -11885,13 +11885,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ChildrenCounts": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"ConfFloat64": {
|
||||
"description": "ConfFloat64 is a float64. It Marshals float64 values of NaN of Inf\nto null.",
|
||||
"type": "number",
|
||||
@@ -13098,6 +13091,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DescendantCounts": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"DiscordConfig": {
|
||||
"type": "object",
|
||||
"title": "DiscordConfig configures notifications via Discord.",
|
||||
@@ -20341,10 +20341,10 @@
|
||||
"$ref": "#/definitions/DataSourceList"
|
||||
}
|
||||
},
|
||||
"getFolderChildrenCountsResponse": {
|
||||
"getFolderDescendantCountsResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ChildrenCounts"
|
||||
"$ref": "#/definitions/DescendantCounts"
|
||||
}
|
||||
},
|
||||
"getFolderPermissionListResponse": {
|
||||
|
||||
+11
-11
@@ -792,11 +792,11 @@
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"getFolderChildrenCountsResponse": {
|
||||
"getFolderDescendantCountsResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChildrenCounts"
|
||||
"$ref": "#/components/schemas/DescendantCounts"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2952,13 +2952,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ChildrenCounts": {
|
||||
"additionalProperties": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ConfFloat64": {
|
||||
"description": "ConfFloat64 is a float64. It Marshals float64 values of NaN of Inf\nto null.",
|
||||
"format": "double",
|
||||
@@ -4165,6 +4158,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DescendantCounts": {
|
||||
"additionalProperties": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DiscordConfig": {
|
||||
"properties": {
|
||||
"http_config": {
|
||||
@@ -17026,7 +17026,7 @@
|
||||
},
|
||||
"/folders/{folder_uid}/counts": {
|
||||
"get": {
|
||||
"operationId": "getFolderChildrenCounts",
|
||||
"operationId": "getFolderDescendantCounts",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
@@ -17039,7 +17039,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/getFolderChildrenCountsResponse"
|
||||
"$ref": "#/components/responses/getFolderDescendantCountsResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
|
||||
Reference in New Issue
Block a user