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
+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) {