diff --git a/pkg/services/dashboardimport/service/service.go b/pkg/services/dashboardimport/service/service.go index a901e8f3071..c16801e55fa 100644 --- a/pkg/services/dashboardimport/service/service.go +++ b/pkg/services/dashboardimport/service/service.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboardimport/api" "github.com/grafana/grafana/pkg/services/dashboardimport/utils" "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/librarypanels" "github.com/grafana/grafana/pkg/services/plugindashboards" @@ -23,13 +24,14 @@ func ProvideService(routeRegister routing.RouteRegister, quotaService quota.Service, pluginDashboardService plugindashboards.Service, pluginStore pluginstore.Store, libraryPanelService librarypanels.Service, dashboardService dashboards.DashboardService, - ac accesscontrol.AccessControl, folderService folder.Service, + ac accesscontrol.AccessControl, folderService folder.Service, features featuremgmt.FeatureToggles, ) *ImportDashboardService { s := &ImportDashboardService{ pluginDashboardService: pluginDashboardService, dashboardService: dashboardService, libraryPanelService: libraryPanelService, folderService: folderService, + features: features, } dashboardImportAPI := api.New(s, quotaService, pluginStore, ac) @@ -43,6 +45,7 @@ type ImportDashboardService struct { dashboardService dashboards.DashboardService libraryPanelService librarypanels.Service folderService folder.Service + features featuremgmt.FeatureToggles } 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, } - savedDashboard, err := s.dashboardService.ImportDashboard(ctx, dto) - if err != nil { - return nil, err - } - - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc() // nolint:staticcheck err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId, req.FolderUid) if err != nil { return nil, err } - err = s.libraryPanelService.ConnectLibraryPanelsForDashboard(ctx, req.User, savedDashboard) + savedDashboard, err := s.dashboardService.ImportDashboard(ctx, dto) if err != nil { 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) metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc() diff --git a/pkg/services/dashboardimport/service/service_test.go b/pkg/services/dashboardimport/service/service_test.go index 365930fd618..a4a1af485a2 100644 --- a/pkg/services/dashboardimport/service/service_test.go +++ b/pkg/services/dashboardimport/service/service_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/dashboardimport" "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/foldertest" "github.com/grafana/grafana/pkg/services/librarypanels" @@ -67,6 +68,7 @@ func TestImportDashboardService(t *testing.T) { dashboardService: dashboardService, libraryPanelService: libraryPanelService, folderService: folderService, + features: featuremgmt.WithFeatures(), } req := &dashboardimport.ImportDashboardRequest{ @@ -127,6 +129,7 @@ func TestImportDashboardService(t *testing.T) { dashboardService: dashboardService, libraryPanelService: libraryPanelService, folderService: folderService, + features: featuremgmt.WithFeatures(), } loadResp, err := loadTestDashboard(context.Background(), &plugindashboards.LoadPluginDashboardRequest{ diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index 6901f967044..37a1f195044 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -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) + }) + }) +}