diff --git a/pkg/api/folder.go b/pkg/api/folder.go index ef2b21c1920..d57f6559578 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -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 { diff --git a/pkg/registry/apis/folders/sub_parent_test.go b/pkg/registry/apis/folders/sub_parent_test.go new file mode 100644 index 00000000000..0c29800dbf4 --- /dev/null +++ b/pkg/registry/apis/folders/sub_parent_test.go @@ -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) + }) + } +} diff --git a/pkg/registry/apis/folders/sub_parents.go b/pkg/registry/apis/folders/sub_parents.go index f532b945f49..87f9a999c1f 100644 --- a/pkg/registry/apis/folders/sub_parents.go +++ b/pkg/registry/apis/folders/sub_parents.go @@ -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 +}