Commit 941ba1d2 by Hugo Häggmark Committed by GitHub

PanelLibrary: Adds api and db to create Library/Shared/Reusable Panel (#29642)

* PanelLibrary: Adds panellib table

* Refactor: removes drop table migration

* Refactor: fixes spelling mistake

* Refactor: changes after PR comments

* Refactor: some more renames

* PanelLibrary: Adds api and db to create Library/Shared/Reusable Panel

* Refactor: reverts SqlStore change and uses RegisterOverride instead

* Refactor: fixes lint error

* Refactor: fixes imports

* Refactor: reverts unintentional changes

* Refactor: Adds repository

* Revert "Refactor: Adds repository"

This reverts commit 4c46e8a6c420abe407d431a015cfd11ebee4082c.

* Refactor: changes after PR comments

* Refactor: Simplfies further

* Chore: fixes linting

* Chore: Changes after PR comments

* Update pkg/services/librarypanels/api.go

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

* Chore: fixes import after commited suggestion

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
parent 82b21fe3
......@@ -17,6 +17,8 @@ var (
type Response interface {
WriteTo(ctx *models.ReqContext)
// Status gets the response's status.
Status() int
}
type NormalResponse struct {
......@@ -41,6 +43,11 @@ func Wrap(action interface{}) macaron.Handler {
}
}
// Status gets the response's status.
func (r *NormalResponse) Status() int {
return r.status
}
func (r *NormalResponse) WriteTo(ctx *models.ReqContext) {
if r.err != nil {
ctx.Logger.Error(r.errMessage, "error", r.err, "remote_addr", ctx.RemoteAddr())
......
......@@ -65,6 +65,10 @@ func RegisterOverride(fn OverrideServiceFunc) {
overrides = append(overrides, fn)
}
func ClearOverrides() {
overrides = nil
}
func getServicesWithOverrides() []*Descriptor {
slice := []*Descriptor{}
for _, s := range services {
......
package librarypanels
import (
"errors"
"github.com/go-macaron/binding"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
func (lps *LibraryPanelService) registerAPIEndpoints() {
if !lps.IsEnabled() {
return
}
lps.RouteRegister.Group("/api/library-panels", func(libraryPanels routing.RouteRegister) {
libraryPanels.Post("/", middleware.ReqSignedIn, binding.Bind(addLibraryPanelCommand{}), api.Wrap(lps.createHandler))
})
}
// createHandler handles POST /api/library-panels.
func (lps *LibraryPanelService) createHandler(c *models.ReqContext, cmd addLibraryPanelCommand) api.Response {
panel, err := lps.createLibraryPanel(c, cmd)
if err != nil {
if errors.Is(err, errLibraryPanelAlreadyAdded) {
return api.Error(400, errLibraryPanelAlreadyAdded.Error(), err)
}
return api.Error(500, "Failed to create library panel", err)
}
return api.JSON(200, util.DynMap{"panel": panel})
}
package librarypanels
import (
"context"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
// createLibraryPanel function adds a LibraryPanel
func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd addLibraryPanelCommand) (LibraryPanel, error) {
libraryPanel := LibraryPanel{
OrgID: c.SignedInUser.OrgId,
FolderID: cmd.FolderID,
Title: cmd.Title,
Model: cmd.Model,
Created: time.Now(),
Updated: time.Now(),
CreatedBy: c.SignedInUser.UserId,
UpdatedBy: c.SignedInUser.UserId,
}
err := lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
if res, err := session.Query("SELECT 1 from library_panel WHERE org_id=? and folder_id=? and title=?", c.SignedInUser.OrgId, cmd.FolderID, cmd.Title); err != nil {
return err
} else if len(res) == 1 {
return errLibraryPanelAlreadyAdded
}
if _, err := session.Insert(&libraryPanel); err != nil {
return err
}
return nil
})
return libraryPanel, err
}
package librarypanels
import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/setting"
)
// LibraryPanelService is the service for the Panel Library feature.
type LibraryPanelService struct {
Cfg *setting.Cfg `inject:""`
log log.Logger
Cfg *setting.Cfg `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
RouteRegister routing.RouteRegister `inject:""`
log log.Logger
}
func init() {
......@@ -18,25 +22,27 @@ func init() {
}
// Init initializes the LibraryPanel service
func (pl *LibraryPanelService) Init() error {
pl.log = log.New("library_panel")
func (lps *LibraryPanelService) Init() error {
lps.log = log.New("librarypanels")
lps.registerAPIEndpoints()
return nil
}
// IsEnabled returns true if the Panel Library feature is enabled for this instance.
func (pl *LibraryPanelService) IsEnabled() bool {
if pl.Cfg == nil {
func (lps *LibraryPanelService) IsEnabled() bool {
if lps.Cfg == nil {
return false
}
return pl.Cfg.IsPanelLibraryEnabled()
return lps.Cfg.IsPanelLibraryEnabled()
}
// AddMigration defines database migrations.
// If Panel Library is not enabled does nothing.
func (pl *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
if !pl.IsEnabled() {
func (lps *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
if !lps.IsEnabled() {
return
}
......@@ -47,7 +53,7 @@ func (pl *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
{Name: "org_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "folder_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "title", Type: migrator.DB_NVarchar, Length: 255, Nullable: false},
{Name: "data", Type: migrator.DB_Text, Nullable: false},
{Name: "model", Type: migrator.DB_Text, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
......
package librarypanels
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
func TestCreateLibraryPanel(t *testing.T) {
t.Run("should fail if library panel already exists", func(t *testing.T) {
lps, context := setupTestEnv(t, models.ROLE_EDITOR)
command := addLibraryPanelCommand{
FolderID: 1,
Title: "Text - Library Panel",
Model: []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"title": "Text - Library Panel",
"type": "text"
}
`),
}
response := lps.createHandler(&context, command)
require.Equal(t, 200, response.Status())
response = lps.createHandler(&context, command)
require.Equal(t, 400, response.Status())
t.Cleanup(registry.ClearOverrides)
})
}
func setupMigrations(cfg *setting.Cfg) LibraryPanelService {
lps := LibraryPanelService{
SQLStore: nil,
Cfg: cfg,
}
overrideServiceFunc := func(d registry.Descriptor) (*registry.Descriptor, bool) {
descriptor := registry.Descriptor{
Name: "LibraryPanelService",
Instance: &lps,
InitPriority: 0,
}
return &descriptor, true
}
registry.RegisterOverride(overrideServiceFunc)
return lps
}
func setupTestEnv(t *testing.T, orgRole models.RoleType) (LibraryPanelService, models.ReqContext) {
cfg := setting.NewCfg()
cfg.FeatureToggles = map[string]bool{"panelLibrary": true}
service := setupMigrations(cfg)
sqlStore := sqlstore.InitTestDB(t)
service.SQLStore = sqlStore
user := models.SignedInUser{
UserId: 1,
OrgId: 1,
OrgName: "",
OrgRole: orgRole,
Login: "",
Name: "",
Email: "",
ApiKeyId: 0,
OrgCount: 0,
IsGrafanaAdmin: false,
IsAnonymous: false,
HelpFlags1: 0,
LastSeenAt: time.Now(),
Teams: nil,
}
context := models.ReqContext{
Context: nil,
SignedInUser: &user,
UserToken: nil,
IsSignedIn: false,
IsRenderCall: false,
AllowAnonymous: false,
SkipCache: false,
Logger: nil,
}
return service, context
}
package librarypanels
import (
"encoding/json"
"fmt"
"time"
)
// LibraryPanel is the model for library panel definitions.
type LibraryPanel struct {
ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id"`
FolderID int64 `xorm:"folder_id"`
Title string
Model json.RawMessage
Created time.Time
Updated time.Time
CreatedBy int64
UpdatedBy int64
}
var (
// errLibraryPanelAlreadyAdded is an error when you add a library panel that already exists.
errLibraryPanelAlreadyAdded = fmt.Errorf("library panel with that title already exists")
)
// Commands
// addLibraryPanelCommand is the command for adding a LibraryPanel
type addLibraryPanelCommand struct {
FolderID int64 `json:"folderId"`
Title string `json:"title"`
Model json.RawMessage `json:"model"`
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment