mirror of
https://github.com/grafana/grafana.git
synced 2024-11-23 09:26:43 -06:00
NestedFolders: Return full folder hierarchy in Folder response (#66835)
* Delete redundant struct * Include parent folders in DTO * Add test * Update swagger
This commit is contained in:
parent
5c2a344ce1
commit
7dbcd5ecd0
@ -24,6 +24,8 @@ type Folder struct {
|
|||||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||||
// only used if nested folders are enabled
|
// only used if nested folders are enabled
|
||||||
ParentUID string `json:"parentUid,omitempty"`
|
ParentUID string `json:"parentUid,omitempty"`
|
||||||
|
// the parent folders starting from the root going down
|
||||||
|
Parents []Folder `json:"parents,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FolderSearchHit struct {
|
type FolderSearchHit struct {
|
||||||
|
@ -316,42 +316,63 @@ func (hs *HTTPServer) GetFolderChildrenCounts(c *contextmodel.ReqContext) respon
|
|||||||
|
|
||||||
return response.JSON(http.StatusOK, counts)
|
return response.JSON(http.StatusOK, counts)
|
||||||
}
|
}
|
||||||
|
func (hs *HTTPServer) newToFolderDto(c *contextmodel.ReqContext, g guardian.DashboardGuardian, f *folder.Folder) dtos.Folder {
|
||||||
|
ctx := c.Req.Context()
|
||||||
|
toDTO := func(f *folder.Folder) dtos.Folder {
|
||||||
|
canEdit, _ := g.CanEdit()
|
||||||
|
canSave, _ := g.CanSave()
|
||||||
|
canAdmin, _ := g.CanAdmin()
|
||||||
|
canDelete, _ := g.CanDelete()
|
||||||
|
|
||||||
func (hs *HTTPServer) newToFolderDto(c *contextmodel.ReqContext, g guardian.DashboardGuardian, folder *folder.Folder) dtos.Folder {
|
// Finding creator and last updater of the folder
|
||||||
canEdit, _ := g.CanEdit()
|
updater, creator := anonString, anonString
|
||||||
canSave, _ := g.CanSave()
|
if f.CreatedBy > 0 {
|
||||||
canAdmin, _ := g.CanAdmin()
|
creator = hs.getUserLogin(ctx, f.CreatedBy)
|
||||||
canDelete, _ := g.CanDelete()
|
}
|
||||||
|
if f.UpdatedBy > 0 {
|
||||||
|
updater = hs.getUserLogin(ctx, f.UpdatedBy)
|
||||||
|
}
|
||||||
|
|
||||||
// Finding creator and last updater of the folder
|
acMetadata, _ := hs.getFolderACMetadata(c, f)
|
||||||
updater, creator := anonString, anonString
|
|
||||||
if folder.CreatedBy > 0 {
|
return dtos.Folder{
|
||||||
creator = hs.getUserLogin(c.Req.Context(), folder.CreatedBy)
|
Id: f.ID,
|
||||||
}
|
Uid: f.UID,
|
||||||
if folder.UpdatedBy > 0 {
|
Title: f.Title,
|
||||||
updater = hs.getUserLogin(c.Req.Context(), folder.UpdatedBy)
|
Url: f.URL,
|
||||||
|
HasACL: f.HasACL,
|
||||||
|
CanSave: canSave,
|
||||||
|
CanEdit: canEdit,
|
||||||
|
CanAdmin: canAdmin,
|
||||||
|
CanDelete: canDelete,
|
||||||
|
CreatedBy: creator,
|
||||||
|
Created: f.Created,
|
||||||
|
UpdatedBy: updater,
|
||||||
|
Updated: f.Updated,
|
||||||
|
Version: f.Version,
|
||||||
|
AccessControl: acMetadata,
|
||||||
|
ParentUID: f.ParentUID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acMetadata, _ := hs.getFolderACMetadata(c, folder)
|
folderDTO := toDTO(f)
|
||||||
|
|
||||||
return dtos.Folder{
|
if !hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||||
Id: folder.ID,
|
return folderDTO
|
||||||
Uid: folder.UID,
|
|
||||||
Title: folder.Title,
|
|
||||||
Url: folder.URL,
|
|
||||||
HasACL: folder.HasACL,
|
|
||||||
CanSave: canSave,
|
|
||||||
CanEdit: canEdit,
|
|
||||||
CanAdmin: canAdmin,
|
|
||||||
CanDelete: canDelete,
|
|
||||||
CreatedBy: creator,
|
|
||||||
Created: folder.Created,
|
|
||||||
UpdatedBy: updater,
|
|
||||||
Updated: folder.Updated,
|
|
||||||
Version: folder.Version,
|
|
||||||
AccessControl: acMetadata,
|
|
||||||
ParentUID: folder.ParentUID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parents, err := hs.folderService.GetParents(ctx, folder.GetParentsQuery{UID: f.UID, OrgID: f.OrgID})
|
||||||
|
if err != nil {
|
||||||
|
// log the error instead of failing
|
||||||
|
hs.log.Error("failed to fetch folder parents", "folder", f.UID, "org", f.OrgID, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
folderDTO.Parents = make([]dtos.Folder, 0, len(parents))
|
||||||
|
for _, f := range parents {
|
||||||
|
folderDTO.Parents = append(folderDTO.Parents, toDTO(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.Folder) (accesscontrol.Metadata, error) {
|
func (hs *HTTPServer) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.Folder) (accesscontrol.Metadata, error) {
|
||||||
|
@ -422,3 +422,89 @@ func TestFolderMoveAPIEndpoint(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFolderGetAPIEndpoint(t *testing.T) {
|
||||||
|
folderService := &foldertest.FakeService{
|
||||||
|
ExpectedFolder: &folder.Folder{
|
||||||
|
ID: 1,
|
||||||
|
UID: "uid",
|
||||||
|
Title: "uid title",
|
||||||
|
},
|
||||||
|
ExpectedFolders: []*folder.Folder{
|
||||||
|
{
|
||||||
|
UID: "parent",
|
||||||
|
Title: "parent title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UID: "subfolder",
|
||||||
|
Title: "subfolder title",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
setUpRBACGuardian(t)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
URL string
|
||||||
|
features *featuremgmt.FeatureManager
|
||||||
|
expectedCode int
|
||||||
|
expectedParentUIDs []string
|
||||||
|
expectedParentTitles []string
|
||||||
|
permissions []accesscontrol.Permission
|
||||||
|
}
|
||||||
|
tcs := []testCase{
|
||||||
|
{
|
||||||
|
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"},
|
||||||
|
expectedParentTitles: []string{"parent title", "subfolder title"},
|
||||||
|
permissions: []accesscontrol.Permission{
|
||||||
|
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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{},
|
||||||
|
expectedParentTitles: []string{},
|
||||||
|
permissions: []accesscontrol.Permission{
|
||||||
|
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||||
|
hs.Cfg = &setting.Cfg{
|
||||||
|
RBACEnabled: true,
|
||||||
|
}
|
||||||
|
hs.Features = tc.features
|
||||||
|
hs.folderService = folderService
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
req := srv.NewGetRequest(tc.URL)
|
||||||
|
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, tc.permissions))
|
||||||
|
resp, err := srv.Send(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.expectedCode, resp.StatusCode)
|
||||||
|
|
||||||
|
folder := dtos.Folder{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&folder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, len(folder.Parents), len(tc.expectedParentUIDs))
|
||||||
|
require.Equal(t, len(folder.Parents), len(tc.expectedParentTitles))
|
||||||
|
|
||||||
|
for i := 0; i < len(tc.expectedParentUIDs); i++ {
|
||||||
|
assert.Equal(t, tc.expectedParentUIDs[i], folder.Parents[i].Uid)
|
||||||
|
assert.Equal(t, tc.expectedParentTitles[i], folder.Parents[i].Title)
|
||||||
|
}
|
||||||
|
require.NoError(t, resp.Body.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,12 +48,6 @@ func (f *Folder) IsGeneral() bool {
|
|||||||
return f.ID == GeneralFolder.ID && f.Title == GeneralFolder.Title
|
return f.ID == GeneralFolder.ID && f.Title == GeneralFolder.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
type FolderDTO struct {
|
|
||||||
Folder
|
|
||||||
|
|
||||||
Children []FolderDTO
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFolder tales a title and returns a Folder with the Created and Updated
|
// NewFolder tales a title and returns a Folder with the Created and Updated
|
||||||
// fields set to the current time.
|
// fields set to the current time.
|
||||||
func NewFolder(title string, description string) *Folder {
|
func NewFolder(title string, description string) *Folder {
|
||||||
|
@ -13565,6 +13565,13 @@
|
|||||||
"description": "only used if nested folders are enabled",
|
"description": "only used if nested folders are enabled",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"parents": {
|
||||||
|
"description": "the parent folders starting from the root going down",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -4631,6 +4631,13 @@
|
|||||||
"description": "only used if nested folders are enabled",
|
"description": "only used if nested folders are enabled",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"parents": {
|
||||||
|
"description": "the parent folders starting from the root going down",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Folder"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user