public dashboards: insert default public dashboard config into database on save (#49131)
This PR adds endpoints for saving and retrieving a public dashboard configuration and and api endpoint to retrieve the public dashboard. All of this is highly experimental and APIs will change. Notably, we will be removing isPublic from the dashboard model and moving it over to the public dashboard table in the next release. Further context can be found here: https://github.com/grafana/grafana/pull/49131#issuecomment-1145456952
This commit is contained in:
@@ -192,47 +192,6 @@ func (d *DashboardStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models
|
||||
return cmd.Result, err
|
||||
}
|
||||
|
||||
// retrieves public dashboard configuration
|
||||
func (d *DashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
||||
var result []*models.Dashboard
|
||||
|
||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
return sess.Where("org_id = ? AND uid= ?", orgId, dashboardUid).Find(&result)
|
||||
})
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, models.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
pdc := &models.PublicDashboardConfig{
|
||||
IsPublic: result[0].IsPublic,
|
||||
}
|
||||
|
||||
return pdc, err
|
||||
}
|
||||
|
||||
// stores public dashboard configuration
|
||||
func (d *DashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error) {
|
||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
affectedRowCount, err := sess.Table("dashboard").Where("org_id = ? AND uid = ?", cmd.OrgId, cmd.Uid).Update(map[string]interface{}{"is_public": cmd.PublicDashboardConfig.IsPublic})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRowCount == 0 {
|
||||
return models.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cmd.PublicDashboardConfig, nil
|
||||
}
|
||||
|
||||
func (d *DashboardStore) UpdateDashboardACL(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
|
||||
return d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// delete existing items
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
// retrieves public dashboard configuration
|
||||
func (d *DashboardStore) GetPublicDashboard(uid string) (*models.PublicDashboard, *models.Dashboard, error) {
|
||||
if uid == "" {
|
||||
return nil, nil, models.ErrPublicDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
// get public dashboard
|
||||
pdRes := &models.PublicDashboard{Uid: uid}
|
||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
has, err := sess.Get(pdRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return models.ErrPublicDashboardNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// find dashboard
|
||||
dashRes := &models.Dashboard{OrgId: pdRes.OrgId, Uid: pdRes.DashboardUid}
|
||||
err = d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
has, err := sess.Get(dashRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return models.ErrPublicDashboardNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pdRes, dashRes, err
|
||||
}
|
||||
|
||||
// generates a new unique uid to retrieve a public dashboard
|
||||
func generateNewPublicDashboardUid(sess *sqlstore.DBSession) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
exists, err := sess.Get(&models.PublicDashboard{Uid: uid})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", models.ErrPublicDashboardFailedGenerateUniqueUid
|
||||
}
|
||||
|
||||
// retrieves public dashboard configuration
|
||||
func (d *DashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
|
||||
if dashboardUid == "" {
|
||||
return nil, models.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
// get dashboard and publicDashboard
|
||||
dashRes := &models.Dashboard{OrgId: orgId, Uid: dashboardUid}
|
||||
pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}
|
||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
// dashboard
|
||||
has, err := sess.Get(dashRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return models.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// publicDashboard
|
||||
_, err = sess.Get(pdRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pdc := &models.PublicDashboardConfig{
|
||||
IsPublic: dashRes.IsPublic,
|
||||
PublicDashboard: *pdRes,
|
||||
}
|
||||
|
||||
return pdc, err
|
||||
}
|
||||
|
||||
// persists public dashboard configuration
|
||||
func (d *DashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error) {
|
||||
if len(cmd.PublicDashboardConfig.PublicDashboard.DashboardUid) == 0 {
|
||||
return nil, models.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
// update isPublic on dashboard entry
|
||||
affectedRowCount, err := sess.Table("dashboard").Where("org_id = ? AND uid = ?", cmd.OrgId, cmd.DashboardUid).Update(map[string]interface{}{"is_public": cmd.PublicDashboardConfig.IsPublic})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRowCount == 0 {
|
||||
return models.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// update dashboard_public_config
|
||||
// if we have a uid, public dashboard config exists. delete it otherwise generate a uid
|
||||
if cmd.PublicDashboardConfig.PublicDashboard.Uid != "" {
|
||||
if _, err = sess.Exec("DELETE FROM dashboard_public_config WHERE uid=?", cmd.PublicDashboardConfig.PublicDashboard.Uid); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
uid, err := generateNewPublicDashboardUid(sess)
|
||||
if err != nil {
|
||||
return errutil.Wrapf(err, "Failed to generate UID for public dashboard")
|
||||
}
|
||||
cmd.PublicDashboardConfig.PublicDashboard.Uid = uid
|
||||
}
|
||||
|
||||
_, err = sess.Insert(&cmd.PublicDashboardConfig.PublicDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cmd.PublicDashboardConfig, nil
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// GetPublicDashboard
|
||||
func TestGetPublicDashboard(t *testing.T) {
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var dashboardStore *DashboardStore
|
||||
var savedDashboard *models.Dashboard
|
||||
|
||||
setup := func() {
|
||||
sqlStore = sqlstore.InitTestDB(t)
|
||||
dashboardStore = ProvideDashboardStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
t.Run("returns PublicDashboard and Dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
pdc, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: "abc1234",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pd, d, err := dashboardStore.GetPublicDashboard("abc1234")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pd, &pdc.PublicDashboard)
|
||||
assert.Equal(t, d.Uid, pdc.PublicDashboard.DashboardUid)
|
||||
})
|
||||
|
||||
t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) {
|
||||
setup()
|
||||
_, _, err := dashboardStore.GetPublicDashboard("")
|
||||
require.Error(t, models.ErrPublicDashboardIdentifierNotSet, err)
|
||||
})
|
||||
|
||||
t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) {
|
||||
setup()
|
||||
_, _, err := dashboardStore.GetPublicDashboard("zzzzzz")
|
||||
require.Error(t, models.ErrPublicDashboardNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) {
|
||||
setup()
|
||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: "abc1234",
|
||||
DashboardUid: "nevergonnafindme",
|
||||
OrgId: savedDashboard.OrgId,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, _, err = dashboardStore.GetPublicDashboard("abc1234")
|
||||
require.Error(t, models.ErrDashboardNotFound, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig
|
||||
func TestGetPublicDashboardConfig(t *testing.T) {
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var dashboardStore *DashboardStore
|
||||
var savedDashboard *models.Dashboard
|
||||
|
||||
setup := func() {
|
||||
sqlStore = sqlstore.InitTestDB(t)
|
||||
dashboardStore = ProvideDashboardStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
t.Run("returns isPublic and set dashboardUid and orgId", func(t *testing.T) {
|
||||
setup()
|
||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &models.PublicDashboardConfig{IsPublic: false, PublicDashboard: models.PublicDashboard{DashboardUid: savedDashboard.Uid, OrgId: savedDashboard.OrgId}}, pdc)
|
||||
})
|
||||
|
||||
t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) {
|
||||
setup()
|
||||
_, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, "")
|
||||
require.Error(t, models.ErrDashboardIdentifierNotSet, err)
|
||||
})
|
||||
|
||||
t.Run("returns isPublic along with public dashboard when exists", func(t *testing.T) {
|
||||
setup()
|
||||
// insert test public dashboard
|
||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: "pubdash-uid",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
TimeSettings: "{from: now, to: then}",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, resp, pdc)
|
||||
})
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig
|
||||
func TestSavePublicDashboardConfig(t *testing.T) {
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var dashboardStore *DashboardStore
|
||||
var savedDashboard *models.Dashboard
|
||||
var savedDashboard2 *models.Dashboard
|
||||
|
||||
setup := func() {
|
||||
sqlStore = sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
dashboardStore = ProvideDashboardStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true)
|
||||
}
|
||||
|
||||
t.Run("saves new public dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: "pubdash-uid",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
//verify saved response and queried response are the same
|
||||
assert.Equal(t, resp, pdc)
|
||||
|
||||
// verify we have a valid uid
|
||||
assert.True(t, util.IsValidShortUID(pdc.PublicDashboard.Uid))
|
||||
|
||||
// verify we didn't update all dashboards
|
||||
pdc2, err := dashboardStore.GetPublicDashboardConfig(savedDashboard2.OrgId, savedDashboard2.Uid)
|
||||
assert.False(t, pdc2.IsPublic)
|
||||
})
|
||||
|
||||
t.Run("returns ErrDashboardIdentifierNotSet", func(t *testing.T) {
|
||||
setup()
|
||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
DashboardUid: "",
|
||||
OrgId: savedDashboard.OrgId,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, models.ErrDashboardIdentifierNotSet, err)
|
||||
})
|
||||
|
||||
t.Run("overwrites existing public dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
pdUid := util.GenerateShortUID()
|
||||
|
||||
// insert initial record
|
||||
_, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: true,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: pdUid,
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// update initial record
|
||||
resp, err := dashboardStore.SavePublicDashboardConfig(models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboardConfig: models.PublicDashboardConfig{
|
||||
IsPublic: false,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: pdUid,
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
TimeSettings: "{}",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pdc, err := dashboardStore.GetPublicDashboardConfig(savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, resp, pdc)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user