Folder/parent subresource (#98392)

* Expose get folder parents endpoint

* Add tests
This commit is contained in:
Leonor Oliveira 2025-01-03 10:43:56 +01:00 committed by GitHub
parent 0836f71da6
commit 937e8dea2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 136 additions and 41 deletions

View File

@ -76,6 +76,7 @@ func (hs *HTTPServer) registerFolderAPI(apiRoute routing.RouteRegister, authoriz
} else {
folderUidRoute.Post("/move", authorize(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, uidScope)), routing.Wrap(hs.MoveFolder))
}
folderUidRoute.Get("parents", handler.getFolderParents)
})
} else {
folderRoute.Post("/", authorize(accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)), routing.Wrap(hs.CreateFolder))
@ -816,6 +817,23 @@ func (fk8s *folderK8sHandler) countFolderContent(c *contextmodel.ReqContext) {
c.JSON(http.StatusOK, out)
}
func (fk8s *folderK8sHandler) getFolderParents(c *contextmodel.ReqContext) {
client, ok := fk8s.getClient(c)
if !ok {
return
}
uid := web.Params(c.Req)[":uid"]
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{}, "parents")
if err != nil {
fk8s.writeError(c, err)
return
}
c.JSON(http.StatusOK, out)
}
func (fk8s *folderK8sHandler) getFolder(c *contextmodel.ReqContext) {
client, ok := fk8s.getClient(c)
if !ok {

View File

@ -0,0 +1,77 @@
package folders
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestSubParent(t *testing.T) {
tests := []struct {
name string
input *v0alpha1.Folder
expected *v0alpha1.FolderInfoList
setuFn func(*mock.Mock)
}{
{
name: "no parents",
input: &v0alpha1.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Annotations: map[string]string{},
},
Spec: v0alpha1.Spec{
Title: "some tittle",
},
},
expected: &v0alpha1.FolderInfoList{Items: []v0alpha1.FolderInfo{{Name: "test", Title: "some tittle"}}},
},
{
name: "has a parent",
input: &v0alpha1.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Annotations: map[string]string{"grafana.app/folder": "parent-test"},
},
Spec: v0alpha1.Spec{
Title: "some tittle",
},
},
setuFn: func(m *mock.Mock) {
m.On("Get", context.TODO(), "parent-test", &metav1.GetOptions{}).Return(&v0alpha1.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "parent-test",
Annotations: map[string]string{},
},
Spec: v0alpha1.Spec{
Title: "some other tittle",
},
}, nil).Once()
},
expected: &v0alpha1.FolderInfoList{Items: []v0alpha1.FolderInfo{
{Name: "test", Title: "some tittle", Parent: "parent-test"},
{Name: "parent-test", Title: "some other tittle"}},
}},
}
s := (grafanarest.Storage)(nil)
m := &mock.Mock{}
gm := storageMock{m, s}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &subParentsREST{
getter: gm,
}
if tt.setuFn != nil {
tt.setuFn(m)
}
parents := r.parents(context.TODO(), tt.input)
require.Equal(t, tt.expected, parents)
})
}
}

View File

@ -10,7 +10,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
)
@ -55,48 +54,49 @@ func (r *subParentsREST) Connect(ctx context.Context, name string, opts runtime.
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
info := &v0alpha1.FolderInfoList{
Items: []v0alpha1.FolderInfo{},
}
for folder != nil {
parent := ""
meta, _ := utils.MetaAccessor(folder)
if meta != nil {
parent = meta.GetFolder()
}
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: folder.Name,
Title: folder.Spec.Title,
Description: folder.Spec.Description,
Parent: parent,
})
if parent == "" {
break
}
obj, err = r.getter.Get(ctx, parent, &metav1.GetOptions{})
if err != nil {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: err.Error(),
})
break
}
folder, ok = obj.(*v0alpha1.Folder)
if !ok {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: fmt.Sprintf("expected folder, found: %T", obj),
})
break
}
}
info := r.parents(ctx, folder)
// Start from the root
slices.Reverse(info.Items)
responder.Object(http.StatusOK, info)
}), nil
}
func (r *subParentsREST) parents(ctx context.Context, folder *v0alpha1.Folder) *v0alpha1.FolderInfoList {
info := &v0alpha1.FolderInfoList{
Items: []v0alpha1.FolderInfo{},
}
for folder != nil {
parent := getParent(folder)
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: folder.Name,
Title: folder.Spec.Title,
Description: folder.Spec.Description,
Parent: parent,
})
if parent == "" {
break
}
obj, err := r.getter.Get(ctx, parent, &metav1.GetOptions{})
if err != nil {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: err.Error(),
})
break
}
parentFolder, ok := obj.(*v0alpha1.Folder)
if !ok {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: fmt.Sprintf("expected folder, found: %T", obj),
})
break
}
folder = parentFolder
}
return info
}