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

This commit is contained in:
Hugo Häggmark
2020-12-07 07:33:50 +01:00
parent e89075699a
commit 0376bde1c9
8 changed files with 231 additions and 17 deletions
+32
View File
@@ -0,0 +1,32 @@
package librarypanels
import (
"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.addLibraryPanelEndpoint))
})
}
// addLibraryPanelEndpoint handles POST /api/library-panels.
func (lps *LibraryPanelService) addLibraryPanelEndpoint(c *models.ReqContext, cmd addLibraryPanelCommand) api.Response {
cmd.OrgId = c.SignedInUser.OrgId
cmd.SignedInUser = c.SignedInUser
if err := lps.addLibraryPanel(&cmd); err != nil {
return api.Error(500, "Failed to create library panel", err)
}
return api.JSON(200, util.DynMap{"id": cmd.Result.Id})
}
+42
View File
@@ -0,0 +1,42 @@
package librarypanels
import (
"context"
"time"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
// AddLibraryPanel function adds a LibraryPanel
func (lps *LibraryPanelService) addLibraryPanel(cmd *addLibraryPanelCommand) error {
return lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
libraryPanel := &LibraryPanel{
OrgId: cmd.OrgId,
FolderId: cmd.FolderId,
Title: cmd.Title,
Model: cmd.Model,
Created: time.Now(),
Updated: time.Now(),
CreatedBy: cmd.SignedInUser.UserId,
UpdatedBy: cmd.SignedInUser.UserId,
}
if res, err := session.Query("SELECT 1 from library_panel WHERE org_id=? and folder_id=? and title=?", cmd.OrgId, cmd.FolderId, cmd.Title); err != nil {
return err
} else if len(res) == 1 {
return errLibraryPanelAlreadyAdded
}
// TODO
// check if user has rights
if _, err := session.Insert(libraryPanel); err != nil {
return err
}
cmd.Result = libraryPanel
return nil
})
}
@@ -0,0 +1,63 @@
package librarypanels
import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
"testing"
"time"
)
func TestAddLibraryPanel(t *testing.T) {
t.Run("should fail if library panel already exists", func(t *testing.T) {
lps, user := setupTestEnv(t, models.ROLE_EDITOR)
command := addLibraryPanelCommand{
OrgId: 1,
FolderId: 1,
SignedInUser: &user,
Title: "Text - Library Panel",
Model: []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"title": "Text - Library Panel",
"type": "text"
}
`),
}
noErr := lps.addLibraryPanel(&command)
require.NoError(t, noErr)
err := lps.addLibraryPanel(&command)
require.Error(t, err)
})
}
func setupTestEnv(t *testing.T, orgRole models.RoleType) (LibraryPanelService, models.SignedInUser) {
cfg := setting.Cfg{}
cfg.FeatureToggles = map[string]bool{"panelLibrary": true}
sqlStore := sqlstore.InitTestDBWithCfg(t, &cfg)
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,
}
lps := LibraryPanelService{
SQLStore: sqlStore,
Cfg: &cfg,
}
return lps, user
}
+19 -11
View File
@@ -1,16 +1,20 @@
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,29 @@ 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")
if lps.IsEnabled() {
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 {
return false
func (lps *LibraryPanelService) IsEnabled() bool {
if lps.Cfg == nil {
return true // used for test purposes only
}
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 +55,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},
+44
View File
@@ -0,0 +1,44 @@
package librarypanels
import (
"encoding/json"
"fmt"
"time"
"github.com/grafana/grafana/pkg/models"
)
// LibraryPanel is the model for library panel definitions.
type LibraryPanel struct {
Id int64
OrgId int64
FolderId int64
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")
// errLibraryPanelNotFound is an error for an unknown library panel definition.
errLibraryPanelNotFound = fmt.Errorf("could not find library panel definition")
)
// Commands
// addLibraryPanelCommand is the command for adding a LibraryPanel
type addLibraryPanelCommand struct {
OrgId int64 `json:"-"`
FolderId int64 `json:"folderId"`
Title string `json:"title"`
SignedInUser *models.SignedInUser `json:"-"`
Model json.RawMessage `json:"model"`
Result *LibraryPanel
}
+17 -1
View File
@@ -346,6 +346,15 @@ var testSQLStore *SQLStore
// InitTestDB initializes the test DB.
func InitTestDB(t ITestDB) *SQLStore {
return initTestDB(t, nil)
}
// InitTestDBWithCfg initializes the test DB with Cfg.
func InitTestDBWithCfg(t ITestDB, cfg *setting.Cfg) *SQLStore {
return initTestDB(t, nil)
}
func initTestDB(t ITestDB, cfg *setting.Cfg) *SQLStore {
t.Helper()
if testSQLStore == nil {
testSQLStore = &SQLStore{}
@@ -362,7 +371,14 @@ func InitTestDB(t ITestDB) *SQLStore {
}
// set test db config
testSQLStore.Cfg = setting.NewCfg()
if cfg == nil {
testSQLStore.Cfg = setting.NewCfg()
}
if cfg != nil {
testSQLStore.Cfg = cfg
}
sec, err := testSQLStore.Cfg.Raw.NewSection("database")
if err != nil {
t.Fatalf("Failed to create section: %s", err)
@@ -20,6 +20,11 @@ export async function getVariableQueryEditor<
datasource: DataSourceApi<TQuery, TOptions>,
importDataSourcePluginFunc = importDataSourcePlugin
): Promise<VariableQueryEditorType> {
if (hasLegacyVariableSupport(datasource)) {
const dsPlugin = await importDataSourcePluginFunc(datasource.meta!);
return dsPlugin.components.VariableQueryEditor ?? LegacyVariableQueryEditor;
}
if (hasCustomVariableSupport(datasource)) {
return datasource.variables.editor;
}
@@ -38,11 +43,6 @@ export async function getVariableQueryEditor<
return StandardVariableQueryEditor;
}
if (hasLegacyVariableSupport(datasource)) {
const dsPlugin = await importDataSourcePluginFunc(datasource.meta!);
return dsPlugin.components.VariableQueryEditor ?? LegacyVariableQueryEditor;
}
return null;
}
@@ -137,3 +137,12 @@ describe('toEmitValues matcher', () => {
});
});
});
describe.only('demo testing observables', () => {
it('toEmitValues demo', async () => {
const observable = of(1, 'a', 'I');
});
it('toEmitValuesWith demo', async () => {
const observable = of(1, 'a', 'I');
});
});