2025-01-14 05:15:35 +08:00
package folderimpl
import (
"context"
"fmt"
"strings"
2025-05-20 04:25:08 +08:00
"go.opentelemetry.io/otel/trace"
2025-01-22 12:45:59 +08:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2025-01-14 05:15:35 +08:00
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2025-02-20 02:06:26 +08:00
"k8s.io/apimachinery/pkg/selection"
2025-01-14 05:15:35 +08:00
2025-02-20 02:06:26 +08:00
claims "github.com/grafana/authlib/types"
2025-04-15 04:20:10 +08:00
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
2025-09-08 23:27:49 +08:00
"github.com/grafana/grafana/pkg/apimachinery/utils"
2025-01-14 05:15:35 +08:00
"github.com/grafana/grafana/pkg/infra/log"
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
2025-02-20 02:06:26 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2025-02-12 03:14:25 +08:00
"github.com/grafana/grafana/pkg/services/apiserver/client"
2025-01-22 12:45:59 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2025-02-20 02:06:26 +08:00
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
2025-01-14 05:15:35 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2025-01-29 00:46:07 +08:00
"github.com/grafana/grafana/pkg/services/user"
2025-09-08 23:27:49 +08:00
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
2025-01-14 05:15:35 +08:00
"github.com/grafana/grafana/pkg/util"
)
2025-05-20 04:25:08 +08:00
const tracePrefix = "folder.unifiedstore."
2025-01-14 05:15:35 +08:00
type FolderUnifiedStoreImpl struct {
2025-01-29 00:46:07 +08:00
log log . Logger
2025-02-12 03:14:25 +08:00
k8sclient client . K8sHandler
2025-01-29 00:46:07 +08:00
userService user . Service
2025-05-20 04:25:08 +08:00
tracer trace . Tracer
2025-01-14 05:15:35 +08:00
}
// sqlStore implements the store interface.
var _ folder . Store = ( * FolderUnifiedStoreImpl ) ( nil )
2025-05-20 04:25:08 +08:00
func ProvideUnifiedStore ( k8sHandler client . K8sHandler , userService user . Service , tracer trace . Tracer ) * FolderUnifiedStoreImpl {
2025-01-14 05:15:35 +08:00
return & FolderUnifiedStoreImpl {
2025-01-29 00:46:07 +08:00
k8sclient : k8sHandler ,
log : log . New ( "folder-store" ) ,
userService : userService ,
2025-05-20 04:25:08 +08:00
tracer : tracer ,
2025-01-14 05:15:35 +08:00
}
}
func ( ss * FolderUnifiedStoreImpl ) Create ( ctx context . Context , cmd folder . CreateFolderCommand ) ( * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "Create" )
defer span . End ( )
2025-01-14 05:15:35 +08:00
obj , err := internalfolders . LegacyCreateCommandToUnstructured ( & cmd )
if err != nil {
return nil , err
}
2025-04-12 00:52:46 +08:00
out , err := ss . k8sclient . Create ( ctx , obj , cmd . OrgID , v1 . CreateOptions {
FieldValidation : v1 . FieldValidationIgnore } )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
2025-01-29 00:46:07 +08:00
folder , err := ss . UnstructuredToLegacyFolder ( ctx , out )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
return folder , err
}
func ( ss * FolderUnifiedStoreImpl ) Delete ( ctx context . Context , UIDs [ ] string , orgID int64 ) error {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "Delete" )
defer span . End ( )
2025-01-14 05:15:35 +08:00
for _ , uid := range UIDs {
2025-02-12 03:14:25 +08:00
err := ss . k8sclient . Delete ( ctx , uid , orgID , v1 . DeleteOptions { } )
2025-01-14 05:15:35 +08:00
if err != nil {
return err
}
}
return nil
}
func ( ss * FolderUnifiedStoreImpl ) Update ( ctx context . Context , cmd folder . UpdateFolderCommand ) ( * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "Update" )
defer span . End ( )
2025-02-12 03:14:25 +08:00
obj , err := ss . k8sclient . Get ( ctx , cmd . UID , cmd . OrgID , v1 . GetOptions { } )
2025-01-14 05:15:35 +08:00
if err != nil {
2025-03-05 06:31:41 +08:00
if apierrors . IsNotFound ( err ) {
return nil , dashboards . ErrFolderNotFound
}
2025-01-14 05:15:35 +08:00
return nil , err
}
2025-01-28 00:37:28 +08:00
updated := obj . DeepCopy ( )
2025-09-12 15:50:10 +08:00
meta , err := utils . MetaAccessor ( updated )
if err != nil {
return nil , err
}
2025-01-14 05:15:35 +08:00
2025-01-28 00:37:28 +08:00
if cmd . NewTitle != nil {
err = unstructured . SetNestedField ( updated . Object , * cmd . NewTitle , "spec" , "title" )
if err != nil {
return nil , err
}
2025-01-14 05:15:35 +08:00
}
2025-01-28 00:37:28 +08:00
if cmd . NewDescription != nil {
err = unstructured . SetNestedField ( updated . Object , * cmd . NewDescription , "spec" , "description" )
if err != nil {
return nil , err
}
}
if cmd . NewParentUID != nil {
meta . SetFolder ( * cmd . NewParentUID )
2025-04-30 08:04:30 +08:00
} else {
// only compare versions if not moving the folder
if ! cmd . Overwrite && ( cmd . Version != int ( obj . GetGeneration ( ) ) ) {
return nil , dashboards . ErrDashboardVersionMismatch
}
2025-01-14 05:15:35 +08:00
}
2025-09-12 15:50:10 +08:00
// nolint:staticcheck
if cmd . ManagerKindClassicFP != "" {
meta . SetManagerProperties ( utils . ManagerProperties {
Kind : utils . ManagerKindClassicFP ,
Identity : cmd . ManagerKindClassicFP ,
} )
}
2025-04-12 00:52:46 +08:00
out , err := ss . k8sclient . Update ( ctx , updated , cmd . OrgID , v1 . UpdateOptions {
FieldValidation : v1 . FieldValidationIgnore ,
} )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
2025-01-29 00:46:07 +08:00
return ss . UnstructuredToLegacyFolder ( ctx , out )
2025-01-14 05:15:35 +08:00
}
// If WithFullpath is true it computes also the full path of a folder.
// The full path is a string that contains the titles of all parent folders separated by a slash.
// For example, if the folder structure is:
//
// A
// └── B
// └── C
//
// The full path of C is "A/B/C".
// The full path of B is "A/B".
// The full path of A is "A".
// If a folder contains a slash in its title, it is escaped with a backslash.
// For example, if the folder structure is:
//
// A
// └── B/C
//
// The full path of C is "A/B\/C".
func ( ss * FolderUnifiedStoreImpl ) Get ( ctx context . Context , q folder . GetFolderQuery ) ( * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "Get" )
defer span . End ( )
2025-02-12 03:14:25 +08:00
out , err := ss . k8sclient . Get ( ctx , * q . UID , q . OrgID , v1 . GetOptions { } )
2025-01-22 12:45:59 +08:00
if err != nil && ! apierrors . IsNotFound ( err ) {
2025-01-14 05:15:35 +08:00
return nil , err
2025-01-22 12:45:59 +08:00
} else if err != nil || out == nil {
return nil , dashboards . ErrFolderNotFound
2025-01-14 05:15:35 +08:00
}
2025-01-28 00:37:28 +08:00
2025-01-29 00:46:07 +08:00
return ss . UnstructuredToLegacyFolder ( ctx , out )
2025-01-14 05:15:35 +08:00
}
func ( ss * FolderUnifiedStoreImpl ) GetParents ( ctx context . Context , q folder . GetParentsQuery ) ( [ ] * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "GetParents" )
defer span . End ( )
2025-01-14 05:15:35 +08:00
hits := [ ] * folder . Folder { }
2025-05-15 20:14:59 +08:00
parentUID := q . UID
for parentUID != "" {
folder , err := ss . Get ( ctx , folder . GetFolderQuery { UID : & parentUID , OrgID : q . OrgID } )
2025-01-14 05:15:35 +08:00
if err != nil {
2025-05-15 20:14:59 +08:00
if apierrors . IsForbidden ( err ) {
2025-02-25 20:38:32 +08:00
// If we get a Forbidden error when requesting the parent folder, it means the user does not have access
// to it, nor its parents. So we can stop looping
break
}
2025-01-14 05:15:35 +08:00
return nil , err
}
2025-05-15 20:14:59 +08:00
parentUID = folder . ParentUID
2025-01-14 05:15:35 +08:00
hits = append ( hits , folder )
}
2025-05-15 20:14:59 +08:00
if len ( hits ) == 0 {
return hits , nil
2025-01-14 05:15:35 +08:00
}
2025-05-15 20:14:59 +08:00
return util . Reverse ( hits [ 1 : ] ) , nil
2025-01-14 05:15:35 +08:00
}
2025-04-02 12:30:17 +08:00
func ( ss * FolderUnifiedStoreImpl ) GetChildren ( ctx context . Context , q folder . GetChildrenQuery ) ( [ ] * folder . FolderReference , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "GetChildren" )
defer span . End ( )
2025-02-20 02:06:26 +08:00
// the general folder is saved as an empty string in the database
if q . UID == folder . GeneralFolderUID {
q . UID = ""
}
if q . Limit == 0 {
q . Limit = folderSearchLimit
}
if q . Page == 0 {
q . Page = 1
}
2025-03-05 06:31:41 +08:00
if q . UID != "" {
// the original get children query fails if the parent folder does not exist
// check that the parent exists first
_ , err := ss . Get ( ctx , folder . GetFolderQuery { UID : & q . UID , OrgID : q . OrgID } )
if err != nil {
return nil , err
}
}
2025-05-16 03:36:52 +08:00
req := & resourcepb . ResourceSearchRequest {
Options : & resourcepb . ListOptions {
Fields : [ ] * resourcepb . Requirement {
2025-02-20 02:06:26 +08:00
{
Key : resource . SEARCH_FIELD_FOLDER ,
Operator : string ( selection . In ) ,
Values : [ ] string { q . UID } ,
} ,
} ,
} ,
Limit : q . Limit ,
// legacy fallback search requires page, unistore requires offset,
// so set both
Offset : q . Limit * ( q . Page - 1 ) ,
Page : q . Page ,
}
// only filter the folder UIDs if they are provided in the query
if len ( q . FolderUIDs ) > 0 {
2025-05-16 03:36:52 +08:00
req . Options . Fields = append ( req . Options . Fields , & resourcepb . Requirement {
2025-02-20 02:06:26 +08:00
Key : resource . SEARCH_FIELD_NAME ,
Operator : string ( selection . In ) ,
Values : q . FolderUIDs ,
} )
}
// now, get children of the parent folder
out , err := ss . k8sclient . Search ( ctx , q . OrgID , req )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
2025-02-20 02:06:26 +08:00
res , err := dashboardsearch . ParseResults ( out , 0 )
if err != nil {
return nil , err
}
allowK6Folder := ( q . SignedInUser != nil && q . SignedInUser . IsIdentityType ( claims . TypeServiceAccount ) )
2025-04-02 12:30:17 +08:00
hits := make ( [ ] * folder . FolderReference , 0 )
2025-02-20 02:06:26 +08:00
for _ , item := range res . Hits {
// filter out k6 folders if request is not from a service account
if item . Name == accesscontrol . K6FolderUID && ! allowK6Folder {
continue
2025-01-14 05:15:35 +08:00
}
2025-04-02 12:30:17 +08:00
f := & folder . FolderReference {
2025-04-02 20:20:23 +08:00
ID : item . Field . GetNestedInt64 ( resource . SEARCH_FIELD_LEGACY_ID ) ,
2025-04-02 12:30:17 +08:00
UID : item . Name ,
Title : item . Title ,
ParentUID : item . Folder ,
2025-09-29 19:28:26 +08:00
ManagedBy : item . ManagedBy . Kind ,
2025-03-29 07:13:08 +08:00
}
2025-01-14 05:15:35 +08:00
hits = append ( hits , f )
}
return hits , nil
}
// TODO use a single query to get the height of a folder
func ( ss * FolderUnifiedStoreImpl ) GetHeight ( ctx context . Context , foldrUID string , orgID int64 , parentUID * string ) ( int , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "GetHeight" )
defer span . End ( )
2025-01-14 05:15:35 +08:00
height := - 1
queue := [ ] string { foldrUID }
for len ( queue ) > 0 && height <= folder . MaxNestedFolderDepth {
length := len ( queue )
height ++
for i := 0 ; i < length ; i ++ {
ele := queue [ 0 ]
queue = queue [ 1 : ]
if parentUID != nil && * parentUID == ele {
2025-10-01 15:54:14 +08:00
return 0 , folder . ErrCircularReference . Errorf ( "circular reference detected" )
2025-01-14 05:15:35 +08:00
}
folders , err := ss . GetChildren ( ctx , folder . GetChildrenQuery { UID : ele , OrgID : orgID } )
if err != nil {
return 0 , err
}
for _ , f := range folders {
queue = append ( queue , f . UID )
}
}
}
if height > folder . MaxNestedFolderDepth {
ss . log . Warn ( "folder height exceeds the maximum allowed depth, You might have a circular reference" , "uid" , foldrUID , "orgId" , orgID , "maxDepth" , folder . MaxNestedFolderDepth )
}
return height , nil
}
// GetFolders returns org folders by their UIDs.
// If UIDs is empty, it returns all folders in the org.
// If WithFullpath is true it computes also the full path of a folder.
// The full path is a string that contains the titles of all parent folders separated by a slash.
// For example, if the folder structure is:
//
// A
// └── B
// └── C
//
// The full path of C is "A/B/C".
// The full path of B is "A/B".
// The full path of A is "A".
// If a folder contains a slash in its title, it is escaped with a backslash.
// For example, if the folder structure is:
//
// A
// └── B/C
//
// The full path of C is "A/B\/C".
//
// If FullpathUIDs is true it computes a string that contains the UIDs of all parent folders separated by slash.
// For example, if the folder structure is:
//
// A (uid: "uid1")
// └── B (uid: "uid2")
// └── C (uid: "uid3")
//
// The full path UIDs of C is "uid1/uid2/uid3".
// The full path UIDs of B is "uid1/uid2".
// The full path UIDs of A is "uid1".
func ( ss * FolderUnifiedStoreImpl ) GetFolders ( ctx context . Context , q folder . GetFoldersFromStoreQuery ) ( [ ] * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "GetFolders" )
defer span . End ( )
2025-05-16 00:43:44 +08:00
opts := v1 . ListOptions { }
2025-03-26 11:44:10 +08:00
if q . WithFullpath || q . WithFullpathUIDs {
// only supported in modes 0-2, to keep the alerting queries from causing tons of get folder requests
// to retrieve the parent for all folders in grafana
2025-03-27 04:50:53 +08:00
opts . LabelSelector = utils . LabelGetFullpath + "=true"
2025-03-26 11:44:10 +08:00
}
2025-05-16 00:43:44 +08:00
out , err := ss . list ( ctx , q . OrgID , opts )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
2025-03-28 04:01:07 +08:00
// convert item to legacy folder format
folders , err := ss . UnstructuredToLegacyFolderList ( ctx , out )
if err != nil {
return nil , err
}
2025-01-14 05:15:35 +08:00
2025-05-15 20:14:59 +08:00
filters := make ( map [ string ] struct { } , len ( q . UIDs ) )
for _ , uid := range q . UIDs {
filters [ uid ] = struct { } { }
2025-01-14 05:15:35 +08:00
}
2025-05-15 20:14:59 +08:00
folderMap := make ( map [ string ] * folder . Folder )
relations := make ( map [ string ] string )
if q . WithFullpath || q . WithFullpathUIDs {
for _ , folder := range folders {
folderMap [ folder . UID ] = folder
relations [ folder . UID ] = folder . ParentUID
2025-01-14 05:15:35 +08:00
}
}
2025-05-15 20:14:59 +08:00
hits := make ( [ ] * folder . Folder , 0 , len ( folders ) )
for _ , f := range folders {
if shouldSkipFolder ( f , filters ) {
continue
}
if ( q . WithFullpath || q . WithFullpathUIDs ) && f . Fullpath == "" {
buildFolderFullPaths ( f , relations , folderMap )
2025-01-14 05:15:35 +08:00
}
hits = append ( hits , f )
}
2025-05-15 20:14:59 +08:00
// TODO: return all nodes under those ancestors, requires building a tree
// if len(q.AncestorUIDs) > 0 {
// }
2025-01-14 05:15:35 +08:00
return hits , nil
}
func ( ss * FolderUnifiedStoreImpl ) GetDescendants ( ctx context . Context , orgID int64 , ancestor_uid string ) ( [ ] * folder . Folder , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "GetDescendants" )
defer span . End ( )
2025-05-16 00:43:44 +08:00
out , err := ss . list ( ctx , orgID , v1 . ListOptions { } )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
2025-03-28 04:01:07 +08:00
// convert item to legacy folder format
folders , err := ss . UnstructuredToLegacyFolderList ( ctx , out )
if err != nil {
return nil , err
}
2025-01-14 05:15:35 +08:00
2025-03-28 04:01:07 +08:00
nodes := map [ string ] * folder . Folder { }
for _ , f := range folders {
2025-01-14 05:15:35 +08:00
nodes [ f . UID ] = f
}
tree := map [ string ] map [ string ] * folder . Folder { }
for uid , f := range nodes {
parentUID := f . ParentUID
if parentUID == "" {
parentUID = "general"
}
if tree [ parentUID ] == nil {
tree [ parentUID ] = map [ string ] * folder . Folder { }
}
tree [ parentUID ] [ uid ] = f
}
descendantsMap := map [ string ] * folder . Folder { }
getDescendants ( nodes , tree , ancestor_uid , descendantsMap )
descendants := [ ] * folder . Folder { }
for _ , f := range descendantsMap {
descendants = append ( descendants , f )
}
return descendants , nil
}
func getDescendants ( nodes map [ string ] * folder . Folder , tree map [ string ] map [ string ] * folder . Folder , ancestor_uid string , descendantsMap map [ string ] * folder . Folder ) {
Alerting: Add read-only GMA rules to the new list view (#98116)
* Reuse prom groups generator between GMA, external DS and list view
* Improve generators, add initial support for GMA in grouped view components
* Improve handling of GMA rules
* Split componentes into files
* Improve error handling, simplify groups grouping
* Extract grafana rules component
* Reset yarn.lock
* Reset yarn.lock 2
* Update filters, adjust file names, add folder display name to GMA rules
* Re-enable filtering for cloud rules
* Rename AlertRuleLoader
* Add missing translations, fix lint errors
* Remove unused imports, update translations
* Fix responses in BE tests
* Update backend tests
* Update integration test
* Tidy up group page size constants
* Add error throwing to getGroups endpoint to prevent grafana usage
* Refactor FilterView to remove exhaustive check
* Refactor common props for grafana rule rendering
* Unify identifiers' discriminators, add comments, minor refactor
* Update translations
* Remove unnecessary prev page condition, add a few explanations
---------
Co-authored-by: fayzal-g <fayzal.ghantiwala@grafana.com>
Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
2025-01-15 18:36:32 +08:00
for uid := range tree [ ancestor_uid ] {
2025-01-14 05:15:35 +08:00
descendantsMap [ uid ] = nodes [ uid ]
getDescendants ( nodes , tree , uid , descendantsMap )
}
}
func ( ss * FolderUnifiedStoreImpl ) CountFolderContent ( ctx context . Context , orgID int64 , ancestor_uid string ) ( folder . DescendantCounts , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "CountFolderContent" )
defer span . End ( )
2025-02-12 03:14:25 +08:00
counts , err := ss . k8sclient . Get ( ctx , ancestor_uid , orgID , v1 . GetOptions { } , "counts" )
2025-01-14 05:15:35 +08:00
if err != nil {
2025-03-05 06:31:41 +08:00
if apierrors . IsNotFound ( err ) {
return nil , dashboards . ErrFolderNotFound
}
2025-01-14 05:15:35 +08:00
return nil , err
}
res , err := toFolderLegacyCounts ( counts )
return * res , err
}
2025-03-29 09:17:50 +08:00
func ( ss * FolderUnifiedStoreImpl ) CountInOrg ( ctx context . Context , orgID int64 ) ( int64 , error ) {
resp , err := ss . k8sclient . GetStats ( ctx , orgID )
if err != nil {
return 0 , err
}
if len ( resp . Stats ) != 1 {
return 0 , fmt . Errorf ( "expected 1 stat, got %d" , len ( resp . Stats ) )
}
return resp . Stats [ 0 ] . Count , nil
}
2025-05-16 00:43:44 +08:00
func ( ss * FolderUnifiedStoreImpl ) list ( ctx context . Context , orgID int64 , opts v1 . ListOptions ) ( * unstructured . UnstructuredList , error ) {
2025-05-20 04:25:08 +08:00
ctx , span := ss . tracer . Start ( ctx , tracePrefix + "list" )
defer span . End ( )
2025-05-16 00:43:44 +08:00
var allItems [ ] unstructured . Unstructured
listOpts := opts . DeepCopy ( )
if listOpts . Limit == 0 {
listOpts . Limit = folderListLimit
}
for {
out , err := ss . k8sclient . List ( ctx , orgID , * listOpts )
if err != nil {
return nil , err
}
if out == nil {
return nil , fmt . Errorf ( "k8s folder list returned nil" )
}
if len ( out . Items ) > 0 {
allItems = append ( allItems , out . Items ... )
}
if out . GetContinue ( ) == "" || ( opts . Limit > 0 && int64 ( len ( allItems ) ) >= opts . Limit ) {
break
}
listOpts . Continue = out . GetContinue ( )
}
result := & unstructured . UnstructuredList {
Items : allItems ,
}
if opts . Limit > 0 && int64 ( len ( allItems ) ) > opts . Limit {
result . Items = allItems [ : opts . Limit ]
}
return result , nil
}
2025-01-14 05:15:35 +08:00
func toFolderLegacyCounts ( u * unstructured . Unstructured ) ( * folder . DescendantCounts , error ) {
2025-04-11 20:09:52 +08:00
ds , err := folderv1 . UnstructuredToDescendantCounts ( u )
2025-01-14 05:15:35 +08:00
if err != nil {
return nil , err
}
var out = make ( folder . DescendantCounts )
for _ , v := range ds . Counts {
// if stats come from unified storage, we will use them
if v . Group != "sql-fallback" {
out [ v . Resource ] = v . Count
continue
}
// if stats are from single tenant DB and they are not in unified storage, we will use them
if _ , ok := out [ v . Resource ] ; ! ok {
out [ v . Resource ] = v . Count
}
}
return & out , nil
}
2025-01-29 21:50:49 +08:00
func computeFullPath ( parents [ ] * folder . Folder ) ( string , string ) {
fullpath := make ( [ ] string , len ( parents ) )
fullpathUIDs := make ( [ ] string , len ( parents ) )
for i , p := range parents {
fullpath [ i ] = p . Title
fullpathUIDs [ i ] = p . UID
}
return strings . Join ( fullpath , "/" ) , strings . Join ( fullpathUIDs , "/" )
}
2025-05-15 20:14:59 +08:00
func buildFolderFullPaths ( f * folder . Folder , relations map [ string ] string , folderMap map [ string ] * folder . Folder ) {
titles := make ( [ ] string , 0 )
uids := make ( [ ] string , 0 )
titles = append ( titles , f . Title )
uids = append ( uids , f . UID )
currentUID := f . UID
for currentUID != "" {
parentUID , exists := relations [ currentUID ]
if ! exists {
break
}
if parentUID == "" {
break
}
parentFolder , exists := folderMap [ parentUID ]
if ! exists {
break
}
titles = append ( titles , parentFolder . Title )
uids = append ( uids , parentFolder . UID )
currentUID = parentFolder . UID
}
f . Fullpath = strings . Join ( util . Reverse ( titles ) , "/" )
f . FullpathUIDs = strings . Join ( util . Reverse ( uids ) , "/" )
}
func shouldSkipFolder ( f * folder . Folder , filterUIDs map [ string ] struct { } ) bool {
if len ( filterUIDs ) == 0 {
return false
}
_ , exists := filterUIDs [ f . UID ]
return ! exists
}