Nested folders: Remove feature flag (#109212)
This commit is contained in:
+38
-88
@@ -14,10 +14,8 @@ import (
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@@ -71,41 +69,32 @@ func (hs *HTTPServer) GetFolders(c *contextmodel.ReqContext) response.Response {
|
||||
permission = dashboardaccess.PERMISSION_EDIT
|
||||
}
|
||||
|
||||
if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) {
|
||||
q := &folder.GetChildrenQuery{
|
||||
OrgID: c.GetOrgID(),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
Page: c.QueryInt64("page"),
|
||||
UID: c.Query("parentUid"),
|
||||
Permission: permission,
|
||||
SignedInUser: c.SignedInUser,
|
||||
}
|
||||
|
||||
folders, err := hs.folderService.GetChildren(c.Req.Context(), q)
|
||||
if err != nil {
|
||||
return apierrors.ToFolderErrorResponse(err)
|
||||
}
|
||||
|
||||
hits := make([]dtos.FolderSearchHit, 0)
|
||||
for _, f := range folders {
|
||||
hits = append(hits, dtos.FolderSearchHit{
|
||||
ID: f.ID, // nolint:staticcheck
|
||||
UID: f.UID,
|
||||
Title: f.Title,
|
||||
ParentUID: f.ParentUID,
|
||||
ManagedBy: f.ManagedBy,
|
||||
})
|
||||
metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetFolders).Inc()
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, hits)
|
||||
q := &folder.GetChildrenQuery{
|
||||
OrgID: c.GetOrgID(),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
Page: c.QueryInt64("page"),
|
||||
UID: c.Query("parentUid"),
|
||||
Permission: permission,
|
||||
SignedInUser: c.SignedInUser,
|
||||
}
|
||||
|
||||
hits, err := hs.searchFolders(c, permission)
|
||||
folders, err := hs.folderService.GetChildren(c.Req.Context(), q)
|
||||
if err != nil {
|
||||
return apierrors.ToFolderErrorResponse(err)
|
||||
}
|
||||
|
||||
hits := make([]dtos.FolderSearchHit, 0)
|
||||
for _, f := range folders {
|
||||
hits = append(hits, dtos.FolderSearchHit{
|
||||
ID: f.ID, // nolint:staticcheck
|
||||
UID: f.UID,
|
||||
Title: f.Title,
|
||||
ParentUID: f.ParentUID,
|
||||
ManagedBy: f.ManagedBy,
|
||||
})
|
||||
metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetFolders).Inc()
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, hits)
|
||||
}
|
||||
|
||||
@@ -214,30 +203,25 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response {
|
||||
if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) {
|
||||
cmd := folder.MoveFolderCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
var err error
|
||||
|
||||
cmd.OrgID = c.GetOrgID()
|
||||
cmd.UID = web.Params(c.Req)[":uid"]
|
||||
cmd.SignedInUser = c.SignedInUser
|
||||
theFolder, err := hs.folderService.Move(c.Req.Context(), &cmd)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "move folder failed", err)
|
||||
}
|
||||
|
||||
folderDTO, err := hs.newToFolderDto(c, theFolder)
|
||||
if err != nil {
|
||||
return response.Err(err)
|
||||
}
|
||||
return response.JSON(http.StatusOK, folderDTO)
|
||||
cmd := folder.MoveFolderCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
result := map[string]string{}
|
||||
result["message"] = "To use this service, you need to activate nested folder feature."
|
||||
return response.JSON(http.StatusNotFound, result)
|
||||
var err error
|
||||
|
||||
cmd.OrgID = c.GetOrgID()
|
||||
cmd.UID = web.Params(c.Req)[":uid"]
|
||||
cmd.SignedInUser = c.SignedInUser
|
||||
theFolder, err := hs.folderService.Move(c.Req.Context(), &cmd)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "move folder failed", err)
|
||||
}
|
||||
|
||||
folderDTO, err := hs.newToFolderDto(c, theFolder)
|
||||
if err != nil {
|
||||
return response.Err(err)
|
||||
}
|
||||
return response.JSON(http.StatusOK, folderDTO)
|
||||
}
|
||||
|
||||
// swagger:route PUT /folders/{folder_uid} folders updateFolder
|
||||
@@ -396,10 +380,6 @@ func (hs *HTTPServer) newToFolderDto(c *contextmodel.ReqContext, f *folder.Folde
|
||||
return dtos.Folder{}, err
|
||||
}
|
||||
|
||||
if !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) {
|
||||
return folderDTO, nil
|
||||
}
|
||||
|
||||
parents, err := hs.folderService.GetParents(ctx, folder.GetParentsQuery{UID: f.UID, OrgID: f.OrgID})
|
||||
if err != nil {
|
||||
// log the error instead of failing
|
||||
@@ -445,36 +425,6 @@ func (hs *HTTPServer) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) searchFolders(c *contextmodel.ReqContext, permission dashboardaccess.PermissionType) ([]dtos.FolderSearchHit, error) {
|
||||
searchQuery := search.Query{
|
||||
SignedInUser: c.SignedInUser,
|
||||
DashboardIds: make([]int64, 0),
|
||||
FolderIds: make([]int64, 0), // nolint:staticcheck
|
||||
Limit: c.QueryInt64("limit"),
|
||||
OrgId: c.GetOrgID(),
|
||||
Type: "dash-folder",
|
||||
Permission: permission,
|
||||
Page: c.QueryInt64("page"),
|
||||
}
|
||||
|
||||
hits, err := hs.SearchService.SearchHandler(c.Req.Context(), &searchQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
folderHits := make([]dtos.FolderSearchHit, 0)
|
||||
for _, hit := range hits {
|
||||
folderHits = append(folderHits, dtos.FolderSearchHit{
|
||||
ID: hit.ID, // nolint:staticcheck
|
||||
UID: hit.UID,
|
||||
Title: hit.Title,
|
||||
})
|
||||
metrics.MFolderIDsAPICount.WithLabelValues(metrics.SearchFolders).Inc()
|
||||
}
|
||||
|
||||
return folderHits, nil
|
||||
}
|
||||
|
||||
// swagger:parameters getFolders
|
||||
type GetFoldersParams struct {
|
||||
// Limit the maximum number of folders to return
|
||||
|
||||
@@ -107,80 +107,50 @@ func BenchmarkFolderListAndSearch(b *testing.B) {
|
||||
features featuremgmt.FeatureToggles
|
||||
}{
|
||||
{
|
||||
desc: "impl=default nested_folders=on get root folders",
|
||||
desc: "impl=default get root folders",
|
||||
url: "/api/folders",
|
||||
expectedLen: LEVEL0_FOLDER_NUM + 1, // for shared with me folder
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
features: featuremgmt.WithFeatures(),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=on get subfolders",
|
||||
desc: "impl=default get subfolders",
|
||||
url: "/api/folders?parentUid=folder0",
|
||||
expectedLen: LEVEL1_FOLDER_NUM,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=on list all inherited dashboards",
|
||||
url: "/api/search?type=dash-db&limit=5000",
|
||||
expectedLen: withLimit(all),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery nested_folders=on list all inherited dashboards",
|
||||
url: "/api/search?type=dash-db&limit=5000",
|
||||
expectedLen: withLimit(all),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=on search for pattern",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0&limit=5000",
|
||||
expectedLen: withLimit(1 + LEVEL1_DASHBOARD_NUM + LEVEL2_FOLDER_NUM*LEVEL2_DASHBOARD_NUM),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery nested_folders=on search for pattern",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0&limit=5000",
|
||||
expectedLen: withLimit(1 + LEVEL1_DASHBOARD_NUM + LEVEL2_FOLDER_NUM*LEVEL2_DASHBOARD_NUM),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=on search for specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0_0_0",
|
||||
expectedLen: 1,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery nested_folders=on search for specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0_0_0",
|
||||
expectedLen: 1,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=off get root folders",
|
||||
url: "/api/folders?limit=5000",
|
||||
expectedLen: withLimit(LEVEL0_FOLDER_NUM),
|
||||
features: featuremgmt.WithFeatures(),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=off list all dashboards",
|
||||
desc: "impl=defaultlist all inherited dashboards",
|
||||
url: "/api/search?type=dash-db&limit=5000",
|
||||
expectedLen: withLimit(LEVEL0_FOLDER_NUM * LEVEL0_DASHBOARD_NUM),
|
||||
expectedLen: withLimit(all),
|
||||
features: featuremgmt.WithFeatures(),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery nested_folders=off list all dashboards",
|
||||
desc: "impl=permissionsFilterRemoveSubquery list all inherited dashboards",
|
||||
url: "/api/search?type=dash-db&limit=5000",
|
||||
expectedLen: withLimit(LEVEL0_FOLDER_NUM * LEVEL0_DASHBOARD_NUM),
|
||||
expectedLen: withLimit(all),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
{
|
||||
desc: "impl=default nested_folders=off search specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0",
|
||||
desc: "impl=default search for pattern",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0&limit=5000",
|
||||
expectedLen: withLimit(1 + LEVEL1_DASHBOARD_NUM + LEVEL2_FOLDER_NUM*LEVEL2_DASHBOARD_NUM),
|
||||
features: featuremgmt.WithFeatures(),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery search for pattern",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0&limit=5000",
|
||||
expectedLen: withLimit(1 + LEVEL1_DASHBOARD_NUM + LEVEL2_FOLDER_NUM*LEVEL2_DASHBOARD_NUM),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
{
|
||||
desc: "impl=default search for specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0_0_0",
|
||||
expectedLen: 1,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
},
|
||||
{
|
||||
desc: "impl=permissionsFilterRemoveSubquery nested_folders=off search specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0",
|
||||
desc: "impl=permissionsFilterRemoveSubquery search for specific dashboard",
|
||||
url: "/api/search?type=dash-db&query=dashboard_0_0_0_0",
|
||||
expectedLen: 1,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
},
|
||||
|
||||
+2
-31
@@ -43,7 +43,6 @@ func TestFoldersCreateAPIEndpoint(t *testing.T) {
|
||||
expectedFolder *folder.Folder
|
||||
expectedFolderSvcError error
|
||||
permissions []accesscontrol.Permission
|
||||
withNestedFolders bool
|
||||
input string
|
||||
}
|
||||
tcs := []testCase{
|
||||
@@ -119,10 +118,6 @@ func TestFoldersCreateAPIEndpoint(t *testing.T) {
|
||||
|
||||
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
|
||||
if tc.withNestedFolders {
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
}
|
||||
hs.folderService = folderService
|
||||
hs.folderPermissionsService = folderPermService
|
||||
hs.accesscontrolService = actest.FakeService{}
|
||||
@@ -255,7 +250,7 @@ func testDescription(description string, expectedErr error) string {
|
||||
|
||||
func TestHTTPServer_FolderMetadata(t *testing.T) {
|
||||
folderService := &foldertest.FakeService{}
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
features := featuremgmt.WithFeatures()
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.folderService = folderService
|
||||
@@ -292,7 +287,6 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
|
||||
t.Run("Should attach access control metadata to folder response with permissions cascading from nested folders", func(t *testing.T) {
|
||||
folderService.ExpectedFolder = &folder.Folder{UID: "folderUid"}
|
||||
folderService.ExpectedFolders = []*folder.Folder{{UID: "parentUid"}}
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
defer func() {
|
||||
features = featuremgmt.WithFeatures()
|
||||
folderService.ExpectedFolders = nil
|
||||
@@ -386,7 +380,6 @@ func TestFolderMoveAPIEndpoint(t *testing.T) {
|
||||
for _, tc := range tcs {
|
||||
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
hs.folderService = folderService
|
||||
})
|
||||
|
||||
@@ -423,7 +416,6 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||
type testCase struct {
|
||||
description string
|
||||
URL string
|
||||
features featuremgmt.FeatureToggles
|
||||
expectedCode int
|
||||
expectedParentUIDs []string
|
||||
expectedParentOrgIDs []int64
|
||||
@@ -435,7 +427,6 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||
description: "get folder by UID should return parent folders if nested folder are enabled",
|
||||
URL: "/api/folders/uid",
|
||||
expectedCode: http.StatusOK,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
expectedParentUIDs: []string{"parent", "subfolder"},
|
||||
expectedParentOrgIDs: []int64{0, 0},
|
||||
expectedParentTitles: []string{"parent title", "subfolder title"},
|
||||
@@ -449,7 +440,6 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||
description: "get folder by UID should return parent folders redacted if nested folder are enabled and user does not have read access to parent folders",
|
||||
URL: "/api/folders/uid",
|
||||
expectedCode: http.StatusOK,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
expectedParentUIDs: []string{REDACTED, REDACTED},
|
||||
expectedParentOrgIDs: []int64{0, 0},
|
||||
expectedParentTitles: []string{REDACTED, REDACTED},
|
||||
@@ -461,7 +451,6 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||
description: "get folder by UID should return some parent folder titles and some parent folders as redacted if nested folder are enabled and user only has read access to some parent folders",
|
||||
URL: "/api/folders/uid",
|
||||
expectedCode: http.StatusOK,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
expectedParentUIDs: []string{REDACTED, "subfolder"},
|
||||
expectedParentOrgIDs: []int64{0, 0},
|
||||
expectedParentTitles: []string{REDACTED, "subfolder title"},
|
||||
@@ -470,24 +459,11 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("subfolder")},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "get folder by UID should not return parent folders if nested folder are disabled",
|
||||
URL: "/api/folders/uid",
|
||||
expectedCode: http.StatusOK,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParentUIDs: []string{},
|
||||
expectedParentOrgIDs: []int64{0, 0},
|
||||
expectedParentTitles: []string{},
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceAllScope()},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.Features = tc.features
|
||||
hs.folderService = folderService
|
||||
})
|
||||
|
||||
@@ -619,8 +595,6 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
featuresArr := []any{featuremgmt.FlagNestedFolders}
|
||||
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = cfg
|
||||
hs.folderService = &foldertest.FakeService{
|
||||
@@ -634,9 +608,7 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: testuser,
|
||||
}
|
||||
hs.Features = featuremgmt.WithFeatures(
|
||||
featuresArr...,
|
||||
)
|
||||
hs.Features = featuremgmt.WithFeatures()
|
||||
hs.clientConfigProvider = mockClientConfigProvider{
|
||||
host: folderApiServerMock.URL,
|
||||
}
|
||||
@@ -697,7 +669,6 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) {
|
||||
|
||||
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = cfg
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
hs.folderService = folderService
|
||||
hs.folderPermissionsService = folderPermService
|
||||
hs.accesscontrolService = actest.FakeService{}
|
||||
|
||||
Reference in New Issue
Block a user