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{}
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestLegacyAuthorizer(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
authz := newLegacyAuthorizer(acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")))
|
||||
authz := newLegacyAuthorizer(acimpl.ProvideAccessControl(featuremgmt.WithFeatures()))
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -153,7 +153,7 @@ func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID i
|
||||
})
|
||||
}
|
||||
isNested := parentUID != ""
|
||||
if !isNested || !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
if !isNested {
|
||||
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
|
||||
@@ -147,10 +147,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
|
||||
permissions = append(permissions, basicRole.Permissions...)
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
}
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
|
||||
// we don't care about the error here, if this fails we get 0 and no
|
||||
// permission assigned to user will be returned, only for org role.
|
||||
@@ -232,9 +229,7 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
|
||||
}
|
||||
|
||||
permissions = s.actionResolver.ExpandActionSets(permissions)
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
}
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
@@ -933,7 +933,8 @@ func TestIntegrationService_SaveExternalServiceRole(t *testing.T) {
|
||||
// Check that the permissions and assignment are stored correctly
|
||||
perms, errGetPerms := ac.getUserPermissions(ctx, &user.SignedInUser{OrgID: r.cmd.AssignmentOrgID, UserID: 2}, accesscontrol.Options{})
|
||||
require.NoError(t, errGetPerms)
|
||||
assert.ElementsMatch(t, r.cmd.Permissions, perms)
|
||||
// shared with me is added by default for all users in pkg/services/accesscontrol/acimpl/service.go
|
||||
assert.Equal(t, append([]accesscontrol.Permission{{Action: "folders:read", Scope: "folders:uid:sharedwithme"}}, r.cmd.Permissions...), perms)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -989,7 +990,8 @@ func TestIntegrationService_DeleteExternalServiceRole(t *testing.T) {
|
||||
// Check that the permissions and assignment are removed correctly
|
||||
perms, errGetPerms := ac.getUserPermissions(ctx, &user.SignedInUser{OrgID: tt.initCmd.AssignmentOrgID, UserID: 2}, accesscontrol.Options{})
|
||||
require.NoError(t, errGetPerms)
|
||||
assert.Empty(t, perms)
|
||||
// shared with me is added by default for all users in pkg/services/accesscontrol/acimpl/service.go
|
||||
assert.Equal(t, []accesscontrol.Permission{{Action: "folders:read", Scope: "folders:uid:sharedwithme"}}, perms)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
@@ -831,10 +832,9 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
|
||||
|
||||
if len(query.FolderUIDs) > 0 {
|
||||
filters = append(filters, searchstore.FolderUIDFilter{
|
||||
Dialect: d.store.GetDialect(),
|
||||
OrgID: orgID,
|
||||
UIDs: query.FolderUIDs,
|
||||
NestedFoldersEnabled: d.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders),
|
||||
Dialect: d.store.GetDialect(),
|
||||
OrgID: orgID,
|
||||
UIDs: query.FolderUIDs,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -900,6 +900,9 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
|
||||
if item.Term != "" {
|
||||
item.Tags = append(item.Tags, item.Term)
|
||||
}
|
||||
if item.FolderTitle != "" {
|
||||
item.FolderSlug = slugify.Slugify(item.FolderTitle)
|
||||
}
|
||||
seen[item.ID] = len(uniqueRes)
|
||||
uniqueRes = append(uniqueRes, item)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -30,13 +31,16 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
var flder, dashInRoot, childDash *dashboards.Dashboard
|
||||
var currentUser *user.SignedInUser
|
||||
var dashboardStore dashboards.Store
|
||||
var folderStore *folderimpl.FolderStoreImpl
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBWithCfg(t)
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
flder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
|
||||
folderStore = folderimpl.ProvideStore(sqlStore)
|
||||
require.NoError(t, err)
|
||||
flder = insertTestDashFolder(t, dashboardStore, folderStore, "1 test dash folder", 1, 0, "", "prod", "webapp")
|
||||
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod", "webapp")
|
||||
childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, flder.ID, flder.UID, false, "prod", "webapp")
|
||||
insertTestDashboard(t, dashboardStore, "test dash 45", 1, flder.ID, flder.UID, false, "prod")
|
||||
@@ -132,8 +136,8 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
sqlStore, cfg = db.InitTestDBWithCfg(t)
|
||||
var err error
|
||||
require.NoError(t, err)
|
||||
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod")
|
||||
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, "", true, "prod")
|
||||
folder1 = insertTestDashFolder(t, dashboardStore, folderStore, "1 test dash folder", 1, 0, "", "prod")
|
||||
folder2 = insertTestDashFolder(t, dashboardStore, folderStore, "2 test dash folder", 1, 0, "", "prod")
|
||||
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod")
|
||||
childDash1 = insertTestDashboard(t, dashboardStore, "child dash 1", 1, folder1.ID, folder1.UID, false, "prod")
|
||||
childDash2 = insertTestDashboard(t, dashboardStore, "child dash 2", 1, folder2.ID, folder2.UID, false, "prod")
|
||||
@@ -190,8 +194,8 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
t.Run("and a dashboard is moved from folder with acl to the folder without an acl", func(t *testing.T) {
|
||||
setup2()
|
||||
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID, childDash2.FolderUID)
|
||||
currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID), dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}}}
|
||||
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID, childDash2.UID)
|
||||
currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID), dashboards.ScopeDashboardsProvider.GetResourceScopeUID(folder2.UID), dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}}}
|
||||
actest.AddUserPermissionToDB(t, sqlStore, currentUser)
|
||||
|
||||
t.Run("should return folder without acl and its children", func(t *testing.T) {
|
||||
@@ -202,7 +206,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
}
|
||||
hits, err := testSearchDashboards(dashboardStore, query)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(hits), 4)
|
||||
assert.Equal(t, 4, len(hits))
|
||||
assert.Equal(t, hits[0].ID, folder2.ID)
|
||||
assert.Equal(t, hits[1].ID, childDash1.ID)
|
||||
assert.Equal(t, hits[2].ID, childDash2.ID)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
libmodel "github.com/grafana/grafana/pkg/services/libraryelements/model"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
@@ -51,7 +52,8 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
// test dash 23
|
||||
// test dash 45
|
||||
// test dash 67
|
||||
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
|
||||
folderStore := folderimpl.ProvideStore(sqlStore)
|
||||
savedFolder = insertTestDashFolder(t, dashboardStore, folderStore, "1 test dash folder", 1, 0, "", "prod", "webapp")
|
||||
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
|
||||
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, savedFolder.UID, false, "prod")
|
||||
savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod")
|
||||
@@ -814,7 +816,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
|
||||
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPanelTitleSearch)
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -931,7 +933,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
|
||||
}
|
||||
|
||||
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPanelTitleSearch)
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -970,110 +972,89 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
|
||||
folderIDs []int64
|
||||
folderUIDs []string
|
||||
query string
|
||||
expectedResult map[string][]res
|
||||
expectedResult []res
|
||||
typ string
|
||||
}{
|
||||
{
|
||||
desc: "find dashboard under general using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under general"}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
|
||||
},
|
||||
desc: "find dashboard under general using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: []res{{title: "dashboard under general"}},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under general using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under general"}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
|
||||
},
|
||||
desc: "find dashboard under general using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: []res{{title: "dashboard under general"}},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under f0 using folder UID",
|
||||
folderUIDs: []string{f0.UID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
|
||||
},
|
||||
desc: "find dashboard under f0 using folder UID",
|
||||
folderUIDs: []string{f0.UID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: []res{{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under f0 or f1 using folder UID",
|
||||
folderUIDs: []string{f0.UID, f1.UID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
|
||||
expectedResult: []res{
|
||||
{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under general or f0 using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID, f0.UID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under general"}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under general"}},
|
||||
expectedResult: []res{
|
||||
{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under general"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under general or f0 or f1 using folder UID",
|
||||
folderUIDs: []string{folder.GeneralFolderUID, f0.UID, f1.UID},
|
||||
typ: searchstore.TypeDashboard,
|
||||
expectedResult: map[string][]res{
|
||||
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
|
||||
{title: "dashboard under general"}},
|
||||
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
|
||||
{title: "dashboard under general"}},
|
||||
expectedResult: []res{
|
||||
{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
|
||||
{title: "dashboard under general"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "find subfolder",
|
||||
folderUIDs: []string{f0.UID},
|
||||
typ: searchstore.TypeFolder,
|
||||
expectedResult: map[string][]res{
|
||||
"": {},
|
||||
featuremgmt.FlagNestedFolders: {{title: subfolder.Title, folderUID: f0.UID, folderTitle: f0.Title}},
|
||||
},
|
||||
desc: "find subfolder",
|
||||
folderUIDs: []string{f0.UID},
|
||||
typ: searchstore.TypeFolder,
|
||||
expectedResult: []res{{title: subfolder.Title, folderUID: f0.UID, folderTitle: f0.Title}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for featureFlags := range tc.expectedResult {
|
||||
t.Run(fmt.Sprintf("%s with featureFlags: %v", tc.desc, featureFlags), func(t *testing.T) {
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(featureFlags), tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: user,
|
||||
Type: tc.typ,
|
||||
FolderUIDs: tc.folderUIDs,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tc.expectedResult[featureFlags]), len(res))
|
||||
|
||||
for i, r := range tc.expectedResult[featureFlags] {
|
||||
assert.Equal(t, r.title, res[i].Title)
|
||||
if r.folderUID != "" {
|
||||
assert.Equal(t, r.folderUID, res[i].FolderUID)
|
||||
} else {
|
||||
assert.Empty(t, res[i].FolderUID)
|
||||
}
|
||||
if r.folderTitle != "" {
|
||||
assert.Equal(t, r.folderTitle, res[i].FolderTitle)
|
||||
} else {
|
||||
assert.Empty(t, res[i].FolderTitle)
|
||||
}
|
||||
}
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: user,
|
||||
Type: tc.typ,
|
||||
FolderUIDs: tc.folderUIDs,
|
||||
})
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tc.expectedResult), len(res))
|
||||
|
||||
for i, r := range tc.expectedResult {
|
||||
assert.Equal(t, r.title, res[i].Title)
|
||||
if r.folderUID != "" {
|
||||
assert.Equal(t, r.folderUID, res[i].FolderUID)
|
||||
} else {
|
||||
assert.Empty(t, res[i].FolderUID)
|
||||
}
|
||||
if r.folderTitle != "" {
|
||||
assert.Equal(t, r.folderTitle, res[i].FolderTitle)
|
||||
} else {
|
||||
assert.Empty(t, res[i].FolderTitle)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1260,6 +1241,34 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st
|
||||
return dash
|
||||
}
|
||||
|
||||
func insertTestDashFolder(t *testing.T, dashboardStore dashboards.Store, folderStore folder.Store, title string, orgId int64,
|
||||
folderId int64, folderUID string, tags ...interface{}) *dashboards.Dashboard {
|
||||
t.Helper()
|
||||
cmd := dashboards.SaveDashboardCommand{
|
||||
OrgID: orgId,
|
||||
FolderID: folderId, // nolint:staticcheck
|
||||
FolderUID: folderUID,
|
||||
IsFolder: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dash)
|
||||
dash.Data.Set("id", dash.ID)
|
||||
dash.Data.Set("uid", dash.UID)
|
||||
_, err = folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||
Title: title,
|
||||
UID: dash.UID,
|
||||
OrgID: orgId,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return dash
|
||||
}
|
||||
|
||||
func insertTestDashboardForPlugin(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
|
||||
folderUID string, isFolder bool, pluginId string) *dashboards.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
@@ -1162,7 +1162,7 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con
|
||||
isNested := obj.GetFolder() != ""
|
||||
if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) {
|
||||
// legacy behavior
|
||||
if !isNested || !dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
if !isNested {
|
||||
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
@@ -1392,7 +1392,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.FindDashboards")
|
||||
defer span.End()
|
||||
|
||||
if dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && len(query.FolderUIDs) > 0 && slices.Contains(query.FolderUIDs, folder.SharedWithMeFolderUID) {
|
||||
if len(query.FolderUIDs) > 0 && slices.Contains(query.FolderUIDs, folder.SharedWithMeFolderUID) {
|
||||
start := time.Now()
|
||||
userDashboardUIDs, err := dr.getUserSharedDashboardUIDs(ctx, query.SignedInUser)
|
||||
if err != nil {
|
||||
|
||||
@@ -1402,7 +1402,6 @@ func TestSearchDashboards(t *testing.T) {
|
||||
|
||||
t.Run("Should handle Shared with me folder correctly", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
|
||||
if len(req.Options.Fields) == 0 {
|
||||
@@ -2095,9 +2094,9 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) {
|
||||
// Setup mocks and service
|
||||
dashboardStore := &dashboards.FakeDashboardStore{}
|
||||
folderStore := foldertest.FakeFolderStore{}
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
features := featuremgmt.WithFeatures()
|
||||
if tc.featureKubernetesDashboards {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesDashboards, featuremgmt.FlagNestedFolders)
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesDashboards)
|
||||
}
|
||||
|
||||
permService := acmock.NewMockedPermissionsService()
|
||||
|
||||
@@ -118,13 +118,6 @@ var (
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaSearchAndStorageSquad,
|
||||
},
|
||||
{
|
||||
Name: "nestedFolders",
|
||||
Description: "Enable folder nesting",
|
||||
Stage: FeatureStageGeneralAvailability,
|
||||
Owner: grafanaSearchAndStorageSquad,
|
||||
Expression: "true", // enabled by default
|
||||
},
|
||||
{
|
||||
Name: "alertingBacktesting",
|
||||
Description: "Rule backtesting API for alerting",
|
||||
|
||||
@@ -88,7 +88,6 @@ showDashboardValidationWarnings,2022-10-14T13:51:05Z,,2e16d5499e1cf29e67db5031fe
|
||||
interFont,2022-10-15T14:22:33Z,2022-12-01T11:59:37Z,9f5e691994c9db15f5984b196ab55fd7213b7f72,Torkel Ödegaard
|
||||
accessControlOnCall,2022-10-19T16:10:09Z,2025-02-25T12:44:40Z,717bd4a6c051d1447c9e35385a4ba176dd391c65,Gabriel MABILLE
|
||||
newDBLibrary,2022-10-26T01:20:41Z,2023-11-14T14:51:35Z,a3acfb1a48126119fd4913efbb572de162264e92,Ryan McKinley
|
||||
nestedFolders,2022-10-26T14:15:14Z,,b346ae03105af606521bbefec16722d590378646,Kristin Laemmert
|
||||
datasourceLogger,2022-11-02T13:51:51Z,2023-02-07T11:49:16Z,06705a49e236dc3ab3b35db51b7bad8625e4866f,Carl Bergquist
|
||||
promQueryBuilder,2022-11-03T17:34:01Z,2022-12-19T13:52:06Z,857e545c5ac71722c8b2d3d27621dfb453dbb8ff,Ryan McKinley
|
||||
elasticsearchBackendMigration,2022-11-10T15:35:15Z,2023-04-12T12:20:43Z,261d620f1c46eb43282cc444ffb4633b77af4283,Ivana Huckova
|
||||
|
||||
|
@@ -13,7 +13,6 @@ grpcServer,preview,@grafana/search-and-storage,false,false,false
|
||||
cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false
|
||||
showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,false,false,false
|
||||
mysqlAnsiQuotes,experimental,@grafana/search-and-storage,false,false,false
|
||||
nestedFolders,GA,@grafana/search-and-storage,false,false,false
|
||||
alertingBacktesting,experimental,@grafana/alerting-squad,false,false,false
|
||||
editPanelCSVDragAndDrop,experimental,@grafana/dataviz-squad,false,false,true
|
||||
logsContextDatasourceUi,GA,@grafana/observability-logs,false,false,true
|
||||
|
||||
|
@@ -63,10 +63,6 @@ const (
|
||||
// Use double quotes to escape keyword in a MySQL query
|
||||
FlagMysqlAnsiQuotes = "mysqlAnsiQuotes"
|
||||
|
||||
// FlagNestedFolders
|
||||
// Enable folder nesting
|
||||
FlagNestedFolders = "nestedFolders"
|
||||
|
||||
// FlagAlertingBacktesting
|
||||
// Rule backtesting API for alerting
|
||||
FlagAlertingBacktesting = "alertingBacktesting"
|
||||
|
||||
@@ -2096,19 +2096,6 @@
|
||||
"codeowner": "@grafana/search-and-storage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "nestedFolders",
|
||||
"resourceVersion": "1753448760331",
|
||||
"creationTimestamp": "2022-10-26T14:15:14Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable folder nesting",
|
||||
"stage": "GA",
|
||||
"codeowner": "@grafana/search-and-storage",
|
||||
"expression": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "newClickhouseConfigPageDesign",
|
||||
|
||||
@@ -243,29 +243,11 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
|
||||
}
|
||||
}
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
qry.WithFullpath = false // do not request full path if nested folders are disabled
|
||||
qry.WithFullpathUIDs = false
|
||||
}
|
||||
|
||||
dashFolders, err := s.store.GetFolders(ctx, qry)
|
||||
if err != nil {
|
||||
return nil, folder.ErrInternal.Errorf("failed to fetch subfolders: %w", err)
|
||||
}
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
if q.WithFullpathUIDs || q.WithFullpath {
|
||||
for _, f := range dashFolders { // and fix the full path with folder title (unescaped)
|
||||
if q.WithFullpath {
|
||||
f.Fullpath = f.Title
|
||||
}
|
||||
if q.WithFullpathUIDs {
|
||||
f.FullpathUIDs = f.UID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dashFolders, nil
|
||||
}
|
||||
|
||||
@@ -286,7 +268,7 @@ func (s *Service) GetLegacy(ctx context.Context, q *folder.GetFolderQuery) (*fol
|
||||
return folder.RootFolder, nil
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && q.UID != nil && *q.UID == folder.SharedWithMeFolderUID {
|
||||
if q.UID != nil && *q.UID == folder.SharedWithMeFolderUID {
|
||||
return folder.SharedWithMeFolder.WithURL(), nil
|
||||
}
|
||||
|
||||
@@ -317,11 +299,6 @@ func (s *Service) GetLegacy(ctx context.Context, q *folder.GetFolderQuery) (*fol
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
|
||||
// nolint:staticcheck
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
f.Fullpath = f.Title // set full path to the folder title (unescaped)
|
||||
f.FullpathUIDs = f.UID // set full path to the folder UID
|
||||
}
|
||||
|
||||
return f, err
|
||||
}
|
||||
|
||||
@@ -378,7 +355,7 @@ func (s *Service) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQu
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && q.UID == folder.SharedWithMeFolderUID {
|
||||
if q.UID == folder.SharedWithMeFolderUID {
|
||||
return s.GetSharedWithMe(ctx, q, true)
|
||||
}
|
||||
|
||||
@@ -646,7 +623,7 @@ func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*
|
||||
func (s *Service) GetParentsLegacy(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.GetParentsLegacy")
|
||||
defer span.End()
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) || q.UID == accesscontrol.GeneralFolderUID {
|
||||
if q.UID == accesscontrol.GeneralFolderUID {
|
||||
return nil, nil
|
||||
}
|
||||
if q.UID == folder.SharedWithMeFolderUID {
|
||||
@@ -667,7 +644,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
|
||||
dashFolder := dashboards.NewDashboardFolder(cmd.Title)
|
||||
dashFolder.OrgID = cmd.OrgID
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.ParentUID != "" {
|
||||
if cmd.ParentUID != "" {
|
||||
// Check that the user is allowed to create a subfolder in this folder
|
||||
parentUIDScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.ParentUID)
|
||||
legacyEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, parentUIDScope)
|
||||
@@ -694,7 +671,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.UID == folder.SharedWithMeFolderUID {
|
||||
if cmd.UID == folder.SharedWithMeFolderUID {
|
||||
return nil, folder.ErrBadRequest.Errorf("cannot create folder with UID %s", folder.SharedWithMeFolderUID)
|
||||
}
|
||||
|
||||
@@ -819,10 +796,6 @@ func (s *Service) UpdateLegacy(ctx context.Context, cmd *folder.UpdateFolderComm
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
return dashFolder, nil
|
||||
}
|
||||
|
||||
// always expose the dashboard store sequential ID
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
|
||||
// nolint:staticcheck
|
||||
@@ -1279,17 +1252,15 @@ func (s *Service) GetDescendantCountsLegacy(ctx context.Context, q *folder.GetDe
|
||||
|
||||
folders := []string{*q.UID}
|
||||
countsMap := make(folder.DescendantCounts, len(s.registry)+1)
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
descendantFolders, err := s.store.GetDescendants(ctx, q.OrgID, *q.UID)
|
||||
if err != nil {
|
||||
s.log.ErrorContext(ctx, "failed to get descendant folders", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range descendantFolders {
|
||||
folders = append(folders, f.UID)
|
||||
}
|
||||
countsMap[entity.StandardKindFolder] = int64(len(descendantFolders))
|
||||
descendantFolders, err := s.store.GetDescendants(ctx, q.OrgID, *q.UID)
|
||||
if err != nil {
|
||||
s.log.ErrorContext(ctx, "failed to get descendant folders", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range descendantFolders {
|
||||
folders = append(folders, f.UID)
|
||||
}
|
||||
countsMap[entity.StandardKindFolder] = int64(len(descendantFolders))
|
||||
|
||||
for _, v := range s.registry {
|
||||
c, err := v.CountInFolders(ctx, q.OrgID, folders, q.SignedInUser)
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestIntegrationDashboardFileReader(t *testing.T) {
|
||||
}
|
||||
|
||||
sql, cfgT := db.InitTestDBWithCfg(t)
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
features := featuremgmt.WithFeatures()
|
||||
fStore := folderimpl.ProvideStore(sql)
|
||||
tagService := tagimpl.ProvideService(sql)
|
||||
dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService)
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestIntegrationDuplicatesValidator(t *testing.T) {
|
||||
logger := log.New("test.logger")
|
||||
|
||||
sql, cfgT := db.InitTestDBWithCfg(t)
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
features := featuremgmt.WithFeatures()
|
||||
fStore := folderimpl.ProvideStore(sql)
|
||||
tagService := tagimpl.ProvideService(sql)
|
||||
dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService)
|
||||
|
||||
@@ -220,36 +220,23 @@ func (f *accessControlDashboardPermissionFilter) buildClauses(dialect migrator.D
|
||||
}
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
case true:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
builder.WriteString("(dashboard.folder_id IN (SELECT d.id FROM dashboard as d ")
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString(fmt.Sprintf("WHERE d.org_id = ? AND d.uid IN (SELECT uid FROM %s)", recQueryName))
|
||||
args = append(args, orgID)
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "folder_id", "d.id", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
builder.WriteString("(dashboard.folder_id IN (SELECT d.id FROM dashboard as d ")
|
||||
builder.WriteString("WHERE 1 = 0")
|
||||
}
|
||||
default:
|
||||
builder.WriteString("(dashboard.folder_id IN (SELECT d.id FROM dashboard as d ")
|
||||
if len(permSelectorArgs) > 0 {
|
||||
builder.WriteString("WHERE d.org_id = ? AND d.uid IN ")
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString(fmt.Sprintf("WHERE d.org_id = ? AND d.uid IN (SELECT uid FROM %s)", recQueryName))
|
||||
args = append(args, orgID)
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
} else {
|
||||
builder.WriteString("WHERE 1 = 0")
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "folder_id", "d.id", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("(dashboard.folder_id IN (SELECT d.id FROM dashboard as d ")
|
||||
builder.WriteString("WHERE 1 = 0")
|
||||
}
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
|
||||
@@ -297,33 +284,22 @@ func (f *accessControlDashboardPermissionFilter) buildClauses(dialect migrator.D
|
||||
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
case true:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(fmt.Sprintf("(SELECT uid FROM %s)", recQueryName))
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "uid", "d.uid", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
builder.WriteRune(')')
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
}
|
||||
default:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
builder.WriteString(fmt.Sprintf("(SELECT uid FROM %s)", recQueryName))
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "uid", "d.uid", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
builder.WriteRune(')')
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
}
|
||||
builder.WriteString(" AND dashboard.is_folder)")
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
@@ -107,36 +106,22 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses(di
|
||||
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
case true:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(folder.uid IN (SELECT uid FROM " + recQueryName)
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "folder", "uid", "", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
f.folderIsRequired = true
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
} else {
|
||||
builder.WriteString("( 1 = 0 AND NOT dashboard.is_folder)")
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(folder.uid IN (SELECT uid FROM " + recQueryName)
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "folder", "uid", "", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
default:
|
||||
builder.WriteString("(")
|
||||
if len(permSelectorArgs) > 0 {
|
||||
builder.WriteString("folder.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
f.folderIsRequired = true
|
||||
} else {
|
||||
builder.WriteString("1 = 0 ")
|
||||
}
|
||||
builder.WriteString(" AND NOT dashboard.is_folder)")
|
||||
f.folderIsRequired = true
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
} else {
|
||||
builder.WriteString("( 1 = 0 AND NOT dashboard.is_folder)")
|
||||
}
|
||||
|
||||
// Include all the dashboards under the root if the user has the required permissions on the root (used to be the General folder)
|
||||
@@ -181,33 +166,22 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses(di
|
||||
}
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
case true:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(fmt.Sprintf("(SELECT uid FROM %s)", recQueryName))
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "uid", "", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
builder.WriteRune(')')
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
}
|
||||
default:
|
||||
if len(permSelectorArgs) > 0 {
|
||||
if len(permSelectorArgs) > 0 {
|
||||
switch f.recursiveQueriesAreSupported {
|
||||
case true:
|
||||
recQueryName := fmt.Sprintf("RecQry%d", len(f.recQueries))
|
||||
f.addRecQry(recQueryName, permSelector.String(), permSelectorArgs, orgID)
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
builder.WriteString(fmt.Sprintf("(SELECT uid FROM %s)", recQueryName))
|
||||
default:
|
||||
nestedFoldersSelectors, nestedFoldersArgs := f.nestedFoldersSelectors(permSelector.String(), permSelectorArgs, "dashboard", "uid", "", orgID)
|
||||
builder.WriteRune('(')
|
||||
builder.WriteString(nestedFoldersSelectors)
|
||||
builder.WriteRune(')')
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
}
|
||||
builder.WriteString(" AND dashboard.is_folder)")
|
||||
} else {
|
||||
|
||||
@@ -384,14 +384,12 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
permission dashboardaccess.PermissionType
|
||||
permissions []accesscontrol.Permission
|
||||
expectedResult []string
|
||||
features []any
|
||||
}{
|
||||
{
|
||||
desc: "Should not be able to view dashboards under inherited folders with no permissions if nested folders are enabled",
|
||||
queryType: searchstore.TypeDashboard,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
permissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
@@ -399,14 +397,12 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
queryType: searchstore.TypeFolder,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
permissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view inherited dashboards and folders with no permissions if nested folders are enabled",
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
permissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
@@ -416,7 +412,6 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -426,19 +421,8 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: []string{"parent", "subfolder"},
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view inherited folders if nested folders are not enabled",
|
||||
queryType: searchstore.TypeFolder,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
|
||||
},
|
||||
features: []any{},
|
||||
expectedResult: []string{"parent"},
|
||||
},
|
||||
}
|
||||
|
||||
var orgID int64 = 1
|
||||
@@ -452,7 +436,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
})
|
||||
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.permissions)}}
|
||||
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
|
||||
m := features.GetEnabled(context.Background())
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
@@ -494,14 +478,12 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
|
||||
permission dashboardaccess.PermissionType
|
||||
signedInUserPermissions []accesscontrol.Permission
|
||||
expectedResult []string
|
||||
features []any
|
||||
}{
|
||||
{
|
||||
desc: "Should not be able to view dashboards under inherited folders with no permissions if nested folders are enabled",
|
||||
queryType: searchstore.TypeDashboard,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
@@ -509,14 +491,12 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
|
||||
queryType: searchstore.TypeFolder,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view inherited dashboards and folders with no permissions if nested folders are enabled",
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
@@ -526,7 +506,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -536,19 +515,8 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: []string{"parent", "subfolder"},
|
||||
},
|
||||
{
|
||||
desc: "Should not be able to view inherited folders if nested folders are not enabled",
|
||||
queryType: searchstore.TypeFolder,
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
|
||||
},
|
||||
features: []any{},
|
||||
expectedResult: []string{"parent"},
|
||||
},
|
||||
}
|
||||
|
||||
var orgID int64 = 1
|
||||
@@ -566,7 +534,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
|
||||
}),
|
||||
},
|
||||
}
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
|
||||
m := features.GetEnabled(context.Background())
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
@@ -676,7 +644,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
Scope: "folders:uid:unrelated"})
|
||||
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}}
|
||||
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
|
||||
m := features.GetEnabled(context.Background())
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
@@ -711,56 +679,65 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB {
|
||||
t.Helper()
|
||||
|
||||
store := db.InitTestDB(t)
|
||||
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
dashes := make([]dashboards.Dashboard, 0, numFolders+numDashboards)
|
||||
for i := 1; i <= numFolders; i++ {
|
||||
str := strconv.Itoa(i)
|
||||
dashes = append(dashes, dashboards.Dashboard{
|
||||
OrgID: 1,
|
||||
Slug: str,
|
||||
UID: str,
|
||||
Title: str,
|
||||
IsFolder: true,
|
||||
Data: simplejson.New(),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
})
|
||||
}
|
||||
// Seed dashboards
|
||||
for i := numFolders + 1; i <= numFolders+numDashboards; i++ {
|
||||
str := strconv.Itoa(i)
|
||||
folderID := 0
|
||||
if i%(numFolders+1) != 0 {
|
||||
folderID = i % (numFolders + 1)
|
||||
}
|
||||
dashes = append(dashes, dashboards.Dashboard{
|
||||
OrgID: 1,
|
||||
IsFolder: false,
|
||||
FolderUID: strconv.Itoa(folderID),
|
||||
UID: str,
|
||||
Slug: str,
|
||||
Title: str,
|
||||
Data: simplejson.New(),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
})
|
||||
db, cfg := db.InitTestDBWithCfg(t)
|
||||
dashStore, err := database.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db))
|
||||
require.NoError(t, err)
|
||||
fStore := folderimpl.ProvideStore(db)
|
||||
|
||||
// Create a signed-in user for folder creation
|
||||
usr := &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: 1,
|
||||
Login: "test",
|
||||
}
|
||||
|
||||
// folders need to be created in both the folder and dashboard table
|
||||
for i := 1; i <= numFolders; i++ {
|
||||
str := strconv.Itoa(i)
|
||||
folder, err := fStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||
Title: str,
|
||||
OrgID: 1,
|
||||
UID: str,
|
||||
SignedInUser: usr,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
|
||||
OrgID: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]any{
|
||||
"title": str,
|
||||
"uid": folder.UID,
|
||||
}),
|
||||
IsFolder: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// now create dashboards
|
||||
for i := numFolders + 1; i <= numFolders+numDashboards; i++ {
|
||||
str := strconv.Itoa(i)
|
||||
folderID := 0
|
||||
if i%(numFolders+1) != 0 {
|
||||
folderID = i % (numFolders + 1)
|
||||
}
|
||||
|
||||
// Insert dashboards in batches
|
||||
batchSize := 500
|
||||
for i := 0; i < len(dashes); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(dashes) {
|
||||
end = len(dashes)
|
||||
}
|
||||
|
||||
_, err := sess.InsertMulti(dashes[i:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := dashboards.SaveDashboardCommand{
|
||||
OrgID: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]any{
|
||||
"title": str,
|
||||
"uid": str,
|
||||
}),
|
||||
IsFolder: false,
|
||||
}
|
||||
if folderID != 0 {
|
||||
cmd.FolderUID = strconv.Itoa(folderID)
|
||||
}
|
||||
|
||||
_, err := dashStore.SaveDashboard(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// insert permissions
|
||||
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
role := &accesscontrol.Role{
|
||||
OrgID: 0,
|
||||
UID: "basic_viewer",
|
||||
@@ -792,6 +769,7 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access
|
||||
}
|
||||
|
||||
if len(permissions) > 0 {
|
||||
batchSize := 500
|
||||
for i := 0; i < len(permissions); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(permissions) {
|
||||
@@ -808,7 +786,7 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return store
|
||||
return db
|
||||
}
|
||||
|
||||
func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol.Permission, orgID int64, features featuremgmt.FeatureToggles) db.DB {
|
||||
|
||||
@@ -44,11 +44,6 @@ func benchmarkDashboardPermissionFilter(b *testing.B, numUsers, numDashboards, n
|
||||
}}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
// if nestingLevel > 0 enable nested folders
|
||||
if nestingLevel > 0 {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
}
|
||||
|
||||
store := setupBenchMark(b, usr, features, numUsers, numDashboards, numFolders, nestingLevel)
|
||||
|
||||
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
|
||||
|
||||
@@ -37,14 +37,9 @@ func (b *Builder) ToSQL(limit, page int64) (string, []any) {
|
||||
INNER JOIN dashboard ON ids.id = dashboard.id`)
|
||||
b.sql.WriteString("\n")
|
||||
|
||||
if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
// covered by UQE_folder_org_id_uid
|
||||
b.sql.WriteString(
|
||||
`LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`)
|
||||
} else {
|
||||
b.sql.WriteString(`
|
||||
LEFT OUTER JOIN dashboard AS folder ON folder.id = dashboard.folder_id`)
|
||||
}
|
||||
// covered by UQE_folder_org_id_uid
|
||||
b.sql.WriteString(
|
||||
`LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`)
|
||||
b.sql.WriteString(`
|
||||
LEFT OUTER JOIN dashboard_tag ON dashboard.id = dashboard_tag.dashboard_id`)
|
||||
b.sql.WriteString("\n")
|
||||
@@ -69,17 +64,9 @@ func (b *Builder) buildSelect() {
|
||||
dashboard.folder_id,
|
||||
dashboard.deleted,
|
||||
folder.uid AS folder_uid,
|
||||
folder.title AS folder_slug,
|
||||
folder.title AS folder_title
|
||||
`)
|
||||
if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
|
||||
b.sql.WriteString(`
|
||||
folder.title AS folder_slug,`)
|
||||
} else {
|
||||
b.sql.WriteString(`
|
||||
folder.slug AS folder_slug,`)
|
||||
}
|
||||
b.sql.WriteString(`
|
||||
folder.title AS folder_title `)
|
||||
|
||||
for _, f := range b.Filters {
|
||||
if f, ok := f.(model.FilterSelect); ok {
|
||||
b.sql.WriteString(fmt.Sprintf(", %s", f.Select()))
|
||||
|
||||
@@ -66,10 +66,9 @@ func (f FolderFilter) Where() (string, []any) {
|
||||
}
|
||||
|
||||
type FolderUIDFilter struct {
|
||||
Dialect migrator.Dialect
|
||||
OrgID int64
|
||||
UIDs []string
|
||||
NestedFoldersEnabled bool
|
||||
Dialect migrator.Dialect
|
||||
OrgID int64
|
||||
UIDs []string
|
||||
}
|
||||
|
||||
func (f FolderUIDFilter) Where() (string, []any) {
|
||||
@@ -92,35 +91,19 @@ func (f FolderUIDFilter) Where() (string, []any) {
|
||||
case len(params) < 1:
|
||||
// do nothing
|
||||
case len(params) == 1:
|
||||
q = "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid = ?)"
|
||||
if f.NestedFoldersEnabled {
|
||||
q = "dashboard.org_id = ? AND dashboard.folder_uid = ?"
|
||||
}
|
||||
q = "dashboard.org_id = ? AND dashboard.folder_uid = ?"
|
||||
params = append([]any{f.OrgID}, params...)
|
||||
default:
|
||||
sqlArray := "(?" + strings.Repeat(",?", len(params)-1) + ")"
|
||||
q = "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN " + sqlArray + ")"
|
||||
if f.NestedFoldersEnabled {
|
||||
q = "dashboard.org_id = ? AND dashboard.folder_uid IN " + sqlArray
|
||||
}
|
||||
q = "dashboard.org_id = ? AND dashboard.folder_uid IN " + sqlArray
|
||||
params = append([]any{f.OrgID}, params...)
|
||||
}
|
||||
|
||||
if includeGeneral {
|
||||
if q == "" {
|
||||
if f.NestedFoldersEnabled {
|
||||
q = "dashboard.folder_uid IS NULL "
|
||||
} else {
|
||||
q = "dashboard.folder_id = ? "
|
||||
params = append(params, 0)
|
||||
}
|
||||
q = "dashboard.folder_uid IS NULL "
|
||||
} else {
|
||||
if f.NestedFoldersEnabled {
|
||||
q = "(" + q + " OR dashboard.folder_uid IS NULL)"
|
||||
} else {
|
||||
q = "(" + q + " OR dashboard.folder_id = ?)"
|
||||
params = append(params, 0)
|
||||
}
|
||||
q = "(" + q + " OR dashboard.folder_uid IS NULL)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,67 +12,34 @@ func TestIntegrationFolderUIDFilter(t *testing.T) {
|
||||
t.Skip("skipping integration test in short mode")
|
||||
}
|
||||
testCases := []struct {
|
||||
description string
|
||||
uids []string
|
||||
expectedSql string
|
||||
expectedParams []any
|
||||
nestedFoldersEnabled bool
|
||||
description string
|
||||
uids []string
|
||||
expectedSql string
|
||||
expectedParams []any
|
||||
}{
|
||||
{
|
||||
description: "searching general folder",
|
||||
uids: []string{"general"},
|
||||
expectedSql: "dashboard.folder_id = ? ",
|
||||
expectedParams: []any{0},
|
||||
nestedFoldersEnabled: false,
|
||||
description: "searching general folder",
|
||||
uids: []string{"general"},
|
||||
expectedSql: "dashboard.folder_uid IS NULL ",
|
||||
expectedParams: []any{},
|
||||
},
|
||||
{
|
||||
description: "searching a specific folder",
|
||||
uids: []string{"abc-123"},
|
||||
expectedSql: "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid = ?)",
|
||||
expectedParams: []any{int64(1), "abc-123"},
|
||||
nestedFoldersEnabled: false,
|
||||
description: "searching a specific folder",
|
||||
uids: []string{"abc-123"},
|
||||
expectedSql: "dashboard.org_id = ? AND dashboard.folder_uid = ?",
|
||||
expectedParams: []any{int64(1), "abc-123"},
|
||||
},
|
||||
{
|
||||
description: "searching a specific folders",
|
||||
uids: []string{"abc-123", "def-456"},
|
||||
expectedSql: "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN (?,?))",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456"},
|
||||
nestedFoldersEnabled: false,
|
||||
description: "searching a specific folders",
|
||||
uids: []string{"abc-123", "def-456"},
|
||||
expectedSql: "dashboard.org_id = ? AND dashboard.folder_uid IN (?,?)",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456"},
|
||||
},
|
||||
{
|
||||
description: "searching a specific folders or general",
|
||||
uids: []string{"general", "abc-123", "def-456"},
|
||||
expectedSql: "(dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN (?,?)) OR dashboard.folder_id = ?)",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456", 0},
|
||||
nestedFoldersEnabled: false,
|
||||
},
|
||||
{
|
||||
description: "searching general folder with nestedFoldersEnabled",
|
||||
uids: []string{"general"},
|
||||
expectedSql: "dashboard.folder_uid IS NULL ",
|
||||
expectedParams: []any{},
|
||||
nestedFoldersEnabled: true,
|
||||
},
|
||||
{
|
||||
description: "searching a specific folder with nestedFoldersEnabled",
|
||||
uids: []string{"abc-123"},
|
||||
expectedSql: "dashboard.org_id = ? AND dashboard.folder_uid = ?",
|
||||
expectedParams: []any{int64(1), "abc-123"},
|
||||
nestedFoldersEnabled: true,
|
||||
},
|
||||
{
|
||||
description: "searching a specific folders with nestedFoldersEnabled",
|
||||
uids: []string{"abc-123", "def-456"},
|
||||
expectedSql: "dashboard.org_id = ? AND dashboard.folder_uid IN (?,?)",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456"},
|
||||
nestedFoldersEnabled: true,
|
||||
},
|
||||
{
|
||||
description: "searching a specific folders or general with nestedFoldersEnabled",
|
||||
uids: []string{"general", "abc-123", "def-456"},
|
||||
expectedSql: "(dashboard.org_id = ? AND dashboard.folder_uid IN (?,?) OR dashboard.folder_uid IS NULL)",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456"},
|
||||
nestedFoldersEnabled: true,
|
||||
description: "searching a specific folders or general",
|
||||
uids: []string{"general", "abc-123", "def-456"},
|
||||
expectedSql: "(dashboard.org_id = ? AND dashboard.folder_uid IN (?,?) OR dashboard.folder_uid IS NULL)",
|
||||
expectedParams: []any{int64(1), "abc-123", "def-456"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -81,10 +48,9 @@ func TestIntegrationFolderUIDFilter(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
f := searchstore.FolderUIDFilter{
|
||||
Dialect: store.GetDialect(),
|
||||
OrgID: 1,
|
||||
UIDs: tc.uids,
|
||||
NestedFoldersEnabled: tc.nestedFoldersEnabled,
|
||||
Dialect: store.GetDialect(),
|
||||
OrgID: 1,
|
||||
UIDs: tc.uids,
|
||||
}
|
||||
|
||||
sql, params := f.Where()
|
||||
|
||||
@@ -146,48 +146,6 @@ func TestIntegrationBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with view permission",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
level: dashboardaccess.PERMISSION_VIEW,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParams: []any{
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:view",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with write permission",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
@@ -196,16 +154,6 @@ func TestIntegrationBuilder_RBAC(t *testing.T) {
|
||||
level: dashboardaccess.PERMISSION_EDIT,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParams: []any{
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -218,6 +166,7 @@ func TestIntegrationBuilder_RBAC(t *testing.T) {
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
@@ -225,15 +174,26 @@ func TestIntegrationBuilder_RBAC(t *testing.T) {
|
||||
"dashboards:create",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with view permission with nesting",
|
||||
desc: "user with view permission",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
level: dashboardaccess.PERMISSION_VIEW,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
features: featuremgmt.WithFeatures(),
|
||||
expectedParams: []any{
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -272,53 +232,12 @@ func TestIntegrationBuilder_RBAC(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with view permission with remove subquery",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
level: dashboardaccess.PERMISSION_VIEW,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
expectedParams: []any{
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:view",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
"Viewer",
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user with edit permission with nesting and remove subquery",
|
||||
desc: "user with edit permission remove subquery",
|
||||
userPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:1"},
|
||||
},
|
||||
level: dashboardaccess.PERMISSION_EDIT,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery),
|
||||
expectedParams: []any{
|
||||
int64(1),
|
||||
int64(1),
|
||||
|
||||
@@ -361,7 +361,6 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-openapi-client-go/client/folders"
|
||||
"github.com/grafana/grafana-openapi-client-go/models"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/tests"
|
||||
@@ -46,7 +45,6 @@ func TestIntegrationFolderServiceGetFolder(t *testing.T) {
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
|
||||
})
|
||||
|
||||
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, p)
|
||||
@@ -199,7 +197,7 @@ func TestIntegrationCreateFolder(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationNestedFoldersOn(t *testing.T) {
|
||||
func TestIntegrationNestedFolders(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
@@ -359,7 +357,6 @@ func TestIntegrationSharedWithMe(t *testing.T) {
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
||||
@@ -550,7 +547,6 @@ func TestIntegrationFineGrainedPermissions(t *testing.T) {
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
||||
|
||||
@@ -150,9 +150,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -166,9 +163,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -182,9 +176,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -198,9 +189,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -228,9 +216,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
// We set it to 1 here, so we always get forced pagination based on the response size.
|
||||
UnifiedStorageMaxPageSizeBytes: 1,
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}), mode)
|
||||
})
|
||||
}
|
||||
@@ -669,9 +654,6 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
})
|
||||
|
||||
user := helper.CreateUser("user", apis.Org1, org.RoleViewer, tc.permissions)
|
||||
@@ -776,9 +758,6 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
})
|
||||
|
||||
// Create parent folder
|
||||
@@ -959,9 +938,6 @@ func TestIntegrationFoldersCreateAPIEndpointK8S(t *testing.T) {
|
||||
DualWriterMode: modeDw,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
})
|
||||
|
||||
userTest := helper.CreateUser("user", apis.Org1, org.RoleViewer, tc.permissions)
|
||||
@@ -1134,7 +1110,6 @@ func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagUnifiedStorageSearch,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user