Dashboard Imports: Fix creating new library panels (#107219)
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/dashboardimport/api"
|
"github.com/grafana/grafana/pkg/services/dashboardimport/api"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboardimport/utils"
|
"github.com/grafana/grafana/pkg/services/dashboardimport/utils"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||||
@@ -23,13 +24,14 @@ func ProvideService(routeRegister routing.RouteRegister,
|
|||||||
quotaService quota.Service,
|
quotaService quota.Service,
|
||||||
pluginDashboardService plugindashboards.Service, pluginStore pluginstore.Store,
|
pluginDashboardService plugindashboards.Service, pluginStore pluginstore.Store,
|
||||||
libraryPanelService librarypanels.Service, dashboardService dashboards.DashboardService,
|
libraryPanelService librarypanels.Service, dashboardService dashboards.DashboardService,
|
||||||
ac accesscontrol.AccessControl, folderService folder.Service,
|
ac accesscontrol.AccessControl, folderService folder.Service, features featuremgmt.FeatureToggles,
|
||||||
) *ImportDashboardService {
|
) *ImportDashboardService {
|
||||||
s := &ImportDashboardService{
|
s := &ImportDashboardService{
|
||||||
pluginDashboardService: pluginDashboardService,
|
pluginDashboardService: pluginDashboardService,
|
||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
libraryPanelService: libraryPanelService,
|
libraryPanelService: libraryPanelService,
|
||||||
folderService: folderService,
|
folderService: folderService,
|
||||||
|
features: features,
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboardImportAPI := api.New(s, quotaService, pluginStore, ac)
|
dashboardImportAPI := api.New(s, quotaService, pluginStore, ac)
|
||||||
@@ -43,6 +45,7 @@ type ImportDashboardService struct {
|
|||||||
dashboardService dashboards.DashboardService
|
dashboardService dashboards.DashboardService
|
||||||
libraryPanelService librarypanels.Service
|
libraryPanelService librarypanels.Service
|
||||||
folderService folder.Service
|
folderService folder.Service
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
|
func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
|
||||||
@@ -131,22 +134,25 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
|
|||||||
User: req.User,
|
User: req.User,
|
||||||
}
|
}
|
||||||
|
|
||||||
savedDashboard, err := s.dashboardService.ImportDashboard(ctx, dto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
|
|
||||||
// nolint:staticcheck
|
// nolint:staticcheck
|
||||||
err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId, req.FolderUid)
|
err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId, req.FolderUid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.libraryPanelService.ConnectLibraryPanelsForDashboard(ctx, req.User, savedDashboard)
|
savedDashboard, err := s.dashboardService.ImportDashboard(ctx, dto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
|
||||||
|
|
||||||
|
// in the k8s flow, we connect the library panels in pkg/registry/apis/dashboard/legacy/sql_dashboards.go
|
||||||
|
if !s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||||
|
err = s.libraryPanelService.ConnectLibraryPanelsForDashboard(ctx, req.User, savedDashboard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
revision := savedDashboard.Data.Get("revision").MustInt64(0)
|
revision := savedDashboard.Data.Get("revision").MustInt64(0)
|
||||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
|
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||||
@@ -67,6 +68,7 @@ func TestImportDashboardService(t *testing.T) {
|
|||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
libraryPanelService: libraryPanelService,
|
libraryPanelService: libraryPanelService,
|
||||||
folderService: folderService,
|
folderService: folderService,
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &dashboardimport.ImportDashboardRequest{
|
req := &dashboardimport.ImportDashboardRequest{
|
||||||
@@ -127,6 +129,7 @@ func TestImportDashboardService(t *testing.T) {
|
|||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
libraryPanelService: libraryPanelService,
|
libraryPanelService: libraryPanelService,
|
||||||
folderService: folderService,
|
folderService: folderService,
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
loadResp, err := loadTestDashboard(context.Background(), &plugindashboards.LoadPluginDashboardRequest{
|
loadResp, err := loadTestDashboard(context.Background(), &plugindashboards.LoadPluginDashboardRequest{
|
||||||
|
|||||||
@@ -551,3 +551,214 @@ func testPreserveSchemaVersion(t *testing.T, featureToggles []string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testImportDashboardWithLibraryPanels(t, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntegrationImportDashboardWithLibraryPanelsK8s(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testImportDashboardWithLibraryPanels(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testImportDashboardWithLibraryPanels(t *testing.T, featureToggles []string) {
|
||||||
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
|
DisableAnonymous: true,
|
||||||
|
EnableFeatureToggles: featureToggles,
|
||||||
|
})
|
||||||
|
|
||||||
|
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
|
|
||||||
|
t.Run("import dashboard with library panels should create library panels and connections", func(t *testing.T) {
|
||||||
|
dashboardJSON := `{
|
||||||
|
"title": "Test Dashboard with Library Panels",
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Library Panel 1",
|
||||||
|
"type": "text",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||||
|
"libraryPanel": {
|
||||||
|
"uid": "test-lib-panel-1",
|
||||||
|
"name": "Test Library Panel 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Library Panel 2",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
|
||||||
|
"libraryPanel": {
|
||||||
|
"uid": "test-lib-panel-2",
|
||||||
|
"name": "Test Library Panel 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__elements": {
|
||||||
|
"test-lib-panel-1": {
|
||||||
|
"uid": "test-lib-panel-1",
|
||||||
|
"name": "Test Library Panel 1",
|
||||||
|
"kind": 1,
|
||||||
|
"type": "text",
|
||||||
|
"model": {
|
||||||
|
"title": "Test Library Panel 1",
|
||||||
|
"type": "text",
|
||||||
|
"options": {
|
||||||
|
"content": "This is a test library panel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-lib-panel-2": {
|
||||||
|
"uid": "test-lib-panel-2",
|
||||||
|
"name": "Test Library Panel 2",
|
||||||
|
"kind": 1,
|
||||||
|
"type": "stat",
|
||||||
|
"model": {
|
||||||
|
"title": "Test Library Panel 2",
|
||||||
|
"type": "stat",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "csv_metric_values",
|
||||||
|
"stringInput": "1,20,90,30,5,0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
data, err := simplejson.NewJson([]byte(dashboardJSON))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err = json.NewEncoder(buf).Encode(dashboardimport.ImportDashboardRequest{
|
||||||
|
Dashboard: data,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/import", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
resp, err := http.Post(u, "application/json", buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var importResp struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &importResp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, importResp.UID)
|
||||||
|
|
||||||
|
t.Run("library panels should be created", func(t *testing.T) {
|
||||||
|
url := fmt.Sprintf("http://admin:admin@%s/api/library-elements/test-lib-panel-1", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
panel, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var panelRes struct {
|
||||||
|
Result struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(panel, &panelRes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-lib-panel-1", panelRes.Result.UID)
|
||||||
|
assert.Equal(t, "Test Library Panel 1", panelRes.Result.Name)
|
||||||
|
assert.Equal(t, "text", panelRes.Result.Type)
|
||||||
|
|
||||||
|
url = fmt.Sprintf("http://admin:admin@%s/api/library-elements/test-lib-panel-2", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
resp, err = http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
panel, err = io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = json.Unmarshal(panel, &panelRes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-lib-panel-2", panelRes.Result.UID)
|
||||||
|
assert.Equal(t, "Test Library Panel 2", panelRes.Result.Name)
|
||||||
|
assert.Equal(t, "stat", panelRes.Result.Type)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("library panels should be connected to dashboard", func(t *testing.T) {
|
||||||
|
url := fmt.Sprintf("http://admin:admin@%s/api/library-elements/test-lib-panel-1/connections", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
connectionsResp, err := http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, connectionsResp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := connectionsResp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
connections, err := io.ReadAll(connectionsResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var connectionsRes struct {
|
||||||
|
Result []struct {
|
||||||
|
ConnectionUID string `json:"connectionUid"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(connections, &connectionsRes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, connectionsRes.Result, 1)
|
||||||
|
assert.Equal(t, importResp.UID, connectionsRes.Result[0].ConnectionUID)
|
||||||
|
|
||||||
|
url = fmt.Sprintf("http://admin:admin@%s/api/library-elements/test-lib-panel-2/connections", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
connectionsResp, err = http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, connectionsResp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := connectionsResp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
connections, err = io.ReadAll(connectionsResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = json.Unmarshal(connections, &connectionsRes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, connectionsRes.Result, 1)
|
||||||
|
assert.Equal(t, importResp.UID, connectionsRes.Result[0].ConnectionUID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user