2020-12-03 08:18:10 -06:00
|
|
|
package librarypanels
|
|
|
|
|
|
|
|
import (
|
2021-09-27 02:04:36 -05:00
|
|
|
"context"
|
2021-09-20 03:58:24 -05:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-01-20 02:28:10 -06:00
|
|
|
"fmt"
|
|
|
|
|
2020-12-09 06:10:18 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
2021-01-20 02:28:10 -06:00
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
2020-12-03 08:18:10 -06:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2021-01-20 02:28:10 -06:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2021-05-11 00:10:19 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/libraryelements"
|
2020-12-09 06:10:18 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
2020-12-03 08:18:10 -06:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
)
|
|
|
|
|
2021-08-25 08:11:22 -05:00
|
|
|
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister,
|
|
|
|
libraryElementService libraryelements.Service) *LibraryPanelService {
|
|
|
|
return &LibraryPanelService{
|
|
|
|
Cfg: cfg,
|
|
|
|
SQLStore: sqlStore,
|
|
|
|
RouteRegister: routeRegister,
|
|
|
|
LibraryElementService: libraryElementService,
|
|
|
|
log: log.New("library-panels"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-12 01:48:17 -05:00
|
|
|
// Service is a service for operating on library panels.
|
|
|
|
type Service interface {
|
2021-09-27 02:04:36 -05:00
|
|
|
LoadLibraryPanelsForDashboard(c context.Context, dash *models.Dashboard) error
|
2021-05-12 01:48:17 -05:00
|
|
|
CleanLibraryPanelsForDashboard(dash *models.Dashboard) error
|
2021-09-27 02:04:36 -05:00
|
|
|
ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error
|
|
|
|
ImportLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard, folderID int64) error
|
2021-05-12 01:48:17 -05:00
|
|
|
}
|
|
|
|
|
2020-12-03 08:18:10 -06:00
|
|
|
// LibraryPanelService is the service for the Panel Library feature.
|
|
|
|
type LibraryPanelService struct {
|
2021-08-25 08:11:22 -05:00
|
|
|
Cfg *setting.Cfg
|
|
|
|
SQLStore *sqlstore.SQLStore
|
|
|
|
RouteRegister routing.RouteRegister
|
|
|
|
LibraryElementService libraryelements.Service
|
2021-05-11 00:10:19 -05:00
|
|
|
log log.Logger
|
2020-12-03 08:18:10 -06:00
|
|
|
}
|
|
|
|
|
2021-01-20 02:28:10 -06:00
|
|
|
// LoadLibraryPanelsForDashboard loops through all panels in dashboard JSON and replaces any library panel JSON
|
|
|
|
// with JSON stored for library panel in db.
|
2021-09-27 02:04:36 -05:00
|
|
|
func (lps *LibraryPanelService) LoadLibraryPanelsForDashboard(c context.Context, dash *models.Dashboard) error {
|
2021-05-11 00:10:19 -05:00
|
|
|
elements, err := lps.LibraryElementService.GetElementsForDashboard(c, dash.Id)
|
2021-01-20 02:28:10 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-09-08 03:53:55 -05:00
|
|
|
return loadLibraryPanelsRecursively(elements, dash.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadLibraryPanelsRecursively(elements map[string]libraryelements.LibraryElementDTO, parent *simplejson.Json) error {
|
|
|
|
panels := parent.Get("panels").MustArray()
|
2021-01-20 02:28:10 -06:00
|
|
|
for i, panel := range panels {
|
|
|
|
panelAsJSON := simplejson.NewFromAny(panel)
|
|
|
|
libraryPanel := panelAsJSON.Get("libraryPanel")
|
2021-09-08 03:53:55 -05:00
|
|
|
panelType := panelAsJSON.Get("type").MustString()
|
|
|
|
if !isLibraryPanelOrRow(libraryPanel, panelType) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a row
|
|
|
|
if panelType == "row" {
|
|
|
|
err := loadLibraryPanelsRecursively(elements, panelAsJSON)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-20 02:28:10 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a library panel
|
2021-09-20 03:58:24 -05:00
|
|
|
UID := libraryPanel.Get("uid").MustString()
|
|
|
|
if len(UID) == 0 {
|
2021-01-20 02:28:10 -06:00
|
|
|
return errLibraryPanelHeaderUIDMissing
|
|
|
|
}
|
|
|
|
|
2021-09-20 03:58:24 -05:00
|
|
|
elementInDB, ok := elements[UID]
|
2021-01-20 02:28:10 -06:00
|
|
|
if !ok {
|
2021-01-28 23:11:13 -06:00
|
|
|
name := libraryPanel.Get("name").MustString()
|
2021-09-08 03:53:55 -05:00
|
|
|
elem := parent.Get("panels").GetIndex(i)
|
2021-01-28 23:11:13 -06:00
|
|
|
elem.Set("gridPos", panelAsJSON.Get("gridPos").MustMap())
|
|
|
|
elem.Set("id", panelAsJSON.Get("id").MustInt64())
|
2021-09-20 03:58:24 -05:00
|
|
|
elem.Set("type", fmt.Sprintf("Name: \"%s\", UID: \"%s\"", name, UID))
|
2021-01-28 23:11:13 -06:00
|
|
|
elem.Set("libraryPanel", map[string]interface{}{
|
2021-09-20 03:58:24 -05:00
|
|
|
"uid": UID,
|
2021-01-28 23:11:13 -06:00
|
|
|
"name": name,
|
|
|
|
})
|
|
|
|
continue
|
2021-01-20 02:28:10 -06:00
|
|
|
}
|
|
|
|
|
2021-05-23 23:11:01 -05:00
|
|
|
if models.LibraryElementKind(elementInDB.Kind) != models.PanelElement {
|
2021-05-11 00:10:19 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-01-20 02:28:10 -06:00
|
|
|
// we have a match between what is stored in db and in dashboard json
|
2021-05-11 00:10:19 -05:00
|
|
|
libraryPanelModel, err := elementInDB.Model.MarshalJSON()
|
2021-01-20 02:28:10 -06:00
|
|
|
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
|
2021-09-08 03:53:55 -05:00
|
|
|
parent.Get("panels").SetIndex(i, libraryPanelModelAsJSON.Interface())
|
2021-01-20 02:28:10 -06:00
|
|
|
|
|
|
|
// set dashboard specific props
|
2021-09-08 03:53:55 -05:00
|
|
|
elem := parent.Get("panels").GetIndex(i)
|
2021-01-20 02:28:10 -06:00
|
|
|
elem.Set("gridPos", panelAsJSON.Get("gridPos").MustMap())
|
|
|
|
elem.Set("id", panelAsJSON.Get("id").MustInt64())
|
|
|
|
elem.Set("libraryPanel", map[string]interface{}{
|
2021-05-11 00:10:19 -05:00
|
|
|
"uid": elementInDB.UID,
|
|
|
|
"name": elementInDB.Name,
|
|
|
|
"type": elementInDB.Type,
|
|
|
|
"description": elementInDB.Description,
|
|
|
|
"version": elementInDB.Version,
|
2021-02-01 23:25:35 -06:00
|
|
|
"meta": map[string]interface{}{
|
2021-05-11 00:10:19 -05:00
|
|
|
"folderName": elementInDB.Meta.FolderName,
|
|
|
|
"folderUid": elementInDB.Meta.FolderUID,
|
2021-05-12 01:48:17 -05:00
|
|
|
"connectedDashboards": elementInDB.Meta.ConnectedDashboards,
|
2021-05-11 00:10:19 -05:00
|
|
|
"created": elementInDB.Meta.Created,
|
|
|
|
"updated": elementInDB.Meta.Updated,
|
2021-02-01 23:25:35 -06:00
|
|
|
"createdBy": map[string]interface{}{
|
2021-05-11 00:10:19 -05:00
|
|
|
"id": elementInDB.Meta.CreatedBy.ID,
|
|
|
|
"name": elementInDB.Meta.CreatedBy.Name,
|
|
|
|
"avatarUrl": elementInDB.Meta.CreatedBy.AvatarURL,
|
2021-02-01 23:25:35 -06:00
|
|
|
},
|
|
|
|
"updatedBy": map[string]interface{}{
|
2021-05-11 00:10:19 -05:00
|
|
|
"id": elementInDB.Meta.UpdatedBy.ID,
|
|
|
|
"name": elementInDB.Meta.UpdatedBy.Name,
|
|
|
|
"avatarUrl": elementInDB.Meta.UpdatedBy.AvatarURL,
|
2021-02-01 23:25:35 -06:00
|
|
|
},
|
|
|
|
},
|
2021-01-20 02:28:10 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2021-09-08 03:53:55 -05:00
|
|
|
return cleanLibraryPanelsRecursively(dash.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanLibraryPanelsRecursively(parent *simplejson.Json) error {
|
|
|
|
panels := parent.Get("panels").MustArray()
|
2021-01-20 02:28:10 -06:00
|
|
|
for i, panel := range panels {
|
|
|
|
panelAsJSON := simplejson.NewFromAny(panel)
|
|
|
|
libraryPanel := panelAsJSON.Get("libraryPanel")
|
2021-09-08 03:53:55 -05:00
|
|
|
panelType := panelAsJSON.Get("type").MustString()
|
|
|
|
if !isLibraryPanelOrRow(libraryPanel, panelType) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a row
|
|
|
|
if panelType == "row" {
|
|
|
|
err := cleanLibraryPanelsRecursively(panelAsJSON)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-20 02:28:10 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a library panel
|
2021-09-20 03:58:24 -05:00
|
|
|
UID := libraryPanel.Get("uid").MustString()
|
|
|
|
if len(UID) == 0 {
|
2021-01-20 02:28:10 -06:00
|
|
|
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()
|
2021-09-20 03:58:24 -05:00
|
|
|
ID := panelAsJSON.Get("id").MustInt64(int64(i))
|
2021-09-08 03:53:55 -05:00
|
|
|
parent.Get("panels").SetIndex(i, map[string]interface{}{
|
2021-09-20 03:58:24 -05:00
|
|
|
"id": ID,
|
2021-01-20 02:28:10 -06:00
|
|
|
"gridPos": gridPos,
|
|
|
|
"libraryPanel": map[string]interface{}{
|
2021-09-20 03:58:24 -05:00
|
|
|
"uid": UID,
|
2021-01-20 02:28:10 -06:00
|
|
|
"name": name,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnectLibraryPanelsForDashboard loops through all panels in dashboard JSON and connects any library panels to the dashboard.
|
2021-09-27 02:04:36 -05:00
|
|
|
func (lps *LibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *models.SignedInUser, dash *models.Dashboard) error {
|
2021-01-20 02:28:10 -06:00
|
|
|
panels := dash.Data.Get("panels").MustArray()
|
2021-05-20 02:40:23 -05:00
|
|
|
libraryPanels := make(map[string]string)
|
2021-09-08 03:53:55 -05:00
|
|
|
err := connectLibraryPanelsRecursively(c, panels, libraryPanels)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
elementUIDs := make([]string, 0, len(libraryPanels))
|
|
|
|
for libraryPanel := range libraryPanels {
|
|
|
|
elementUIDs = append(elementUIDs, libraryPanel)
|
|
|
|
}
|
|
|
|
|
2021-09-27 02:04:36 -05:00
|
|
|
return lps.LibraryElementService.ConnectElementsToDashboard(c, signedInUser, elementUIDs, dash.Id)
|
2021-09-08 03:53:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func isLibraryPanelOrRow(panel *simplejson.Json, panelType string) bool {
|
|
|
|
return panel.Interface() != nil || panelType == "row"
|
|
|
|
}
|
|
|
|
|
2021-09-27 02:04:36 -05:00
|
|
|
func connectLibraryPanelsRecursively(c context.Context, panels []interface{}, libraryPanels map[string]string) error {
|
2021-01-20 02:28:10 -06:00
|
|
|
for _, panel := range panels {
|
|
|
|
panelAsJSON := simplejson.NewFromAny(panel)
|
|
|
|
libraryPanel := panelAsJSON.Get("libraryPanel")
|
2021-09-08 03:53:55 -05:00
|
|
|
panelType := panelAsJSON.Get("type").MustString()
|
|
|
|
if !isLibraryPanelOrRow(libraryPanel, panelType) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a row
|
|
|
|
if panelType == "row" {
|
|
|
|
rowPanels := panelAsJSON.Get("panels").MustArray()
|
|
|
|
err := connectLibraryPanelsRecursively(c, rowPanels, libraryPanels)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-20 02:28:10 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a library panel
|
2021-09-20 03:58:24 -05:00
|
|
|
UID := libraryPanel.Get("uid").MustString()
|
|
|
|
if len(UID) == 0 {
|
2021-01-20 02:28:10 -06:00
|
|
|
return errLibraryPanelHeaderUIDMissing
|
|
|
|
}
|
2021-09-20 03:58:24 -05:00
|
|
|
_, exists := libraryPanels[UID]
|
2021-05-20 02:40:23 -05:00
|
|
|
if !exists {
|
2021-09-20 03:58:24 -05:00
|
|
|
libraryPanels[UID] = UID
|
2021-05-20 02:40:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 03:53:55 -05:00
|
|
|
return nil
|
2021-05-20 02:40:23 -05:00
|
|
|
}
|
2021-09-20 03:58:24 -05:00
|
|
|
|
|
|
|
// ImportLibraryPanelsForDashboard loops through all panels in dashboard JSON and creates any missing library panels in the database.
|
2021-09-27 02:04:36 -05:00
|
|
|
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)
|
2021-09-20 03:58:24 -05:00
|
|
|
}
|
|
|
|
|
2021-09-27 02:04:36 -05:00
|
|
|
func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser *models.SignedInUser, parent *simplejson.Json, folderID int64) error {
|
2021-09-20 03:58:24 -05:00
|
|
|
panels := parent.Get("panels").MustArray()
|
|
|
|
for _, panel := range panels {
|
|
|
|
panelAsJSON := simplejson.NewFromAny(panel)
|
|
|
|
libraryPanel := panelAsJSON.Get("libraryPanel")
|
|
|
|
panelType := panelAsJSON.Get("type").MustString()
|
|
|
|
if !isLibraryPanelOrRow(libraryPanel, panelType) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a row
|
|
|
|
if panelType == "row" {
|
2021-09-27 02:04:36 -05:00
|
|
|
err := importLibraryPanelsRecursively(c, service, signedInUser, panelAsJSON, folderID)
|
2021-09-20 03:58:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-09-27 02:04:36 -05:00
|
|
|
_, err := service.GetElement(c, signedInUser, UID)
|
2021-09-20 03:58:24 -05:00
|
|
|
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)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmd = libraryelements.CreateLibraryElementCommand{
|
|
|
|
FolderID: folderID,
|
|
|
|
Name: name,
|
|
|
|
Model: Model,
|
|
|
|
Kind: int64(models.PanelElement),
|
|
|
|
UID: UID,
|
|
|
|
}
|
2021-09-27 02:04:36 -05:00
|
|
|
_, err = service.CreateElement(c, signedInUser, cmd)
|
2021-09-20 03:58:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|