PanelLibrary: adding library panels to Dashboard Api (#30278)

* Wip: First naive impl

* Chore: fix after merge

* Chore: changes after PR comments

* Chore: removes unused types

* Chore: adds feature toggle

* Refactor: adds library panels cleanup and connect when storing dashboards

* Refactor: adds feature toggle

* Update pkg/services/librarypanels/librarypanels.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/librarypanels/librarypanels.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Refactor: adds disconnect library panels when deleting a dashboard

* Chore: changes after PR comments

* Tests: adds tests for LoadLibraryPanelsForDashboard

* Tests: adds tests for CleanLibraryPanelsForDashboard

* Tests: adds tests for ConnectLibraryPanelsForDashboard

* Tests: adds tests for DisconnectLibraryPanelsForDashboard and small refactor

* Update pkg/services/librarypanels/librarypanels_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/librarypanels/librarypanels_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/librarypanels/librarypanels_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/librarypanels/librarypanels_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Refactor: deletes all connections in one call and connects all in the same transaction

* Chore: adds better comments

* Chore: changes after PR comments

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Hugo Häggmark
2021-01-20 09:28:10 +01:00
committed by GitHub
co-authored by Arve Knudsen
parent 36dc70e168
commit b7b6632a4d
10 changed files with 1093 additions and 73 deletions
+80 -15
View File
@@ -5,6 +5,8 @@ import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/models"
@@ -40,27 +42,46 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
return libraryPanel, err
}
func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, user *models.SignedInUser, uid string, dashboardID int64) error {
panel, err := getLibraryPanel(session, uid, user.OrgId)
if err != nil {
return err
}
// TODO add check that dashboard exists
libraryPanelDashboard := libraryPanelDashboard{
DashboardID: dashboardID,
LibraryPanelID: panel.ID,
Created: time.Now(),
CreatedBy: user.UserId,
}
if _, err := session.Insert(&libraryPanelDashboard); err != nil {
if dialect.IsUniqueConstraintViolation(err) {
return nil
}
return err
}
return nil
}
// connectDashboard adds a connection between a Library Panel and a Dashboard.
func (lps *LibraryPanelService) connectDashboard(c *models.ReqContext, uid string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
panel, err := getLibraryPanel(session, uid, c.SignedInUser.OrgId)
if err != nil {
return err
}
return connectDashboard(session, lps.SQLStore.Dialect, c.SignedInUser, uid, dashboardID)
})
// TODO add check that dashboard exists
return err
}
libraryPanelDashboard := libraryPanelDashboard{
DashboardID: dashboardID,
LibraryPanelID: panel.ID,
Created: time.Now(),
CreatedBy: c.SignedInUser.UserId,
}
if _, err := session.Insert(&libraryPanelDashboard); err != nil {
if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return nil
// connectLibraryPanelsForDashboard adds connections for all Library Panels in a Dashboard.
func (lps *LibraryPanelService) connectLibraryPanelsForDashboard(c *models.ReqContext, uids []string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
for _, uid := range uids {
err := connectDashboard(session, lps.SQLStore.Dialect, c.SignedInUser, uid, dashboardID)
if err != nil {
return err
}
return err
}
return nil
})
@@ -110,6 +131,23 @@ func (lps *LibraryPanelService) disconnectDashboard(c *models.ReqContext, uid st
})
}
// disconnectLibraryPanelsForDashboard deletes connections for all Library Panels in a Dashboard.
func (lps *LibraryPanelService) disconnectLibraryPanelsForDashboard(dashboardID int64, panelCount int64) error {
return lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
result, err := session.Exec("DELETE FROM library_panel_dashboard WHERE dashboard_id=?", dashboardID)
if err != nil {
return err
}
if rowsAffected, err := result.RowsAffected(); err != nil {
return err
} else if rowsAffected != panelCount {
lps.log.Warn("Number of disconnects does not match number of panels", "dashboard", dashboardID, "rowsAffected", rowsAffected, "panelCount", panelCount)
}
return nil
})
}
func getLibraryPanel(session *sqlstore.DBSession, uid string, orgID int64) (LibraryPanel, error) {
libraryPanels := make([]LibraryPanel, 0)
session.Table("library_panel")
@@ -183,6 +221,33 @@ func (lps *LibraryPanelService) getConnectedDashboards(c *models.ReqContext, uid
return connectedDashboardIDs, err
}
func (lps *LibraryPanelService) getLibraryPanelsForDashboardID(dashboardID int64) (map[string]LibraryPanel, error) {
libraryPanelMap := make(map[string]LibraryPanel)
err := lps.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
sql := `SELECT
lp.id, lp.org_id, lp.folder_id, lp.uid, lp.name, lp.model, lp.created, lp.created_by, lp.updated, updated_by
FROM
library_panel_dashboard AS lpd
INNER JOIN
library_panel AS lp ON lpd.librarypanel_id = lp.id AND lpd.dashboard_id=?`
var libraryPanels []LibraryPanel
sess := session.SQL(sql, dashboardID)
err := sess.Find(&libraryPanels)
if err != nil {
return err
}
for _, panel := range libraryPanels {
libraryPanelMap[panel.UID] = panel
}
return nil
})
return libraryPanelMap, err
}
// patchLibraryPanel updates a Library Panel.
func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patchLibraryPanelCommand, uid string) (LibraryPanel, error) {
var libraryPanel LibraryPanel
+155
View File
@@ -1,8 +1,12 @@
package librarypanels
import (
"fmt"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@@ -39,6 +43,157 @@ func (lps *LibraryPanelService) IsEnabled() bool {
return lps.Cfg.IsPanelLibraryEnabled()
}
// LoadLibraryPanelsForDashboard loops through all panels in dashboard JSON and replaces any library panel JSON
// with JSON stored for library panel in db.
func (lps *LibraryPanelService) LoadLibraryPanelsForDashboard(dash *models.Dashboard) error {
if !lps.IsEnabled() {
return nil
}
libraryPanels, err := lps.getLibraryPanelsForDashboardID(dash.Id)
if err != nil {
return err
}
panels := dash.Data.Get("panels").MustArray()
for i, panel := range panels {
panelAsJSON := simplejson.NewFromAny(panel)
libraryPanel := panelAsJSON.Get("libraryPanel")
if libraryPanel.Interface() == nil {
continue
}
// we have a library panel
uid := libraryPanel.Get("uid").MustString()
if len(uid) == 0 {
return errLibraryPanelHeaderUIDMissing
}
libraryPanelInDB, ok := libraryPanels[uid]
if !ok {
return fmt.Errorf("found connection to library panel %q that isn't in database", uid)
}
// we have a match between what is stored in db and in dashboard json
libraryPanelModel, err := libraryPanelInDB.Model.MarshalJSON()
if err != nil {
return fmt.Errorf("could not marshal library panel JSON: %w", err)
}
libraryPanelModelAsJSON, err := simplejson.NewJson(libraryPanelModel)
if err != nil {
return fmt.Errorf("could not convert library panel to simplejson model: %w", err)
}
// set the library panel json as the new panel json in dashboard json
dash.Data.Get("panels").SetIndex(i, libraryPanelModelAsJSON.Interface())
// set dashboard specific props
elem := dash.Data.Get("panels").GetIndex(i)
elem.Set("gridPos", panelAsJSON.Get("gridPos").MustMap())
elem.Set("id", panelAsJSON.Get("id").MustInt64())
elem.Set("libraryPanel", map[string]interface{}{
"uid": libraryPanelInDB.UID,
"name": libraryPanelInDB.Name,
})
}
return nil
}
// CleanLibraryPanelsForDashboard loops through all panels in dashboard JSON and cleans up any library panel JSON so that
// only the necessary JSON properties remain when storing the dashboard JSON.
func (lps *LibraryPanelService) CleanLibraryPanelsForDashboard(dash *models.Dashboard) error {
if !lps.IsEnabled() {
return nil
}
panels := dash.Data.Get("panels").MustArray()
for i, panel := range panels {
panelAsJSON := simplejson.NewFromAny(panel)
libraryPanel := panelAsJSON.Get("libraryPanel")
if libraryPanel.Interface() == nil {
continue
}
// we have a library panel
uid := libraryPanel.Get("uid").MustString()
if len(uid) == 0 {
return errLibraryPanelHeaderUIDMissing
}
name := libraryPanel.Get("name").MustString()
if len(name) == 0 {
return errLibraryPanelHeaderNameMissing
}
// keep only the necessary JSON properties, the rest of the properties should be safely stored in library_panels table
gridPos := panelAsJSON.Get("gridPos").MustMap()
id := panelAsJSON.Get("id").MustInt64(int64(i))
dash.Data.Get("panels").SetIndex(i, map[string]interface{}{
"id": id,
"gridPos": gridPos,
"libraryPanel": map[string]interface{}{
"uid": uid,
"name": name,
},
})
}
return nil
}
// ConnectLibraryPanelsForDashboard loops through all panels in dashboard JSON and connects any library panels to the dashboard.
func (lps *LibraryPanelService) ConnectLibraryPanelsForDashboard(c *models.ReqContext, dash *models.Dashboard) error {
if !lps.IsEnabled() {
return nil
}
panels := dash.Data.Get("panels").MustArray()
var libraryPanels []string
for _, panel := range panels {
panelAsJSON := simplejson.NewFromAny(panel)
libraryPanel := panelAsJSON.Get("libraryPanel")
if libraryPanel.Interface() == nil {
continue
}
// we have a library panel
uid := libraryPanel.Get("uid").MustString()
if len(uid) == 0 {
return errLibraryPanelHeaderUIDMissing
}
libraryPanels = append(libraryPanels, uid)
}
return lps.connectLibraryPanelsForDashboard(c, libraryPanels, dash.Id)
}
// DisconnectLibraryPanelsForDashboard loops through all panels in dashboard JSON and disconnects any library panels from the dashboard.
func (lps *LibraryPanelService) DisconnectLibraryPanelsForDashboard(dash *models.Dashboard) error {
if !lps.IsEnabled() {
return nil
}
panels := dash.Data.Get("panels").MustArray()
panelCount := int64(0)
for _, panel := range panels {
panelAsJSON := simplejson.NewFromAny(panel)
libraryPanel := panelAsJSON.Get("libraryPanel")
if libraryPanel.Interface() == nil {
continue
}
// we have a library panel
uid := libraryPanel.Get("uid").MustString()
if len(uid) == 0 {
return errLibraryPanelHeaderUIDMissing
}
panelCount++
}
return lps.disconnectLibraryPanelsForDashboard(dash.Id, panelCount)
}
// AddMigration defines database migrations.
// If Panel Library is not enabled does nothing.
func (lps *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
@@ -2,9 +2,12 @@ package librarypanels
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"gopkg.in/macaron.v1"
@@ -30,7 +33,7 @@ func TestCreateLibraryPanel(t *testing.T) {
func TestConnectLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to create a connection for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", "dashboardId": "1"})
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
response := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
@@ -45,7 +48,7 @@ func TestConnectLibraryPanel(t *testing.T) {
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
@@ -97,7 +100,7 @@ func TestDeleteLibraryPanel(t *testing.T) {
func TestDisconnectLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to remove a connection with a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", "dashboardId": "1"})
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
response := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
@@ -112,7 +115,7 @@ func TestDisconnectLibraryPanel(t *testing.T) {
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "1"})
response = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
@@ -127,7 +130,7 @@ func TestDisconnectLibraryPanel(t *testing.T) {
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
response = sc.service.disconnectHandler(sc.reqContext)
@@ -330,7 +333,7 @@ func TestPatchLibraryPanel(t *testing.T) {
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"name": "Model - New name",
"title": "Model - New name",
"type": "text"
}
`),
@@ -344,7 +347,7 @@ func TestPatchLibraryPanel(t *testing.T) {
require.NoError(t, err)
existing.Result.FolderID = int64(2)
existing.Result.Name = "Panel - New name"
existing.Result.Model["name"] = "Model - New name"
existing.Result.Model["title"] = "Model - New name"
if diff := cmp.Diff(existing.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
@@ -516,6 +519,578 @@ func TestPatchLibraryPanel(t *testing.T) {
})
}
func TestLoadLibraryPanelsForDashboard(t *testing.T) {
testScenario(t, "When an admin tries to load a dashboard with a library panel, it should copy JSON properties from library panel",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.LoadLibraryPanelsForDashboard(&dash)
require.NoError(t, err)
expectedJSON := 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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
expected := simplejson.NewFromAny(expectedJSON)
if diff := cmp.Diff(expected.Interface(), dash.Data.Interface(), getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
testScenario(t, "When an admin tries to load a dashboard with a library panel without uid, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]interface{}{
"name": existing.Result.Name,
},
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.LoadLibraryPanelsForDashboard(&dash)
require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error())
})
testScenario(t, "When an admin tries to load a dashboard with a library panel that is not connected, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.LoadLibraryPanelsForDashboard(&dash)
require.EqualError(t, err, fmt.Errorf("found connection to library panel %q that isn't in database", existing.Result.UID).Error())
})
}
func TestCleanLibraryPanelsForDashboard(t *testing.T) {
testScenario(t, "When an admin tries to store a dashboard with a library panel, it should just keep the correct JSON properties in library panel",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.CleanLibraryPanelsForDashboard(&dash)
require.NoError(t, err)
expectedJSON := 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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
},
},
}
expected := simplejson.NewFromAny(expectedJSON)
if diff := cmp.Diff(expected.Interface(), dash.Data.Interface(), getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
testScenario(t, "When an admin tries to store a dashboard with a library panel without uid, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.CleanLibraryPanelsForDashboard(&dash)
require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error())
})
testScenario(t, "When an admin tries to store a dashboard with a library panel without name, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: 1,
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.CleanLibraryPanelsForDashboard(&dash)
require.EqualError(t, err, errLibraryPanelHeaderNameMissing.Error())
})
}
func TestConnectLibraryPanelsForDashboard(t *testing.T) {
testScenario(t, "When an admin tries to store a dashboard with a library panel, it should connect the two",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: int64(1),
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.ConnectLibraryPanelsForDashboard(sc.reqContext, &dash)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID})
response = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
var dashResult libraryPanelDashboardsResult
err = json.Unmarshal(response.Body(), &dashResult)
require.NoError(t, err)
require.Len(t, dashResult.Result, 1)
require.Equal(t, int64(1), dashResult.Result[0])
})
testScenario(t, "When an admin tries to store a dashboard with a library panel without uid, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: int64(1),
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.ConnectLibraryPanelsForDashboard(sc.reqContext, &dash)
require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error())
})
}
func TestDisconnectLibraryPanelsForDashboard(t *testing.T) {
testScenario(t, "When an admin tries to delete a dashboard with a library panel, it should disconnect the two",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"uid": existing.Result.UID,
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: int64(1),
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.DisconnectLibraryPanelsForDashboard(&dash)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID})
response = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
var dashResult libraryPanelDashboardsResult
err = json.Unmarshal(response.Body(), &dashResult)
require.NoError(t, err)
require.Empty(t, dashResult.Result)
})
testScenario(t, "When an admin tries to delete a dashboard with a library panel without uid, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel1")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var existing libraryPanelResult
err := json.Unmarshal(response.Body(), &existing)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": existing.Result.UID, ":dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
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{}{
"id": int64(2),
"gridPos": map[string]interface{}{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"datasource": "${DS_GDEV-TESTDATA}",
"libraryPanel": map[string]interface{}{
"name": existing.Result.Name,
},
"title": "Text - Library Panel",
"type": "text",
},
},
}
dash := models.Dashboard{
Id: int64(1),
Data: simplejson.NewFromAny(dashJSON),
}
err = sc.service.DisconnectLibraryPanelsForDashboard(&dash)
require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error())
})
}
type libraryPanel struct {
ID int64 `json:"id"`
OrgID int64 `json:"orgId"`
@@ -570,7 +1145,7 @@ func getCreateCommand(folderID int64, name string) createLibraryPanelCommand {
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"name": "Text - Library Panel",
"title": "Text - Library Panel",
"type": "text"
}
`),
+4
View File
@@ -40,6 +40,10 @@ var (
errLibraryPanelNotFound = errors.New("library panel could not be found")
// errLibraryPanelDashboardNotFound is an error for when a library panel connection can't be found.
errLibraryPanelDashboardNotFound = errors.New("library panel connection could not be found")
// errLibraryPanelHeaderUIDMissing is an error for when a library panel header is missing the uid property.
errLibraryPanelHeaderUIDMissing = errors.New("library panel header is missing required property uid")
// errLibraryPanelHeaderNameMissing is an error for when a library panel header is missing the name property.
errLibraryPanelHeaderNameMissing = errors.New("library panel header is missing required property name")
)
// Commands