From d88be2819d0af6070ccee6dff79dd4b54ce00867 Mon Sep 17 00:00:00 2001
From: Dana Axinte <53751979+dana-axinte@users.noreply.github.com>
Date: Mon, 7 Oct 2024 06:35:08 -0400
Subject: [PATCH] CloudMigrations: Store parent folder name in
cloud_migration_resource table (#94009)
* use name in fe
* store parent folder name in local db
* clean up
* tiny test
* trial react
* rename to parent name
* go lint
* generate api and ts
* go tests
* rearrange
* clean
* update with suggestions from josh
* make library elements work
* updates from comments
* global migration types
* parent name for alter table
---
.betterer.results | 3 +-
pkg/services/cloudmigration/api/api.go | 11 +-
pkg/services/cloudmigration/api/api_test.go | 2 +-
pkg/services/cloudmigration/api/dtos.go | 3 +-
.../cloudmigrationimpl/cloudmigration_test.go | 121 ++++++++++++++++++
.../fake/cloudmigration_fake.go | 18 +++
.../cloudmigrationimpl/snapshot_mgmt.go | 104 +++++++++++++--
pkg/services/cloudmigration/model.go | 4 +-
.../sqlstore/migrations/cloud_migrations.go | 6 +
public/api-enterprise-spec.json | 4 +
public/api-merged.json | 3 +
.../migrate-to-cloud/api/endpoints.gen.ts | 1 +
.../migrate-to-cloud/onprem/NameCell.tsx | 81 ++++++------
public/locales/en-US/grafana.json | 1 +
public/locales/pseudo-LOCALE/grafana.json | 1 +
public/openapi3.json | 3 +
16 files changed, 309 insertions(+), 57 deletions(-)
diff --git a/.betterer.results b/.betterer.results
index 867fddaac20..8bb613ae86d 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -4593,8 +4593,7 @@ exports[`better eslint`] = {
"public/app/features/migrate-to-cloud/onprem/NameCell.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "3"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"]
],
"public/app/features/notifications/StoredNotifications.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
diff --git a/pkg/services/cloudmigration/api/api.go b/pkg/services/cloudmigration/api/api.go
index 055da380018..338ee41f911 100644
--- a/pkg/services/cloudmigration/api/api.go
+++ b/pkg/services/cloudmigration/api/api.go
@@ -385,11 +385,12 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R
dtoResults := make([]MigrateDataResponseItemDTO, len(results))
for i := 0; i < len(results); i++ {
dtoResults[i] = MigrateDataResponseItemDTO{
- Name: results[i].Name,
- Type: MigrateDataType(results[i].Type),
- RefID: results[i].RefID,
- Status: ItemStatus(results[i].Status),
- Message: results[i].Error,
+ Name: results[i].Name,
+ Type: MigrateDataType(results[i].Type),
+ RefID: results[i].RefID,
+ Status: ItemStatus(results[i].Status),
+ Message: results[i].Error,
+ ParentName: results[i].ParentName,
}
}
diff --git a/pkg/services/cloudmigration/api/api_test.go b/pkg/services/cloudmigration/api/api_test.go
index 1b56c8a4c20..5ede5b8b46d 100644
--- a/pkg/services/cloudmigration/api/api_test.go
+++ b/pkg/services/cloudmigration/api/api_test.go
@@ -345,7 +345,7 @@ func TestCloudMigrationAPI_GetSnapshot(t *testing.T) {
requestUrl: "/api/cloudmigration/migration/1234/snapshot/1",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
- expectedBody: `{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z","results":[],"stats":{"types":{},"statuses":{},"total":0}}`,
+ expectedBody: `{"uid":"fake_uid","status":"CREATING","sessionUid":"1234","created":"0001-01-01T00:00:00Z","finished":"0001-01-01T00:00:00Z","results":[{"name":"dashboard name","parentName":"dashboard parent name","type":"DASHBOARD","refId":"123","status":"PENDING"},{"name":"datasource name","parentName":"dashboard parent name","type":"DATASOURCE","refId":"456","status":"OK"}],"stats":{"types":{},"statuses":{},"total":0}}`,
},
{
desc: "should return 403 if no used is not admin",
diff --git a/pkg/services/cloudmigration/api/dtos.go b/pkg/services/cloudmigration/api/dtos.go
index d16f616739e..594acf41c54 100644
--- a/pkg/services/cloudmigration/api/dtos.go
+++ b/pkg/services/cloudmigration/api/dtos.go
@@ -106,7 +106,8 @@ type MigrateDataResponseDTO struct {
}
type MigrateDataResponseItemDTO struct {
- Name string `json:"name"`
+ Name string `json:"name"`
+ ParentName string `json:"parentName"`
// required:true
Type MigrateDataType `json:"type"`
// required:true
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
index 3667b7fd081..44affa5ae92 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go
@@ -2,8 +2,10 @@ package cloudmigrationimpl
import (
"context"
+ "maps"
"os"
"path/filepath"
+ "slices"
"testing"
"time"
@@ -392,6 +394,7 @@ func Test_NonCoreDataSourcesHaveWarning(t *testing.T) {
Results: []cloudmigration.CloudMigrationResource{
{
Name: "1 name",
+ ParentName: "1 parent name",
Type: cloudmigration.DatasourceDataType,
RefID: "1", // this will be core
Status: cloudmigration.ItemStatusOK,
@@ -399,6 +402,7 @@ func Test_NonCoreDataSourcesHaveWarning(t *testing.T) {
},
{
Name: "2 name",
+ ParentName: "",
Type: cloudmigration.DatasourceDataType,
RefID: "2", // this will be non-core
Status: cloudmigration.ItemStatusOK,
@@ -406,6 +410,7 @@ func Test_NonCoreDataSourcesHaveWarning(t *testing.T) {
},
{
Name: "3 name",
+ ParentName: "3 parent name",
Type: cloudmigration.DatasourceDataType,
RefID: "3", // this will be non-core with an error
Status: cloudmigration.ItemStatusError,
@@ -414,6 +419,7 @@ func Test_NonCoreDataSourcesHaveWarning(t *testing.T) {
},
{
Name: "4 name",
+ ParentName: "4 folder name",
Type: cloudmigration.DatasourceDataType,
RefID: "4", // this will be deleted
Status: cloudmigration.ItemStatusOK,
@@ -564,6 +570,121 @@ func TestReportEvent(t *testing.T) {
require.Equal(t, 1, gmsMock.reportEventCalled)
})
}
+func TestGetFolderNamesForFolderUIDs(t *testing.T) {
+ s := setUpServiceTest(t, false).(*Service)
+ ctx, cancel := context.WithCancel(context.Background())
+ t.Cleanup(cancel)
+
+ user := &user.SignedInUser{OrgID: 1}
+
+ testcases := []struct {
+ folders []*folder.Folder
+ folderUIDs []string
+ expectedFolderNames []string
+ }{
+ {
+ folders: []*folder.Folder{
+ {UID: "folderUID-A", Title: "Folder A", OrgID: 1},
+ {UID: "folderUID-B", Title: "Folder B", OrgID: 1},
+ },
+ folderUIDs: []string{"folderUID-A", "folderUID-B"},
+ expectedFolderNames: []string{"Folder A", "Folder B"},
+ },
+ {
+ folders: []*folder.Folder{
+ {UID: "folderUID-A", Title: "Folder A", OrgID: 1},
+ },
+ folderUIDs: []string{"folderUID-A"},
+ expectedFolderNames: []string{"Folder A"},
+ },
+ {
+ folders: []*folder.Folder{},
+ folderUIDs: []string{"folderUID-A"},
+ expectedFolderNames: []string{""},
+ },
+ {
+ folders: []*folder.Folder{
+ {UID: "folderUID-A", Title: "Folder A", OrgID: 1},
+ },
+ folderUIDs: []string{"folderUID-A", "folderUID-B"},
+ expectedFolderNames: []string{"Folder A", ""},
+ },
+ {
+ folders: []*folder.Folder{},
+ folderUIDs: []string{""},
+ expectedFolderNames: []string{""},
+ },
+ {
+ folders: []*folder.Folder{},
+ folderUIDs: []string{},
+ expectedFolderNames: []string{},
+ },
+ }
+
+ for _, tc := range testcases {
+ s.folderService = &foldertest.FakeService{ExpectedFolders: tc.folders}
+
+ folderUIDsToFolders, err := s.getFolderNamesForFolderUIDs(ctx, user, tc.folderUIDs)
+ require.NoError(t, err)
+
+ resFolderNames := slices.Collect(maps.Values(folderUIDsToFolders))
+ require.Len(t, resFolderNames, len(tc.expectedFolderNames))
+
+ require.ElementsMatch(t, resFolderNames, tc.expectedFolderNames)
+ }
+}
+
+func TestGetParentNames(t *testing.T) {
+ s := setUpServiceTest(t, false).(*Service)
+ ctx, cancel := context.WithCancel(context.Background())
+ t.Cleanup(cancel)
+
+ user := &user.SignedInUser{OrgID: 1}
+ libraryElementFolderUID := "folderUID-A"
+ testcases := []struct {
+ fakeFolders []*folder.Folder
+ folders []folder.CreateFolderCommand
+ dashboards []dashboards.Dashboard
+ libraryElements []libraryElement
+ expectedDashParentNames []string
+ expectedFoldParentNames []string
+ }{
+ {
+ fakeFolders: []*folder.Folder{
+ {UID: "folderUID-A", Title: "Folder A", OrgID: 1, ParentUID: ""},
+ {UID: "folderUID-B", Title: "Folder B", OrgID: 1, ParentUID: "folderUID-A"},
+ },
+ folders: []folder.CreateFolderCommand{
+ {UID: "folderUID-C", Title: "Folder A", OrgID: 1, ParentUID: "folderUID-A"},
+ },
+ dashboards: []dashboards.Dashboard{
+ {UID: "dashboardUID-0", OrgID: 1, FolderUID: ""},
+ {UID: "dashboardUID-1", OrgID: 1, FolderUID: "folderUID-A"},
+ {UID: "dashboardUID-2", OrgID: 1, FolderUID: "folderUID-B"},
+ },
+ libraryElements: []libraryElement{
+ {UID: "libraryElementUID-0", FolderUID: &libraryElementFolderUID},
+ },
+ expectedDashParentNames: []string{"", "Folder A", "Folder B"},
+ expectedFoldParentNames: []string{"Folder A"},
+ },
+ }
+
+ for _, tc := range testcases {
+ s.folderService = &foldertest.FakeService{ExpectedFolders: tc.fakeFolders}
+
+ dataUIDsToParentNamesByType, err := s.getParentNames(ctx, user, tc.dashboards, tc.folders, tc.libraryElements)
+ require.NoError(t, err)
+
+ resDashParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[cloudmigration.DashboardDataType]))
+ require.Len(t, resDashParentNames, len(tc.expectedDashParentNames))
+ require.ElementsMatch(t, resDashParentNames, tc.expectedDashParentNames)
+
+ resFoldParentNames := slices.Collect(maps.Values(dataUIDsToParentNamesByType[cloudmigration.FolderDataType]))
+ require.Len(t, resFoldParentNames, len(tc.expectedFoldParentNames))
+ require.ElementsMatch(t, resFoldParentNames, tc.expectedFoldParentNames)
+ }
+}
func TestGetLibraryElementsCommands(t *testing.T) {
s := setUpServiceTest(t, false).(*Service)
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go
index 50ccfff8ef9..8b5f224f710 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go
@@ -98,10 +98,28 @@ func (m FakeServiceImpl) GetSnapshot(ctx context.Context, query cloudmigration.G
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
+ cloudMigrationResources := []cloudmigration.CloudMigrationResource{
+ {
+ Type: cloudmigration.DashboardDataType,
+ RefID: "123",
+ Status: cloudmigration.ItemStatusPending,
+ Name: "dashboard name",
+ ParentName: "dashboard parent name",
+ },
+ {
+ Type: cloudmigration.DatasourceDataType,
+ RefID: "456",
+ Status: cloudmigration.ItemStatusOK,
+ Name: "datasource name",
+ ParentName: "dashboard parent name",
+ },
+ }
+
return &cloudmigration.CloudMigrationSnapshot{
UID: "fake_uid",
SessionUID: "fake_uid",
Status: cloudmigration.SnapshotStatusCreating,
+ Resources: cloudMigrationResources,
}, nil
}
diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
index 48bf773d44b..45f6eefc74a 100644
--- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
+++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go
@@ -27,6 +27,13 @@ import (
"go.opentelemetry.io/otel/codes"
)
+var currentMigrationTypes = []cloudmigration.MigrateDataType{
+ cloudmigration.DatasourceDataType,
+ cloudmigration.FolderDataType,
+ cloudmigration.LibraryElementDataType,
+ cloudmigration.DashboardDataType,
+}
+
func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) {
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.getMigrationDataJSON")
defer span.End()
@@ -100,8 +107,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S
})
}
+ // Obtain the names of parent elements for Dashboard and Folders data types
+ parentNamesByType, err := s.getParentNames(ctx, signedInUser, dashs, folders, libraryElements)
+ if err != nil {
+ s.log.Error("Failed to get parent folder names", "err", err)
+ }
+
migrationData := &cloudmigration.MigrateDataRequest{
- Items: migrationDataSlice,
+ Items: migrationDataSlice,
+ ItemParentNames: parentNamesByType,
}
return migrationData, nil
@@ -306,20 +320,21 @@ func (s *Service) buildSnapshot(ctx context.Context, signedInUser *user.SignedIn
Data: item.Data,
})
+ parentName := ""
+ if _, exists := migrationData.ItemParentNames[item.Type]; exists {
+ parentName = migrationData.ItemParentNames[item.Type][item.RefID]
+ }
+
localSnapshotResource[i] = cloudmigration.CloudMigrationResource{
- Name: item.Name,
- Type: item.Type,
- RefID: item.RefID,
- Status: cloudmigration.ItemStatusPending,
+ Name: item.Name,
+ Type: item.Type,
+ RefID: item.RefID,
+ Status: cloudmigration.ItemStatusPending,
+ ParentName: parentName,
}
}
- for _, resourceType := range []cloudmigration.MigrateDataType{
- cloudmigration.DatasourceDataType,
- cloudmigration.FolderDataType,
- cloudmigration.LibraryElementDataType,
- cloudmigration.DashboardDataType,
- } {
+ for _, resourceType := range currentMigrationTypes {
for chunk := range slices.Chunk(resourcesGroupedByType[resourceType], int(maxItemsPerPartition)) {
if err := snapshotWriter.Write(string(resourceType), chunk); err != nil {
return fmt.Errorf("writing resources to snapshot writer: resourceType=%s %w", resourceType, err)
@@ -533,3 +548,70 @@ func sortFolders(input []folder.CreateFolderCommand) []folder.CreateFolderComman
return input
}
+
+// getFolderNamesForFolderUIDs queries the folders service to obtain folder names for a list of folderUIDs
+func (s *Service) getFolderNamesForFolderUIDs(ctx context.Context, signedInUser *user.SignedInUser, folderUIDs []string) (map[string](string), error) {
+ folders, err := s.folderService.GetFolders(ctx, folder.GetFoldersQuery{
+ UIDs: folderUIDs,
+ SignedInUser: signedInUser,
+ WithFullpathUIDs: true,
+ })
+ if err != nil {
+ s.log.Error("Failed to obtain folders from folder UIDs", "err", err)
+ return nil, err
+ }
+
+ folderUIDsToNames := make(map[string](string), len(folderUIDs))
+ for _, folderUID := range folderUIDs {
+ folderUIDsToNames[folderUID] = ""
+ }
+ for _, f := range folders {
+ folderUIDsToNames[f.UID] = f.Title
+ }
+ return folderUIDsToNames, nil
+}
+
+// getParentNames finds the parent names for resources and returns a map of data type: {data UID : parentName}
+// for dashboards, folders and library elements - the parent is the parent folder
+func (s *Service) getParentNames(ctx context.Context, signedInUser *user.SignedInUser, dashboards []dashboards.Dashboard, folders []folder.CreateFolderCommand, libraryElements []libraryElement) (map[cloudmigration.MigrateDataType]map[string](string), error) {
+ parentNamesByType := make(map[cloudmigration.MigrateDataType]map[string](string))
+ for _, dataType := range currentMigrationTypes {
+ parentNamesByType[dataType] = make(map[string]string)
+ }
+
+ // Obtain list of unique folderUIDs
+ parentFolderUIDsSet := make(map[string]struct{}, len(dashboards)+len(folders)+len(libraryElements))
+ for _, dashboard := range dashboards {
+ parentFolderUIDsSet[dashboard.FolderUID] = struct{}{}
+ }
+ for _, f := range folders {
+ parentFolderUIDsSet[f.ParentUID] = struct{}{}
+ }
+ for _, libraryElement := range libraryElements {
+ parentFolderUIDsSet[*libraryElement.FolderUID] = struct{}{}
+ }
+ parentFolderUIDsSlice := make([]string, 0, len(parentFolderUIDsSet))
+ for parentFolderUID := range parentFolderUIDsSet {
+ parentFolderUIDsSlice = append(parentFolderUIDsSlice, parentFolderUID)
+ }
+
+ // Obtain folder names given a list of folderUIDs
+ foldersUIDsToFolderName, err := s.getFolderNamesForFolderUIDs(ctx, signedInUser, parentFolderUIDsSlice)
+ if err != nil {
+ s.log.Error("Failed to get parent folder names from folder UIDs", "err", err)
+ return parentNamesByType, err
+ }
+
+ // Prepare map of {data type: {data UID : parentName}}
+ for _, dashboard := range dashboards {
+ parentNamesByType[cloudmigration.DashboardDataType][dashboard.UID] = foldersUIDsToFolderName[dashboard.FolderUID]
+ }
+ for _, f := range folders {
+ parentNamesByType[cloudmigration.FolderDataType][f.UID] = foldersUIDsToFolderName[f.ParentUID]
+ }
+ for _, libraryElement := range libraryElements {
+ parentNamesByType[cloudmigration.LibraryElementDataType][libraryElement.UID] = foldersUIDsToFolderName[*libraryElement.FolderUID]
+ }
+
+ return parentNamesByType, err
+}
diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go
index 786df738ca5..01697e9f80c 100644
--- a/pkg/services/cloudmigration/model.go
+++ b/pkg/services/cloudmigration/model.go
@@ -75,6 +75,7 @@ type CloudMigrationResource struct {
Error string `xorm:"error_string" json:"error"`
SnapshotUID string `xorm:"snapshot_uid"`
+ ParentName string `xorm:"parent_name" json:"parentName"`
}
type MigrateDataType string
@@ -185,7 +186,8 @@ type Base64HGInstance struct {
// GMS domain structs
type MigrateDataRequest struct {
- Items []MigrateDataRequestItem
+ Items []MigrateDataRequestItem
+ ItemParentNames map[MigrateDataType]map[string](string)
}
type MigrateDataRequestItem struct {
diff --git a/pkg/services/sqlstore/migrations/cloud_migrations.go b/pkg/services/sqlstore/migrations/cloud_migrations.go
index 8b9e3a72d60..7dd4b167595 100644
--- a/pkg/services/sqlstore/migrations/cloud_migrations.go
+++ b/pkg/services/sqlstore/migrations/cloud_migrations.go
@@ -164,4 +164,10 @@ func addCloudMigrationsMigrations(mg *Migrator) {
Type: DB_Text,
Nullable: true,
}))
+
+ mg.AddMigration("add cloud_migration_resource.parent_name column", NewAddColumnMigration(migrationResourceTable, &Column{
+ Name: "parent_name",
+ Type: DB_Text,
+ Nullable: true,
+ }))
}
diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json
index e2d491516ff..69333249132 100644
--- a/public/api-enterprise-spec.json
+++ b/public/api-enterprise-spec.json
@@ -3382,6 +3382,7 @@
}
},
"CorrelationType": {
+ "description": "the type of correlation, either query for containing query information, or external for containing an external URL\n+enum",
"type": "string"
},
"CreateAccessTokenResponseDTO": {
@@ -5419,6 +5420,9 @@
"name": {
"type": "string"
},
+ "parentName": {
+ "type": "string"
+ },
"refId": {
"type": "string"
},
diff --git a/public/api-merged.json b/public/api-merged.json
index a149ee3ead8..b57bd02ce3d 100644
--- a/public/api-merged.json
+++ b/public/api-merged.json
@@ -16909,6 +16909,9 @@
"name": {
"type": "string"
},
+ "parentName": {
+ "type": "string"
+ },
"refId": {
"type": "string"
},
diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
index 24615c0608e..5494d76674b 100644
--- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
+++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
@@ -166,6 +166,7 @@ export type CreateSnapshotResponseDto = {
export type MigrateDataResponseItemDto = {
message?: string;
name?: string;
+ parentName?: string;
refId: string;
status: 'OK' | 'WARNING' | 'ERROR' | 'PENDING' | 'UNKNOWN';
type: 'DASHBOARD' | 'DATASOURCE' | 'FOLDER' | 'LIBRARY_ELEMENT';
diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
index 74d49b51244..a796ebd917c 100644
--- a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
+++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx
@@ -40,14 +40,6 @@ function ResourceInfo({ data }: { data: ResourceTableItem }) {
}
}
-function getDashboardTitle(dashboardData: object) {
- if ('title' in dashboardData && typeof dashboardData.title === 'string') {
- return dashboardData.title;
- }
-
- return undefined;
-}
-
function DatasourceInfo({ data }: { data: ResourceTableItem }) {
const datasourceUID = data.refId;
const datasource = useDatasource(datasourceUID);
@@ -75,72 +67,91 @@ function DatasourceInfo({ data }: { data: ResourceTableItem }) {
);
}
+function getTitleFromDashboardJSON(dashboardData: object | undefined): string | null {
+ if (dashboardData && 'title' in dashboardData && typeof dashboardData.title === 'string') {
+ return dashboardData.title;
+ }
+
+ return null;
+}
+
function DashboardInfo({ data }: { data: ResourceTableItem }) {
const dashboardUID = data.refId;
- // TODO: really, the API should return this directly
- const { data: dashboardData, isError } = useGetDashboardByUidQuery({
- uid: dashboardUID,
- });
+ const skipApiCall = !!data.name && !!data.parentName;
+ const {
+ data: dashboardData,
+ isLoading,
+ isError,
+ } = useGetDashboardByUidQuery({ uid: dashboardUID }, { skip: skipApiCall });
- const dashboardName = useMemo(() => {
- return (dashboardData?.dashboard && getDashboardTitle(dashboardData.dashboard)) ?? dashboardUID;
- }, [dashboardData, dashboardUID]);
+ const dashboardName = data.name || getTitleFromDashboardJSON(dashboardData?.dashboard) || dashboardUID;
+ const dashboardParentName = data.parentName || dashboardData?.meta?.folderTitle || 'Dashboards';
if (isError) {
- // Not translated because this is only temporary until the data comes through in the MigrationRun API
return (
<>
- Unable to load dashboard
+
+ Unable to load dashboard
+
Dashboard {dashboardUID}
>
);
}
- if (!dashboardData) {
+ if (isLoading) {
return ;
}
return (
<>
{dashboardName}
- {dashboardData.meta?.folderTitle ?? 'Dashboards'}
+ {dashboardParentName}
>
);
}
function FolderInfo({ data }: { data: ResourceTableItem }) {
- const { data: folderData, isLoading, isError } = useGetFolderQuery(data.refId);
+ const folderUID = data.refId;
+ const skipApiCall = !!data.name && !!data.parentName;
- if (isLoading || !folderData) {
- return ;
- }
+ const { data: folderData, isLoading, isError } = useGetFolderQuery(folderUID, { skip: skipApiCall });
+
+ const folderName = data.name || folderData?.title;
+ const folderParentName = data.parentName || folderData?.parents?.[folderData.parents.length - 1]?.title;
if (isError) {
return (
<>
- Unable to load dashboard
- Dashboard {data.refId}
+ Unable to load folder
+ Folder {data.refId}
>
);
}
- const parentFolderName = folderData.parents?.[folderData.parents.length - 1]?.title;
+ if (isLoading) {
+ return ;
+ }
return (
<>
- {folderData.title}
- {parentFolderName ?? 'Dashboards'}
+ {folderName}
+ {folderParentName ?? 'Dashboards'}
>
);
}
function LibraryElementInfo({ data }: { data: ResourceTableItem }) {
const uid = data.refId;
- const { data: libraryElementData, isError, isLoading } = useGetLibraryElementByUidQuery({ libraryElementUid: uid });
+ const skipApiCall = !!data.name && !!data.parentName;
- const name = useMemo(() => {
- return data?.name || (libraryElementData?.result?.name ?? uid);
- }, [data, libraryElementData, uid]);
+ const {
+ data: libraryElementData,
+ isError,
+ isLoading,
+ } = useGetLibraryElementByUidQuery({ libraryElementUid: uid }, { skip: skipApiCall });
+
+ const name = data.name || libraryElementData?.result?.name || uid;
+ const parentName = data.parentName || libraryElementData?.result?.meta?.folderName || 'General';
if (isError) {
return (
@@ -158,16 +169,14 @@ function LibraryElementInfo({ data }: { data: ResourceTableItem }) {
);
}
- if (isLoading || !libraryElementData) {
+ if (isLoading) {
return ;
}
- const folderName = libraryElementData?.result?.meta?.folderName ?? 'General';
-
return (
<>
{name}
- {folderName}
+ {parentName}
>
);
}
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 63d9bb626dc..49128c086d3 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -1478,6 +1478,7 @@
"warning-details-button": "Details"
},
"resource-table": {
+ "dashboard-load-error": "Unable to load dashboard",
"error-library-element-sub": "Library Element {uid}",
"error-library-element-title": "Unable to load library element",
"unknown-datasource-title": "Data source {{datasourceUID}}",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index fabb719f921..d652a3025aa 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -1478,6 +1478,7 @@
"warning-details-button": "Đęŧäįľş"
},
"resource-table": {
+ "dashboard-load-error": "Ůʼnäþľę ŧő ľőäđ đäşĥþőäřđ",
"error-library-element-sub": "Ŀįþřäřy Ēľęmęʼnŧ {ūįđ}",
"error-library-element-title": "Ůʼnäþľę ŧő ľőäđ ľįþřäřy ęľęmęʼnŧ",
"unknown-datasource-title": "Đäŧä şőūřčę {{datasourceUID}}",
diff --git a/public/openapi3.json b/public/openapi3.json
index 65eec59a72c..365f64a5b80 100644
--- a/public/openapi3.json
+++ b/public/openapi3.json
@@ -7130,6 +7130,9 @@
"name": {
"type": "string"
},
+ "parentName": {
+ "type": "string"
+ },
"refId": {
"type": "string"
},