grafana/pkg/services/librarypanels/librarypanels.go

233 lines
7.8 KiB
Go

package librarypanels
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/setting"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister,
libraryElementService libraryelements.Service, folderService folder.Service) (*LibraryPanelService, error) {
lps := LibraryPanelService{
Cfg: cfg,
SQLStore: sqlStore,
RouteRegister: routeRegister,
LibraryElementService: libraryElementService,
FolderService: folderService,
log: log.New("library-panels"),
}
if err := folderService.RegisterService(lps); err != nil {
return nil, err
}
return &lps, nil
}
// Service is a service for operating on library panels.
type Service interface {
ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error
ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error
}
type LibraryInfo struct {
Panels []*any
LibraryPanels *simplejson.Json
}
// LibraryPanelService is the service for the Panel Library feature.
type LibraryPanelService struct {
Cfg *setting.Cfg
SQLStore db.DB
RouteRegister routing.RouteRegister
LibraryElementService libraryelements.Service
FolderService folder.Service
log log.Logger
}
var _ Service = (*LibraryPanelService)(nil)
// ConnectLibraryPanelsForDashboard loops through all panels in dashboard JSON and connects any library panels to the dashboard.
func (lps *LibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error {
panels := dash.Data.Get("panels").MustArray()
libraryPanels := make(map[string]string)
err := connectLibraryPanelsRecursively(c, panels, libraryPanels)
if err != nil {
return err
}
elementUIDs := make([]string, 0, len(libraryPanels))
for libraryPanel := range libraryPanels {
elementUIDs = append(elementUIDs, libraryPanel)
}
return lps.LibraryElementService.ConnectElementsToDashboard(c, signedInUser, elementUIDs, dash.ID)
}
func isLibraryPanelOrRow(panel *simplejson.Json, panelType string) bool {
return panel.Interface() != nil || panelType == "row"
}
func connectLibraryPanelsRecursively(c context.Context, panels []any, libraryPanels map[string]string) error {
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" {
rowPanels := panelAsJSON.Get("panels").MustArray()
err := connectLibraryPanelsRecursively(c, rowPanels, libraryPanels)
if err != nil {
return err
}
continue
}
// we have a library panel
UID := libraryPanel.Get("uid").MustString()
if len(UID) == 0 {
return errLibraryPanelHeaderUIDMissing
}
_, exists := libraryPanels[UID]
if !exists {
libraryPanels[UID] = UID
}
}
return nil
}
// ImportLibraryPanelsForDashboard loops through all panels in dashboard JSON and creates any missing library panels in the database.
func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error {
return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, libraryPanels, panels, folderID, folderUID)
}
func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error {
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" {
err := importLibraryPanelsRecursively(c, service, signedInUser, libraryPanels, panelAsJSON.Get("panels").MustArray(), folderID, folderUID)
if err != nil {
return err
}
continue
}
// we have a library panel
UID := libraryPanel.Get("uid").MustString()
if len(UID) == 0 {
return errLibraryPanelHeaderUIDMissing
}
_, err := service.GetElement(c, signedInUser, model.GetLibraryElementCommand{UID: UID, FolderName: dashboards.RootFolderName})
if err == nil {
continue
}
if errors.Is(err, model.ErrLibraryElementNotFound) {
name := libraryPanel.Get("name").MustString()
if len(name) == 0 {
return errLibraryPanelHeaderNameMissing
}
elementModel := libraryPanels.Get(UID).Get("model")
elementModel.Set("libraryPanel", map[string]any{
"uid": UID,
})
Model, err := json.Marshal(&elementModel)
if err != nil {
return err
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryPanels).Inc()
var cmd = model.CreateLibraryElementCommand{
FolderID: folderID, // nolint:staticcheck
FolderUID: &folderUID,
Name: name,
Model: Model,
Kind: int64(model.PanelElement),
UID: UID,
}
_, err = service.CreateElement(c, signedInUser, cmd)
if err != nil {
return err
}
continue
}
return err
}
return nil
}
// CountInFolder is a handler for retrieving the number of library panels contained
// within a given folder and for a specific organisation.
func (lps LibraryPanelService) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
if len(folderUIDs) == 0 {
return 0, nil
}
var count int64
return count, lps.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryPanels).Inc()
// the sequential IDs for the respective entries of dashboard and folder tables are different,
// so we need to get the folder ID from the dashboard table
// TODO: In the future, we should consider adding a folder UID column to the library_element table
// and use that instead of the folder ID.
s := fmt.Sprintf(`SELECT COUNT(*) FROM library_element
WHERE org_id = ? AND folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN (%s)) AND kind = ?`, strings.Repeat("?,", len(folderUIDs)-1)+"?")
args := make([]interface{}, 0, len(folderUIDs)+2)
args = append(args, orgID, orgID)
for _, folderUID := range folderUIDs {
args = append(args, folderUID)
}
args = append(args, int64(model.PanelElement))
_, err := sess.SQL(s, args...).Get(&count)
if err != nil {
return err
}
return err
})
}
// DeleteInFolder deletes the library panels contained in a given folder.
func (lps LibraryPanelService) DeleteInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) error {
for _, folderUID := range folderUIDs {
if err := lps.LibraryElementService.DeleteLibraryElementsInFolder(ctx, user, folderUID); err != nil {
return err
}
}
return nil
}
// Kind returns the name of the library panel type of entity.
func (lps LibraryPanelService) Kind() string { return entity.StandardKindLibraryPanel }