diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index b4cbef367bf..2a1f1fbf7b9 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -384,6 +384,29 @@ func Test_DeletedDashboardsNotMigrated(t *testing.T) { assert.Equal(t, 1, dashCount) } +// Implementation inspired by ChatGPT, OpenAI's language model. +func Test_SortFolders(t *testing.T) { + folders := []folder.CreateFolderCommand{ + {UID: "a", ParentUID: "", Title: "Root"}, + {UID: "b", ParentUID: "a", Title: "Child of Root"}, + {UID: "c", ParentUID: "b", Title: "Child of b"}, + {UID: "d", ParentUID: "a", Title: "Another Child of Root"}, + {UID: "e", ParentUID: "", Title: "Another Root"}, + } + + expected := []folder.CreateFolderCommand{ + {UID: "a", ParentUID: "", Title: "Root"}, + {UID: "e", ParentUID: "", Title: "Another Root"}, + {UID: "b", ParentUID: "a", Title: "Child of Root"}, + {UID: "d", ParentUID: "a", Title: "Another Child of Root"}, + {UID: "c", ParentUID: "b", Title: "Child of b"}, + } + + sortedFolders := sortFolders(folders) + + require.Equal(t, expected, sortedFolders) +} + func ctxWithSignedInUser() context.Context { c := &contextmodel.ReqContext{ SignedInUser: &user.SignedInUser{OrgID: 1}, diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index e26450b241f..d9f4020dce0 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "time" snapshot "github.com/grafana/grafana-cloud-migration-snapshot/src" @@ -61,6 +62,7 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + folders = sortFolders(folders) for _, f := range folders { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.FolderDataType, @@ -114,6 +116,7 @@ func (s *Service) getDataSourceCommands(ctx context.Context) ([]datasources.AddD return result, err } +// getDashboardAndFolderCommands returns the json payloads required by the dashboard and folder creation APIs func (s *Service) getDashboardAndFolderCommands(ctx context.Context, signedInUser *user.SignedInUser) ([]dashboards.Dashboard, []folder.CreateFolderCommand, error) { dashs, err := s.dashboardService.GetAllDashboards(ctx) if err != nil { @@ -369,3 +372,43 @@ func (s *Service) updateSnapshotWithRetries(ctx context.Context, cmd cloudmigrat } return nil } + +// sortFolders implements a sort such that parent folders always come before their children +// Implementation inspired by ChatGPT, OpenAI's language model. +func sortFolders(input []folder.CreateFolderCommand) []folder.CreateFolderCommand { + // Map from UID to the corresponding folder for quick lookup + folderMap := make(map[string]folder.CreateFolderCommand) + for _, folder := range input { + folderMap[folder.UID] = folder + } + // Dynamic map of folderUID to depth + depthMap := make(map[string]int) + + // Function to get the depth of a folder based on its parent hierarchy + var getDepth func(uid string) int + getDepth = func(uid string) int { + if uid == "" { + return 0 + } + if d, ok := depthMap[uid]; ok { + return d + } + folder, exists := folderMap[uid] + if !exists || folder.ParentUID == "" { + return 1 + } + return 1 + getDepth(folder.ParentUID) + } + + // Calculate the depth of each folder + for _, folder := range input { + depthMap[folder.UID] = getDepth(folder.UID) + } + + // Sort folders by their depth, ensuring a stable sort + sort.SliceStable(input, func(i, j int) bool { + return depthMap[input[i].UID] < depthMap[input[j].UID] + }) + + return input +} diff --git a/pkg/services/cloudmigration/gmsclient/gms_client.go b/pkg/services/cloudmigration/gmsclient/gms_client.go index c942f69d220..a831d58aa00 100644 --- a/pkg/services/cloudmigration/gmsclient/gms_client.go +++ b/pkg/services/cloudmigration/gmsclient/gms_client.go @@ -254,7 +254,7 @@ func (c *gmsClientImpl) ReportEvent(ctx context.Context, session cloudmigration. return } - path := fmt.Sprintf("%s/api/v1/snapshots/events", c.buildBasePath(session.ClusterSlug)) + path := fmt.Sprintf("%s/api/v1/events", c.buildBasePath(session.ClusterSlug)) var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(event); err != nil {