mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Export: Fix export/import of dash with library panels (#49319)
This commit is contained in:
parent
fd3c986ceb
commit
8053f770c1
@ -4606,7 +4606,7 @@ exports[`better eslint`] = {
|
||||
[90, 20, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[126, 18, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
],
|
||||
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:2274633003": [
|
||||
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:3288513450": [
|
||||
[22, 6, 59, "Do not use any type assertions.", "3685154675"],
|
||||
[22, 6, 49, "Do not use any type assertions.", "1184085652"],
|
||||
[25, 15, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
@ -4631,27 +4631,25 @@ exports[`better eslint`] = {
|
||||
[318, 17, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[325, 20, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[334, 19, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[370, 13, 18, "Do not use any type assertions.", "3420875325"],
|
||||
[371, 9, 55, "Do not use any type assertions.", "1888720338"],
|
||||
[371, 17, 10, "Do not use any type assertions.", "1501063862"],
|
||||
[371, 24, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[371, 61, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
[364, 13, 18, "Do not use any type assertions.", "3420875325"],
|
||||
[365, 9, 55, "Do not use any type assertions.", "1888720338"],
|
||||
[365, 17, 10, "Do not use any type assertions.", "1501063862"],
|
||||
[365, 24, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[365, 61, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
],
|
||||
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts:568589687": [
|
||||
[17, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[44, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[65, 43, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[72, 45, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[79, 30, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[82, 25, 17, "Do not use any type assertions.", "1733699692"],
|
||||
[82, 39, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[83, 20, 33, "Do not use any type assertions.", "1729190460"],
|
||||
[83, 21, 17, "Do not use any type assertions.", "1733699692"],
|
||||
[83, 35, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[148, 41, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[176, 29, 31, "Do not use any type assertions.", "1451241646"],
|
||||
[176, 29, 13, "Do not use any type assertions.", "2146830713"],
|
||||
[195, 32, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
"public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts:565128344": [
|
||||
[18, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[47, 37, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[65, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[86, 43, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[93, 45, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[100, 30, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[102, 37, 17, "Do not use any type assertions.", "1733699692"],
|
||||
[102, 51, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[169, 41, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[198, 29, 31, "Do not use any type assertions.", "1451241646"],
|
||||
[198, 29, 13, "Do not use any type assertions.", "2146830713"],
|
||||
[217, 32, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
],
|
||||
"public/app/features/dashboard/components/DashNav/DashNav.tsx:1533394562": [
|
||||
[67, 87, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
@ -6193,7 +6191,7 @@ exports[`better eslint`] = {
|
||||
[81, 19, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[82, 25, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
],
|
||||
"public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:72231267": [
|
||||
"public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:123008784": [
|
||||
[62, 28, 9, "Do not use any type assertions.", "3692209159"],
|
||||
[62, 34, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[125, 22, 23, "Do not use any type assertions.", "2412780303"],
|
||||
@ -6221,7 +6219,7 @@ exports[`better eslint`] = {
|
||||
[38, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[44, 23, 174, "Do not use any type assertions.", "1993531937"]
|
||||
],
|
||||
"public/app/features/manage-dashboards/state/actions.ts:2109642147": [
|
||||
"public/app/features/manage-dashboards/state/actions.ts:658353090": [
|
||||
[43, 47, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[51, 38, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[54, 20, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
@ -6244,7 +6242,7 @@ exports[`better eslint`] = {
|
||||
[295, 9, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[319, 31, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
],
|
||||
"public/app/features/manage-dashboards/state/reducers.test.ts:4097685863": [
|
||||
"public/app/features/manage-dashboards/state/reducers.test.ts:354708423": [
|
||||
[94, 25, 53, "Do not use any type assertions.", "3616064645"],
|
||||
[95, 23, 51, "Do not use any type assertions.", "2715348726"],
|
||||
[108, 25, 53, "Do not use any type assertions.", "3616064645"],
|
||||
@ -6255,7 +6253,7 @@ exports[`better eslint`] = {
|
||||
[125, 23, 51, "Do not use any type assertions.", "2715348726"],
|
||||
[128, 23, 71, "Do not use any type assertions.", "1935232832"]
|
||||
],
|
||||
"public/app/features/manage-dashboards/state/reducers.ts:3417985415": [
|
||||
"public/app/features/manage-dashboards/state/reducers.ts:2004505684": [
|
||||
[58, 13, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
[68, 10, 21, "Do not use any type assertions.", "2133660528"],
|
||||
[76, 81, 3, "Unexpected any. Specify a different type.", "193409811"],
|
||||
|
@ -1158,7 +1158,7 @@ func (m *mockLibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Con
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error {
|
||||
func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@ -62,6 +63,23 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Maintain backwards compatibility by transforming array of library elements to map
|
||||
libraryElements := generatedDash.Get("__elements")
|
||||
libElementsArr, err := libraryElements.Array()
|
||||
if err == nil {
|
||||
elementMap := map[string]interface{}{}
|
||||
for _, el := range libElementsArr {
|
||||
libElement := simplejson.NewFromAny(el)
|
||||
elementMap[libElement.Get("uid").MustString()] = el
|
||||
}
|
||||
libraryElements = simplejson.NewFromAny(elementMap)
|
||||
}
|
||||
|
||||
// No need to keep these in the stored dashboard JSON
|
||||
generatedDash.Del("__elements")
|
||||
generatedDash.Del("__inputs")
|
||||
generatedDash.Del("__requires")
|
||||
|
||||
saveCmd := models.SaveDashboardCommand{
|
||||
Dashboard: generatedDash,
|
||||
OrgId: req.User.OrgId,
|
||||
@ -83,7 +101,7 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, savedDashboard, req.FolderId)
|
||||
err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestImportDashboardService(t *testing.T) {
|
||||
importLibraryPanelsForDashboard := false
|
||||
connectLibraryPanelsForDashboardCalled := false
|
||||
libraryPanelService := &libraryPanelServiceMock{
|
||||
importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error {
|
||||
importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error {
|
||||
importLibraryPanelsForDashboard = true
|
||||
return nil
|
||||
},
|
||||
@ -185,8 +185,8 @@ func (s *dashboardServiceMock) ImportDashboard(ctx context.Context, dto *dashboa
|
||||
|
||||
type libraryPanelServiceMock struct {
|
||||
librarypanels.Service
|
||||
connectLibraryPanelsForDashboardFunc func(ctx context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error
|
||||
importLibraryPanelsForDashboardFunc func(ctx context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error
|
||||
connectLibraryPanelsForDashboardFunc func(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error
|
||||
importLibraryPanelsForDashboardFunc func(c context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error
|
||||
}
|
||||
|
||||
func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error {
|
||||
@ -197,9 +197,9 @@ func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.C
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error {
|
||||
func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error {
|
||||
if s.importLibraryPanelsForDashboardFunc != nil {
|
||||
return s.importLibraryPanelsForDashboardFunc(ctx, signedInUser, dash, folderID)
|
||||
return s.importLibraryPanelsForDashboardFunc(ctx, signedInUser, libraryPanels, panels, folderID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -31,7 +31,12 @@ type Service interface {
|
||||
LoadLibraryPanelsForDashboard(c context.Context, dash *models.Dashboard) error
|
||||
CleanLibraryPanelsForDashboard(dash *models.Dashboard) error
|
||||
ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error
|
||||
ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error
|
||||
ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error
|
||||
}
|
||||
|
||||
type LibraryInfo struct {
|
||||
Panels []*interface{}
|
||||
LibraryPanels *simplejson.Json
|
||||
}
|
||||
|
||||
// LibraryPanelService is the service for the Panel Library feature.
|
||||
@ -89,7 +94,6 @@ func loadLibraryPanelsRecursively(elements map[string]libraryelements.LibraryEle
|
||||
elem.Set("gridPos", gridPos)
|
||||
}
|
||||
elem.Set("id", panelAsJSON.Get("id").MustInt64())
|
||||
elem.Set("type", fmt.Sprintf("Library panel with UID: \"%s\"", UID))
|
||||
elem.Set("libraryPanel", map[string]interface{}{
|
||||
"uid": UID,
|
||||
})
|
||||
@ -253,12 +257,11 @@ func connectLibraryPanelsRecursively(c context.Context, panels []interface{}, li
|
||||
}
|
||||
|
||||
// ImportLibraryPanelsForDashboard loops through all panels in dashboard JSON and creates any missing library panels in the database.
|
||||
func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error {
|
||||
return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, dash.Data, folderID)
|
||||
func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error {
|
||||
return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, libraryPanels, panels, folderID)
|
||||
}
|
||||
|
||||
func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser *models.SignedInUser, parent *simplejson.Json, folderID int64) error {
|
||||
panels := parent.Get("panels").MustArray()
|
||||
func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser *models.SignedInUser, libraryPanels *simplejson.Json, panels []interface{}, folderID int64) error {
|
||||
for _, panel := range panels {
|
||||
panelAsJSON := simplejson.NewFromAny(panel)
|
||||
libraryPanel := panelAsJSON.Get("libraryPanel")
|
||||
@ -269,7 +272,7 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S
|
||||
|
||||
// we have a row
|
||||
if panelType == "row" {
|
||||
err := importLibraryPanelsRecursively(c, service, signedInUser, panelAsJSON, folderID)
|
||||
err := importLibraryPanelsRecursively(c, service, signedInUser, libraryPanels, panelAsJSON.Get("panels").MustArray(), folderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -281,22 +284,24 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S
|
||||
if len(UID) == 0 {
|
||||
return errLibraryPanelHeaderUIDMissing
|
||||
}
|
||||
name := libraryPanel.Get("name").MustString()
|
||||
if len(name) == 0 {
|
||||
return errLibraryPanelHeaderNameMissing
|
||||
}
|
||||
|
||||
_, err := service.GetElement(c, signedInUser, UID)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, libraryelements.ErrLibraryElementNotFound) {
|
||||
panelAsJSON.Set("libraryPanel",
|
||||
map[string]interface{}{
|
||||
"uid": UID,
|
||||
"name": name,
|
||||
})
|
||||
Model, err := json.Marshal(&panelAsJSON)
|
||||
name := libraryPanel.Get("name").MustString()
|
||||
if len(name) == 0 {
|
||||
return errLibraryPanelHeaderNameMissing
|
||||
}
|
||||
|
||||
elementModel := libraryPanels.Get(UID).Get("model")
|
||||
elementModel.Set("libraryPanel", map[string]interface{}{
|
||||
"uid": UID,
|
||||
})
|
||||
|
||||
Model, err := json.Marshal(&elementModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package librarypanels
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -439,7 +438,6 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) {
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": sc.initialResult.Result.UID,
|
||||
},
|
||||
"type": fmt.Sprintf("Library panel with UID: \"%s\"", sc.initialResult.Result.UID),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1021,30 +1019,35 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) {
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
}
|
||||
|
||||
dashJSON := map[string]interface{}{
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
missingModel,
|
||||
var libraryElements = map[string]interface{}{
|
||||
missingUID: map[string]interface{}{
|
||||
"model": missingModel,
|
||||
},
|
||||
}
|
||||
dash := models.Dashboard{
|
||||
Title: "Testing ImportLibraryPanelsForDashboard",
|
||||
Data: simplejson.NewFromAny(dashJSON),
|
||||
|
||||
panels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": missingUID,
|
||||
"name": missingName,
|
||||
},
|
||||
},
|
||||
}
|
||||
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id)
|
||||
|
||||
_, err := sc.elementService.GetElement(sc.ctx, sc.user, missingUID)
|
||||
|
||||
require.EqualError(t, err, libraryelements.ErrLibraryElementNotFound.Error())
|
||||
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB, 0)
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
element, err := sc.elementService.GetElement(sc.ctx, sc.user, missingUID)
|
||||
@ -1061,39 +1064,28 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) {
|
||||
var existingUID = sc.initialResult.Result.UID
|
||||
var existingName = sc.initialResult.Result.Name
|
||||
|
||||
dashJSON := map[string]interface{}{
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
panels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"description": "Updated description",
|
||||
"datasource": "Updated datasource",
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": sc.initialResult.Result.UID,
|
||||
"name": sc.initialResult.Result.Name,
|
||||
},
|
||||
"title": "Updated Title",
|
||||
"type": "stat",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": sc.initialResult.Result.UID,
|
||||
"name": sc.initialResult.Result.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
dash := models.Dashboard{
|
||||
Title: "Testing ImportLibraryPanelsForDashboard",
|
||||
Data: simplejson.NewFromAny(dashJSON),
|
||||
}
|
||||
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id)
|
||||
|
||||
_, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB, sc.folder.Id)
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
element, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID)
|
||||
@ -1129,6 +1121,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) {
|
||||
"title": "Outside row",
|
||||
"type": "text",
|
||||
}
|
||||
|
||||
var insideUID = "iK7NsyDNz"
|
||||
var insideName = "Inside Library Panel"
|
||||
var insideModel = map[string]interface{}{
|
||||
@ -1148,54 +1141,67 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) {
|
||||
"type": "text",
|
||||
}
|
||||
|
||||
dashJSON := map[string]interface{}{
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"collapsed": true,
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
},
|
||||
"id": int64(2),
|
||||
"type": "row",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(3),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 7,
|
||||
},
|
||||
},
|
||||
insideModel,
|
||||
},
|
||||
},
|
||||
outsideModel,
|
||||
var libraryElements = map[string]interface{}{
|
||||
outsideUID: map[string]interface{}{
|
||||
"model": outsideModel,
|
||||
},
|
||||
insideUID: map[string]interface{}{
|
||||
"model": insideModel,
|
||||
},
|
||||
}
|
||||
dash := models.Dashboard{
|
||||
Title: "Testing ImportLibraryPanelsForDashboard",
|
||||
Data: simplejson.NewFromAny(dashJSON),
|
||||
|
||||
panels := []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(1),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": outsideUID,
|
||||
"name": outsideName,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"collapsed": true,
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
},
|
||||
"id": int64(2),
|
||||
"type": "row",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": int64(3),
|
||||
"gridPos": map[string]interface{}{
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 7,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": insideUID,
|
||||
"name": insideName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id)
|
||||
|
||||
_, err := sc.elementService.GetElement(sc.ctx, sc.user, outsideUID)
|
||||
require.EqualError(t, err, libraryelements.ErrLibraryElementNotFound.Error())
|
||||
_, err = sc.elementService.GetElement(sc.ctx, sc.user, insideUID)
|
||||
require.EqualError(t, err, libraryelements.ErrLibraryElementNotFound.Error())
|
||||
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB, 0)
|
||||
err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
element, err := sc.elementService.GetElement(sc.ctx, sc.user, outsideUID)
|
||||
|
@ -337,13 +337,10 @@ describe('given dashboard with repeated panels', () => {
|
||||
});
|
||||
|
||||
it('should add library panels as elements', () => {
|
||||
const element: LibraryElementExport = exported.__elements.find(
|
||||
(element: LibraryElementExport) => element.uid === 'ah8NqyDPs'
|
||||
);
|
||||
const element: LibraryElementExport = exported.__elements['ah8NqyDPs'];
|
||||
expect(element.name).toBe('Library Panel 2');
|
||||
expect(element.kind).toBe(LibraryElementKind.Panel);
|
||||
expect(element.model).toEqual({
|
||||
id: 17,
|
||||
datasource: { type: 'other2', uid: '$ds' },
|
||||
targets: [{ refId: 'A', datasource: { type: 'other2', uid: '$ds' } }],
|
||||
type: 'graph',
|
||||
@ -351,13 +348,10 @@ describe('given dashboard with repeated panels', () => {
|
||||
});
|
||||
|
||||
it('should add library panels in collapsed rows as elements', () => {
|
||||
const element: LibraryElementExport = exported.__elements.find(
|
||||
(element: LibraryElementExport) => element.uid === 'jL6MrxCMz'
|
||||
);
|
||||
const element: LibraryElementExport = exported.__elements['jL6MrxCMz'];
|
||||
expect(element.name).toBe('Library Panel');
|
||||
expect(element.kind).toBe(LibraryElementKind.Panel);
|
||||
expect(element.model).toEqual({
|
||||
id: 16,
|
||||
type: 'graph',
|
||||
datasource: {
|
||||
type: 'testdb',
|
||||
|
@ -10,6 +10,7 @@ import { LibraryElementKind } from '../../../library-panels/types';
|
||||
import { isConstant, isQuery } from '../../../variables/guard';
|
||||
import { VariableOption, VariableRefresh } from '../../../variables/types';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { GridPos } from '../../state/PanelModel';
|
||||
|
||||
interface Input {
|
||||
name: string;
|
||||
@ -28,6 +29,26 @@ interface Requires {
|
||||
};
|
||||
}
|
||||
|
||||
interface ExternalDashboard {
|
||||
__inputs: Input[];
|
||||
__elements: Record<string, LibraryElementExport>;
|
||||
__requires: Array<Requires[string]>;
|
||||
panels: Array<PanelModel | PanelWithExportableLibraryPanel>;
|
||||
}
|
||||
|
||||
interface PanelWithExportableLibraryPanel {
|
||||
gridPos: GridPos;
|
||||
id: number;
|
||||
libraryPanel: {
|
||||
name: string;
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
function isExportableLibraryPanel(p: any): p is PanelWithExportableLibraryPanel {
|
||||
return p.libraryPanel && typeof p.libraryPanel.name === 'string' && typeof p.libraryPanel.uid === 'string';
|
||||
}
|
||||
|
||||
interface DataSources {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
@ -79,11 +100,11 @@ export class DashboardExporter {
|
||||
let datasource: string = obj.datasource;
|
||||
let datasourceVariable: any = null;
|
||||
|
||||
const datasourceUid: string = (datasource as any)?.uid;
|
||||
// ignore data source properties that contain a variable
|
||||
if (datasource && (datasource as any).uid) {
|
||||
const uid = (datasource as any).uid as string;
|
||||
if (uid.indexOf('$') === 0) {
|
||||
datasourceVariable = variableLookup[uid.substring(1)];
|
||||
if (datasourceUid) {
|
||||
if (datasourceUid.indexOf('$') === 0) {
|
||||
datasourceVariable = variableLookup[datasourceUid.substring(1)];
|
||||
if (datasourceVariable && datasourceVariable.current) {
|
||||
datasource = datasourceVariable.current.value;
|
||||
}
|
||||
@ -150,8 +171,9 @@ export class DashboardExporter {
|
||||
if (isPanelModelLibraryPanel(panel)) {
|
||||
const { libraryPanel, ...model } = panel;
|
||||
const { name, uid } = libraryPanel;
|
||||
const { gridPos, id, ...rest } = model;
|
||||
if (!libraryPanels.has(uid)) {
|
||||
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model });
|
||||
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model: rest });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -230,13 +252,36 @@ export class DashboardExporter {
|
||||
}
|
||||
}
|
||||
|
||||
// make inputs and requires a top thing
|
||||
const newObj: { [key: string]: {} } = {};
|
||||
newObj['__inputs'] = inputs;
|
||||
newObj['__elements'] = [...libraryPanels.values()];
|
||||
newObj['__requires'] = sortBy(requires, ['id']);
|
||||
const __elements = [...libraryPanels.entries()].reduce<Record<string, LibraryElementExport>>(
|
||||
(prev, [curKey, curLibPanel]) => {
|
||||
prev[curKey] = curLibPanel;
|
||||
return prev;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// make inputs and requires a top thing
|
||||
const newObj: ExternalDashboard = defaults(
|
||||
{
|
||||
__inputs: inputs,
|
||||
__elements,
|
||||
__requires: sortBy(requires, ['id']),
|
||||
},
|
||||
saveModel
|
||||
);
|
||||
|
||||
// Remove extraneous props from library panels
|
||||
for (let i = 0; i < newObj.panels.length; i++) {
|
||||
const libPanel = newObj.panels[i];
|
||||
if (isExportableLibraryPanel(libPanel)) {
|
||||
newObj.panels[i] = {
|
||||
gridPos: libPanel.gridPos,
|
||||
id: libPanel.id,
|
||||
libraryPanel: { uid: libPanel.libraryPanel.uid, name: libPanel.libraryPanel.name },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
defaults(newObj, saveModel);
|
||||
return newObj;
|
||||
} catch (err) {
|
||||
console.error('Export failed:', err);
|
||||
|
@ -64,7 +64,7 @@ export const ImportDashboardForm: FC<Props> = ({
|
||||
}
|
||||
}, [errors, getValues, isSubmitted, onSubmit]);
|
||||
const newLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.New) ?? [];
|
||||
const existingLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.Exits) ?? [];
|
||||
const existingLibraryPanels = inputs?.libraryPanels?.filter((i) => i.state === LibraryPanelInputState.Exists) ?? [];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -77,7 +77,7 @@ function processInputs(dashboardJson: any): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }): ThunkResult<void> {
|
||||
function processElements(dashboardJson?: { __elements?: Record<string, LibraryElementExport> }): ThunkResult<void> {
|
||||
return async function (dispatch) {
|
||||
if (!dashboardJson || !dashboardJson.__elements) {
|
||||
return;
|
||||
@ -85,7 +85,7 @@ function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }
|
||||
|
||||
const libraryPanelInputs: LibraryPanelInput[] = [];
|
||||
|
||||
for (const element of dashboardJson.__elements) {
|
||||
for (const element of Object.values(dashboardJson.__elements)) {
|
||||
if (element.kind !== LibraryElementKind.Panel) {
|
||||
continue;
|
||||
}
|
||||
@ -110,7 +110,7 @@ function processElements(dashboardJson?: { __elements?: LibraryElementExport[] }
|
||||
|
||||
try {
|
||||
const panelInDb = await getLibraryPanel(uid, true);
|
||||
input.state = LibraryPanelInputState.Exits;
|
||||
input.state = LibraryPanelInputState.Exists;
|
||||
input.model = panelInDb;
|
||||
} catch (e: any) {
|
||||
if (e.status !== 404) {
|
||||
|
@ -115,7 +115,7 @@ describe('importDashboardReducer', () => {
|
||||
setLibraryPanelInputs([
|
||||
{
|
||||
model: { uid: 'sadjahsdk', name: 'A name', type: 'text' } as LibraryElementDTO,
|
||||
state: LibraryPanelInputState.Exits,
|
||||
state: LibraryPanelInputState.Exists,
|
||||
},
|
||||
])
|
||||
)
|
||||
@ -127,7 +127,7 @@ describe('importDashboardReducer', () => {
|
||||
libraryPanels: [
|
||||
{
|
||||
model: { uid: 'sadjahsdk', name: 'A name', type: 'text' } as LibraryElementDTO,
|
||||
state: LibraryPanelInputState.Exits,
|
||||
state: LibraryPanelInputState.Exists,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ export enum InputType {
|
||||
|
||||
export enum LibraryPanelInputState {
|
||||
New = 'new',
|
||||
Exits = 'exists',
|
||||
Exists = 'exists',
|
||||
Different = 'different',
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user