diff --git a/pkg/apis/folder/v0alpha1/types.go b/pkg/apis/folder/v0alpha1/types.go index c0bbb12ba40..ceaaaf1233b 100644 --- a/pkg/apis/folder/v0alpha1/types.go +++ b/pkg/apis/folder/v0alpha1/types.go @@ -45,14 +45,20 @@ type FolderInfoList struct { // FolderInfo briefly describes a folder -- unlike a folder resource, // this is a partial record of the folder metadata used for navigating parents and children type FolderInfo struct { - // UID is the unique identifier for a folder (and the k8s name) - UID string `json:"uid"` + // Name is the k8s name (eg, the unique identifier) for a folder + Name string `json:"name"` // Title is the display value Title string `json:"title"` + // The folder description + Description string `json:"description,omitempty"` + // The parent folder UID Parent string `json:"parent,omitempty"` + + // This folder does not resolve + Detached bool `json:"detached,omitempty"` } // Access control information for the current user diff --git a/pkg/apis/folder/v0alpha1/zz_generated.openapi.go b/pkg/apis/folder/v0alpha1/zz_generated.openapi.go index 25a12cef515..c3c9f74bfc4 100644 --- a/pkg/apis/folder/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/folder/v0alpha1/zz_generated.openapi.go @@ -170,9 +170,9 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co Description: "FolderInfo briefly describes a folder -- unlike a folder resource, this is a partial record of the folder metadata used for navigating parents and children", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "uid": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "UID is the unique identifier for a folder (and the k8s name)", + Description: "Name is the k8s name (eg, the unique identifier) for a folder", Default: "", Type: []string{"string"}, Format: "", @@ -186,6 +186,13 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co Format: "", }, }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "The folder description", + Type: []string{"string"}, + Format: "", + }, + }, "parent": { SchemaProps: spec.SchemaProps{ Description: "The parent folder UID", @@ -193,8 +200,15 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co Format: "", }, }, + "detached": { + SchemaProps: spec.SchemaProps{ + Description: "This folder does not resolve", + Type: []string{"boolean"}, + Format: "", + }, + }, }, - Required: []string{"uid", "title"}, + Required: []string{"name", "title"}, }, }, } diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index f4f36f26d9c..dd4a4d3c826 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -126,11 +126,6 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API storage := map[string]rest.Storage{} storage[resourceInfo.StoragePath()] = legacyStore - storage[resourceInfo.StoragePath("parents")] = &subParentsREST{b.folderSvc} - storage[resourceInfo.StoragePath("access")] = &subAccessREST{b.folderSvc} - storage[resourceInfo.StoragePath("counts")] = &subCountREST{searcher: b.searcher} - - // enable dual writer if optsGetter != nil && dualWriteBuilder != nil { store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter) if err != nil { @@ -141,6 +136,11 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API return err } } + storage[resourceInfo.StoragePath("parents")] = &subParentsREST{ + getter: storage[resourceInfo.StoragePath()].(rest.Getter), // Get the parents + } + storage[resourceInfo.StoragePath("counts")] = &subCountREST{searcher: b.searcher} + storage[resourceInfo.StoragePath("access")] = &subAccessREST{b.folderSvc} apiGroupInfo.VersionedResourcesStorageMap[v0alpha1.VERSION] = storage b.storage = storage[resourceInfo.StoragePath()].(grafanarest.Storage) diff --git a/pkg/registry/apis/folders/sub_parents.go b/pkg/registry/apis/folders/sub_parents.go index f7761d075b1..f532b945f49 100644 --- a/pkg/registry/apis/folders/sub_parents.go +++ b/pkg/registry/apis/folders/sub_parents.go @@ -2,18 +2,20 @@ package folders import ( "context" + "fmt" "net/http" + "slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "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" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/folder" ) type subParentsREST struct { - service folder.Service + getter rest.Getter } var _ = rest.Connecter(&subParentsREST{}) @@ -43,32 +45,58 @@ func (r *subParentsREST) NewConnectOptions() (runtime.Object, bool, string) { } func (r *subParentsREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + obj, err := r.getter.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, err + } + folder, ok := obj.(*v0alpha1.Folder) + if !ok { + return nil, fmt.Errorf("expecting folder, found: %T", folder) + } + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - responder.Error(err) - return - } - - parents, err := r.service.GetParents(ctx, folder.GetParentsQuery{ - UID: name, - OrgID: ns.OrgID, - }) - if err != nil { - responder.Error(err) - return - } - info := &v0alpha1.FolderInfoList{ - Items: make([]v0alpha1.FolderInfo, 0), + Items: []v0alpha1.FolderInfo{}, } - for _, parent := range parents { + for folder != nil { + parent := "" + meta, _ := utils.MetaAccessor(folder) + if meta != nil { + parent = meta.GetFolder() + } info.Items = append(info.Items, v0alpha1.FolderInfo{ - UID: parent.UID, - Title: parent.Title, - Parent: parent.ParentUID, + 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 + } } + + // Start from the root + slices.Reverse(info.Items) responder.Object(http.StatusOK, info) }), nil }