public dashboards: move into into its own service (#51358)
This PR moves public dashboards into its own self contained service including API, Service, Database, and Models. Routes are mounted on the Grafana HTTPServer by the API service at injection time with wire.go. The main route that loads the frontend for public dashboards is still handled by the API package. Co-authored-by: Jesse Weaver <jesse.weaver@grafana.com> Co-authored-by: Owen Smallwood <owen.smallwood@grafana.com>
This commit is contained in:
co-authored by
Jesse Weaver
Owen Smallwood
parent
ba2d8cd838
commit
eacee08135
@@ -3,15 +3,12 @@ package dashboards
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
//go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go
|
||||
// DashboardService is a service for operating on dashboards.
|
||||
type DashboardService interface {
|
||||
BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error)
|
||||
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error)
|
||||
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
|
||||
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
||||
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
||||
@@ -20,14 +17,11 @@ type DashboardService interface {
|
||||
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
|
||||
GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error
|
||||
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
|
||||
GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error)
|
||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error)
|
||||
HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error
|
||||
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
||||
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
|
||||
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
|
||||
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
|
||||
SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error)
|
||||
SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error
|
||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
||||
}
|
||||
@@ -66,18 +60,13 @@ type Store interface {
|
||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
|
||||
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
|
||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error)
|
||||
GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error)
|
||||
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
||||
HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error
|
||||
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
|
||||
// SaveAlerts saves dashboard alerts.
|
||||
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
|
||||
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
|
||||
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error)
|
||||
UnprovisionDashboard(ctx context.Context, id int64) error
|
||||
UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error
|
||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
||||
// ValidateDashboardBeforeSave validates a dashboard before save.
|
||||
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Code generated by mockery v2.12.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.12.2. DO NOT EDIT.
|
||||
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
dtos "github.com/grafana/grafana/pkg/api/dtos"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
models "github.com/grafana/grafana/pkg/models"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
testing "testing"
|
||||
)
|
||||
@@ -18,50 +16,6 @@ type FakeDashboardService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// BuildAnonymousUser provides a mock function with given fields: ctx, dashboard
|
||||
func (_m *FakeDashboardService) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
ret := _m.Called(ctx, dashboard)
|
||||
|
||||
var r0 *models.SignedInUser
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard) *models.SignedInUser); ok {
|
||||
r0 = rf(ctx, dashboard)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard) error); ok {
|
||||
r1 = rf(ctx, dashboard)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId
|
||||
func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||
ret := _m.Called(ctx, dashboard, publicDashboard, panelId)
|
||||
|
||||
var r0 dtos.MetricRequest
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) dtos.MetricRequest); ok {
|
||||
r0 = rf(ctx, dashboard, publicDashboard, panelId)
|
||||
} else {
|
||||
r0 = ret.Get(0).(dtos.MetricRequest)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) error); ok {
|
||||
r1 = rf(ctx, dashboard, publicDashboard, panelId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BuildSaveDashboardCommand provides a mock function with given fields: ctx, dto, shouldValidateAlerts, validateProvisionedDashboard
|
||||
func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
|
||||
ret := _m.Called(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
|
||||
@@ -192,52 +146,6 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *models
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
||||
var r0 *models.Dashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Dashboard); ok {
|
||||
r0 = rf(ctx, accessToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, accessToken)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
||||
func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, orgId, dashboardUid)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, orgId, dashboardUid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, orgId, dashboardUid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query
|
||||
func (_m *FakeDashboardService) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error {
|
||||
ret := _m.Called(ctx, query)
|
||||
@@ -326,29 +234,6 @@ func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDash
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig provides a mock function with given fields: ctx, dto
|
||||
func (_m *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, dto)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *SavePublicDashboardConfigDTO) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, dto)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *SavePublicDashboardConfigDTO) error); ok {
|
||||
r1 = rf(ctx, dto)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchDashboards provides a mock function with given fields: ctx, query
|
||||
func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// retrieves public dashboard configuration
|
||||
func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) {
|
||||
if accessToken == "" {
|
||||
return nil, nil, dashboards.ErrPublicDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
// get public dashboard
|
||||
pdRes := &models.PublicDashboard{AccessToken: accessToken}
|
||||
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
has, err := sess.Get(pdRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return dashboards.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(ctx, func(sess *sqlstore.DBSession) error {
|
||||
has, err := sess.Get(dashRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return dashboards.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 (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
||||
var uid string
|
||||
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) 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 nil
|
||||
}
|
||||
}
|
||||
|
||||
return dashboards.ErrPublicDashboardFailedGenerateUniqueUid
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// retrieves public dashboard configuration
|
||||
func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||
if dashboardUid == "" {
|
||||
return nil, dashboards.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}
|
||||
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// publicDashboard
|
||||
_, err := sess.Get(pdRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pdRes, err
|
||||
}
|
||||
|
||||
// persists public dashboard configuration
|
||||
func (d *DashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) {
|
||||
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cmd.PublicDashboard, nil
|
||||
}
|
||||
|
||||
// updates existing public dashboard configuration
|
||||
func (d *DashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
|
||||
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
timeSettingsJSON, err := cmd.PublicDashboard.TimeSettings.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("UPDATE dashboard_public SET is_enabled = ?, time_settings = ?, updated_by = ?, updated_at = ? WHERE uid = ?",
|
||||
cmd.PublicDashboard.IsEnabled,
|
||||
string(timeSettingsJSON),
|
||||
cmd.PublicDashboard.UpdatedBy,
|
||||
cmd.PublicDashboard.UpdatedAt.UTC().Format("2006-01-02 15:04:05"),
|
||||
cmd.PublicDashboard.Uid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// This is what the db sets empty time settings to
|
||||
var DefaultTimeSettings, _ = simplejson.NewJson([]byte(`{}`))
|
||||
|
||||
// Default time to pass in with seconds rounded
|
||||
var DefaultTime = time.Now().UTC().Round(time.Second)
|
||||
|
||||
// GetPublicDashboard
|
||||
func TestIntegrationGetPublicDashboard(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()
|
||||
pubdash, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "abc1234",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
TimeSettings: DefaultTimeSettings,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pd, d, err := dashboardStore.GetPublicDashboard(context.Background(), "NOTAREALUUID")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, pd, pubdash)
|
||||
assert.Equal(t, d.Uid, pubdash.DashboardUid)
|
||||
})
|
||||
|
||||
t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) {
|
||||
setup()
|
||||
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "")
|
||||
require.Error(t, dashboards.ErrPublicDashboardIdentifierNotSet, err)
|
||||
})
|
||||
|
||||
t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) {
|
||||
setup()
|
||||
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "zzzzzz")
|
||||
require.Error(t, dashboards.ErrPublicDashboardNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) {
|
||||
setup()
|
||||
_, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "abc1234",
|
||||
DashboardUid: "nevergonnafindme",
|
||||
OrgId: savedDashboard.OrgId,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, _, err = dashboardStore.GetPublicDashboard(context.Background(), "abc1234")
|
||||
require.Error(t, dashboards.ErrDashboardNotFound, err)
|
||||
})
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig
|
||||
func TestIntegrationGetPublicDashboardConfig(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()
|
||||
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &models.PublicDashboard{IsEnabled: false, DashboardUid: savedDashboard.Uid, OrgId: savedDashboard.OrgId}, pubdash)
|
||||
})
|
||||
|
||||
t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) {
|
||||
setup()
|
||||
_, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, "")
|
||||
require.Error(t, dashboards.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(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "pubdash-uid",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
TimeSettings: DefaultTimeSettings,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, assert.ObjectsAreEqualValues(resp, pubdash))
|
||||
assert.True(t, assert.ObjectsAreEqual(resp, pubdash))
|
||||
})
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig
|
||||
func TestIntegrationSavePublicDashboardConfig(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(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
Uid: "pubdash-uid",
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
TimeSettings: DefaultTimeSettings,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
//verify saved response and queried response are the same
|
||||
assert.Equal(t, resp, pubdash)
|
||||
|
||||
// verify we have a valid uid
|
||||
assert.True(t, util.IsValidShortUID(pubdash.Uid))
|
||||
|
||||
// verify we didn't update all dashboards
|
||||
pubdash2, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard2.OrgId, savedDashboard2.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, pubdash2.IsEnabled)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var dashboardStore *DashboardStore
|
||||
var savedDashboard *models.Dashboard
|
||||
var anotherSavedDashboard *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)
|
||||
anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, true)
|
||||
}
|
||||
|
||||
t.Run("updates an existing dashboard", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
// inserting two different public dashboards to test update works and only affect the desired pd by uid
|
||||
anotherPdUid := "anotherUid"
|
||||
_, err := dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: anotherSavedDashboard.Uid,
|
||||
OrgId: anotherSavedDashboard.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: anotherPdUid,
|
||||
DashboardUid: anotherSavedDashboard.Uid,
|
||||
OrgId: anotherSavedDashboard.OrgId,
|
||||
IsEnabled: true,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
AccessToken: "fakeaccesstoken",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pdUid := "asdf1234"
|
||||
_, err = dashboardStore.SavePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: pdUid,
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
IsEnabled: true,
|
||||
CreatedAt: DefaultTime,
|
||||
CreatedBy: 7,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedPublicDashboard := models.PublicDashboard{
|
||||
Uid: pdUid,
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
IsEnabled: false,
|
||||
TimeSettings: simplejson.NewFromAny(map[string]interface{}{"from": "now-8", "to": "now"}),
|
||||
UpdatedAt: time.Now().UTC().Round(time.Second),
|
||||
UpdatedBy: 8,
|
||||
}
|
||||
// update initial record
|
||||
err = dashboardStore.UpdatePublicDashboardConfig(context.Background(), models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: savedDashboard.Uid,
|
||||
OrgId: savedDashboard.OrgId,
|
||||
PublicDashboard: updatedPublicDashboard,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// updated dashboard should have changed
|
||||
pdRetrieved, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, savedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, updatedPublicDashboard.UpdatedAt, pdRetrieved.UpdatedAt)
|
||||
// make sure we're correctly updated IsEnabled because we have to call
|
||||
// UseBool with xorm
|
||||
assert.Equal(t, updatedPublicDashboard.IsEnabled, pdRetrieved.IsEnabled)
|
||||
|
||||
// not updated dashboard shouldn't have changed
|
||||
pdNotUpdatedRetrieved, err := dashboardStore.GetPublicDashboardConfig(context.Background(), anotherSavedDashboard.OrgId, anotherSavedDashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, updatedPublicDashboard.UpdatedAt, pdNotUpdatedRetrieved.UpdatedAt)
|
||||
assert.NotEqual(t, updatedPublicDashboard.IsEnabled, pdNotUpdatedRetrieved.IsEnabled)
|
||||
})
|
||||
}
|
||||
@@ -15,13 +15,6 @@ type SaveDashboardDTO struct {
|
||||
Dashboard *models.Dashboard
|
||||
}
|
||||
|
||||
type SavePublicDashboardConfigDTO struct {
|
||||
DashboardUid string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
PublicDashboard *models.PublicDashboard
|
||||
}
|
||||
|
||||
type DashboardSearchProjection struct {
|
||||
ID int64 `xorm:"id"`
|
||||
UID string `xorm:"uid"`
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
// Gets public dashboard via access token
|
||||
func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) {
|
||||
pubdash, d, err := dr.dashboardStore.GetPublicDashboard(ctx, accessToken)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pubdash == nil || d == nil {
|
||||
return nil, dashboards.ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
if !pubdash.IsEnabled {
|
||||
return nil, dashboards.ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
ts := pubdash.BuildTimeSettings(d)
|
||||
d.Data.SetPath([]string{"time", "from"}, ts.From)
|
||||
d.Data.SetPath([]string{"time", "to"}, ts.To)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig is a helper method to retrieve the public dashboard configuration for a given dashboard from the database
|
||||
func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||
pdc, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, orgId, dashboardUid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pdc, nil
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig is a helper method to persist the sharing config
|
||||
// to the database. It handles validations for sharing config and persistence
|
||||
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||
if len(dto.DashboardUid) == 0 {
|
||||
return nil, dashboards.ErrDashboardIdentifierNotSet
|
||||
}
|
||||
|
||||
// set default value for time settings
|
||||
if dto.PublicDashboard.TimeSettings == nil {
|
||||
dto.PublicDashboard.TimeSettings = simplejson.New()
|
||||
}
|
||||
|
||||
if dto.PublicDashboard.Uid == "" {
|
||||
return dr.savePublicDashboardConfig(ctx, dto)
|
||||
}
|
||||
|
||||
return dr.updatePublicDashboardConfig(ctx, dto)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) savePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||
uid, err := dr.dashboardStore.GenerateNewPublicDashboardUid(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessToken, err := GenerateAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := models.SavePublicDashboardConfigCommand{
|
||||
DashboardUid: dto.DashboardUid,
|
||||
OrgId: dto.OrgId,
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: uid,
|
||||
DashboardUid: dto.DashboardUid,
|
||||
OrgId: dto.OrgId,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
CreatedBy: dto.UserId,
|
||||
CreatedAt: time.Now(),
|
||||
AccessToken: accessToken,
|
||||
},
|
||||
}
|
||||
|
||||
return dr.dashboardStore.SavePublicDashboardConfig(ctx, cmd)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) updatePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
|
||||
cmd := models.SavePublicDashboardConfigCommand{
|
||||
PublicDashboard: models.PublicDashboard{
|
||||
Uid: dto.PublicDashboard.Uid,
|
||||
IsEnabled: dto.PublicDashboard.IsEnabled,
|
||||
TimeSettings: dto.PublicDashboard.TimeSettings,
|
||||
UpdatedBy: dto.UserId,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
err := dr.dashboardStore.UpdatePublicDashboardConfig(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicDashboard, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, dto.OrgId, dto.DashboardUid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return publicDashboard, nil
|
||||
}
|
||||
|
||||
// BuildPublicDashboardMetricRequest merges public dashboard parameters with
|
||||
// dashboard and returns a metrics request to be sent to query backend
|
||||
func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
|
||||
if !publicDashboard.IsEnabled {
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardNotFound
|
||||
}
|
||||
|
||||
queriesByPanel := models.GroupQueriesByPanelId(dashboard.Data)
|
||||
|
||||
if _, ok := queriesByPanel[panelId]; !ok {
|
||||
return dtos.MetricRequest{}, dashboards.ErrPublicDashboardPanelNotFound
|
||||
}
|
||||
|
||||
ts := publicDashboard.BuildTimeSettings(dashboard)
|
||||
|
||||
return dtos.MetricRequest{
|
||||
From: ts.From,
|
||||
To: ts.To,
|
||||
Queries: queriesByPanel[panelId],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
||||
func (dr *DashboardServiceImpl) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*models.SignedInUser, error) {
|
||||
datasourceUids := models.GetUniqueDashboardDatasourceUids(dashboard.Data)
|
||||
|
||||
// Create a temp user with read-only datasource permissions
|
||||
anonymousUser := &models.SignedInUser{OrgId: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
||||
permissions := make(map[string][]string)
|
||||
queryScopes := make([]string, 0)
|
||||
readScopes := make([]string, 0)
|
||||
for _, uid := range datasourceUids {
|
||||
queryScopes = append(queryScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
readScopes = append(readScopes, fmt.Sprintf("datasources:uid:%s", uid))
|
||||
}
|
||||
permissions[datasources.ActionQuery] = queryScopes
|
||||
permissions[datasources.ActionRead] = readScopes
|
||||
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
||||
|
||||
return anonymousUser, nil
|
||||
}
|
||||
|
||||
// generates a uuid formatted without dashes to use as access token
|
||||
func GenerateAccessToken() (string, error) {
|
||||
token, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", token[:]), nil
|
||||
}
|
||||
@@ -1,492 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
var timeSettings, _ = simplejson.NewJson([]byte(`{"from": "now-12", "to": "now"}`))
|
||||
var defaultPubdashTimeSettings, _ = simplejson.NewJson([]byte(`{}`))
|
||||
var dashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-8", "to": "now"}})
|
||||
var mergedDashboardData = simplejson.NewFromAny(map[string]interface{}{"time": map[string]interface{}{"from": "now-12", "to": "now"}})
|
||||
|
||||
func TestGetPublicDashboard(t *testing.T) {
|
||||
type storeResp struct {
|
||||
pd *models.PublicDashboard
|
||||
d *models.Dashboard
|
||||
err error
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
AccessToken string
|
||||
StoreResp *storeResp
|
||||
ErrResp error
|
||||
DashResp *models.Dashboard
|
||||
}{
|
||||
{
|
||||
Name: "returns a dashboard",
|
||||
AccessToken: "abc123",
|
||||
StoreResp: &storeResp{
|
||||
pd: &models.PublicDashboard{IsEnabled: true},
|
||||
d: &models.Dashboard{Uid: "mydashboard", Data: dashboardData},
|
||||
err: nil,
|
||||
},
|
||||
ErrResp: nil,
|
||||
DashResp: &models.Dashboard{Uid: "mydashboard", Data: dashboardData},
|
||||
},
|
||||
{
|
||||
Name: "puts pubdash time settings into dashboard",
|
||||
AccessToken: "abc123",
|
||||
StoreResp: &storeResp{
|
||||
pd: &models.PublicDashboard{IsEnabled: true, TimeSettings: timeSettings},
|
||||
d: &models.Dashboard{Data: dashboardData},
|
||||
err: nil,
|
||||
},
|
||||
ErrResp: nil,
|
||||
DashResp: &models.Dashboard{Data: mergedDashboardData},
|
||||
},
|
||||
{
|
||||
Name: "returns ErrPublicDashboardNotFound when isEnabled is false",
|
||||
AccessToken: "abc123",
|
||||
StoreResp: &storeResp{
|
||||
pd: &models.PublicDashboard{IsEnabled: false},
|
||||
d: &models.Dashboard{Uid: "mydashboard"},
|
||||
err: nil,
|
||||
},
|
||||
ErrResp: dashboards.ErrPublicDashboardNotFound,
|
||||
DashResp: nil,
|
||||
},
|
||||
{
|
||||
Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing",
|
||||
AccessToken: "abc123",
|
||||
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
||||
ErrResp: dashboards.ErrPublicDashboardNotFound,
|
||||
DashResp: nil,
|
||||
},
|
||||
{
|
||||
Name: "returns ErrPublicDashboardNotFound if Dashboard missing",
|
||||
AccessToken: "abc123",
|
||||
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
||||
ErrResp: dashboards.ErrPublicDashboardNotFound,
|
||||
DashResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
|
||||
fakeStore.On("GetPublicDashboard", mock.Anything, mock.Anything).
|
||||
Return(test.StoreResp.pd, test.StoreResp.d, test.StoreResp.err)
|
||||
|
||||
dashboard, err := service.GetPublicDashboard(context.Background(), test.AccessToken)
|
||||
if test.ErrResp != nil {
|
||||
assert.Error(t, test.ErrResp, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.DashResp, dashboard)
|
||||
|
||||
if test.DashResp != nil {
|
||||
assert.NotNil(t, dashboard.CreatedBy)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSavePublicDashboard(t *testing.T) {
|
||||
t.Run("Saving public dashboard", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// DashboardUid/OrgId/CreatedBy set by the command, not parameters
|
||||
assert.Equal(t, dashboard.Uid, pubdash.DashboardUid)
|
||||
assert.Equal(t, dashboard.OrgId, pubdash.OrgId)
|
||||
assert.Equal(t, dto.UserId, pubdash.CreatedBy)
|
||||
// IsEnabled set by parameters
|
||||
assert.Equal(t, dto.PublicDashboard.IsEnabled, pubdash.IsEnabled)
|
||||
// CreatedAt set to non-zero time
|
||||
assert.NotEqual(t, &time.Time{}, pubdash.CreatedAt)
|
||||
// Time settings set by db
|
||||
assert.Equal(t, timeSettings, pubdash.TimeSettings)
|
||||
// accessToken is valid uuid
|
||||
_, err = uuid.Parse(pubdash.AccessToken)
|
||||
require.NoError(t, err, "expected a valid UUID, got %s", pubdash.AccessToken)
|
||||
})
|
||||
|
||||
t.Run("Validate pubdash has default time setting value", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, defaultPubdashTimeSettings, pubdash.TimeSettings)
|
||||
})
|
||||
|
||||
t.Run("PLACEHOLDER - dashboard with template variables cannot be saved", func(t *testing.T) {})
|
||||
}
|
||||
|
||||
func TestUpdatePublicDashboard(t *testing.T) {
|
||||
t.Run("Updating public dashboard", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
savedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to overwrite settings
|
||||
dto = &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 8,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
Uid: savedPubdash.Uid,
|
||||
OrgId: 9,
|
||||
DashboardUid: "abc1234",
|
||||
CreatedBy: 9,
|
||||
CreatedAt: time.Time{},
|
||||
|
||||
IsEnabled: true,
|
||||
TimeSettings: timeSettings,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
}
|
||||
|
||||
// Since the dto.PublicDashboard has a uid, this will call
|
||||
// service.updatePublicDashboardConfig
|
||||
_, err = service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// don't get updated
|
||||
assert.Equal(t, savedPubdash.DashboardUid, updatedPubdash.DashboardUid)
|
||||
assert.Equal(t, savedPubdash.OrgId, updatedPubdash.OrgId)
|
||||
assert.Equal(t, savedPubdash.CreatedAt, updatedPubdash.CreatedAt)
|
||||
assert.Equal(t, savedPubdash.CreatedBy, updatedPubdash.CreatedBy)
|
||||
assert.Equal(t, savedPubdash.AccessToken, updatedPubdash.AccessToken)
|
||||
|
||||
// gets updated
|
||||
assert.Equal(t, dto.PublicDashboard.IsEnabled, updatedPubdash.IsEnabled)
|
||||
assert.Equal(t, dto.PublicDashboard.TimeSettings, updatedPubdash.TimeSettings)
|
||||
assert.Equal(t, dto.UserId, updatedPubdash.UpdatedBy)
|
||||
assert.NotEqual(t, &time.Time{}, updatedPubdash.UpdatedAt)
|
||||
})
|
||||
|
||||
t.Run("Updating set empty time settings", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 7,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
// Since the dto.PublicDashboard has a uid, this will call
|
||||
// service.updatePublicDashboardConfig
|
||||
_, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
savedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to overwrite settings
|
||||
dto = &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: dashboard.Uid,
|
||||
OrgId: dashboard.OrgId,
|
||||
UserId: 8,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
Uid: savedPubdash.Uid,
|
||||
OrgId: 9,
|
||||
DashboardUid: "abc1234",
|
||||
CreatedBy: 9,
|
||||
CreatedAt: time.Time{},
|
||||
|
||||
IsEnabled: true,
|
||||
AccessToken: "NOTAREALUUID",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedPubdash, err := service.GetPublicDashboardConfig(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
timeSettings, err := simplejson.NewJson([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, timeSettings, updatedPubdash.TimeSettings)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
|
||||
user, err := service.BuildAnonymousUser(context.Background(), dashboard)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dashboard.OrgId, user.OrgId)
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgId]["datasources:query"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgId]["datasources:query"][1])
|
||||
require.Equal(t, "datasources:uid:ds1", user.Permissions[user.OrgId]["datasources:read"][0])
|
||||
require.Equal(t, "datasources:uid:ds3", user.Permissions[user.OrgId]["datasources:read"][1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildPublicDashboardMetricRequest(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore)
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
}
|
||||
|
||||
dto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: publicDashboard.Uid,
|
||||
OrgId: publicDashboard.OrgId,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: true,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: timeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
publicDashboardPD, err := service.SavePublicDashboardConfig(context.Background(), dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
nonPublicDto := &dashboards.SavePublicDashboardConfigDTO{
|
||||
DashboardUid: nonPublicDashboard.Uid,
|
||||
OrgId: nonPublicDashboard.OrgId,
|
||||
PublicDashboard: &models.PublicDashboard{
|
||||
IsEnabled: false,
|
||||
DashboardUid: "NOTTHESAME",
|
||||
OrgId: 9999999,
|
||||
TimeSettings: defaultPubdashTimeSettings,
|
||||
},
|
||||
}
|
||||
|
||||
nonPublicDashboardPD, err := service.SavePublicDashboardConfig(context.Background(), nonPublicDto)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("extracts queries from provided dashboard", func(t *testing.T) {
|
||||
reqDTO, err := service.BuildPublicDashboardMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, timeSettings.Get("from").MustString(), reqDTO.From)
|
||||
require.Equal(t, timeSettings.Get("to").MustString(), reqDTO.To)
|
||||
require.Len(t, reqDTO.Queries, 2)
|
||||
require.Equal(
|
||||
t,
|
||||
simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "mysql",
|
||||
"uid": "ds1"
|
||||
},
|
||||
"refId": "A"
|
||||
}`)),
|
||||
reqDTO.Queries[0],
|
||||
)
|
||||
require.Equal(
|
||||
t,
|
||||
simplejson.MustJson([]byte(`{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "ds2"
|
||||
},
|
||||
"refId": "B"
|
||||
}`)),
|
||||
reqDTO.Queries[1],
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("returns an error when panel missing", func(t *testing.T) {
|
||||
_, err := service.BuildPublicDashboardMetricRequest(
|
||||
context.Background(),
|
||||
publicDashboard,
|
||||
publicDashboardPD,
|
||||
49,
|
||||
)
|
||||
|
||||
require.ErrorContains(t, err, "Panel not found")
|
||||
})
|
||||
|
||||
t.Run("returns an error when dashboard not public", func(t *testing.T) {
|
||||
_, err := service.BuildPublicDashboardMetricRequest(
|
||||
context.Background(),
|
||||
nonPublicDashboard,
|
||||
nonPublicDashboardPD,
|
||||
2,
|
||||
)
|
||||
require.ErrorContains(t, err, "Public dashboard not found")
|
||||
})
|
||||
}
|
||||
|
||||
func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64,
|
||||
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
|
||||
t.Helper()
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds1",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds1",
|
||||
},
|
||||
"refId": "A",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "prometheus",
|
||||
"uid": "ds2",
|
||||
},
|
||||
"refId": "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"datasource": map[string]interface{}{
|
||||
"uid": "ds3",
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": "mysql",
|
||||
"uid": "ds3",
|
||||
},
|
||||
"refId": "C",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
dash, err := dashboardStore.SaveDashboard(cmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dash)
|
||||
dash.Data.Set("id", dash.Id)
|
||||
dash.Data.Set("uid", dash.Uid)
|
||||
return dash
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.12.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.12.2. DO NOT EDIT.
|
||||
|
||||
package dashboards
|
||||
|
||||
@@ -67,27 +67,6 @@ func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *models.
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GenerateNewPublicDashboardUid provides a mock function with given fields: ctx
|
||||
func (_m *FakeDashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetDashboard provides a mock function with given fields: ctx, query
|
||||
func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
@@ -319,61 +298,6 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dash
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPublicDashboard provides a mock function with given fields: ctx, accessToken
|
||||
func (_m *FakeDashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) {
|
||||
ret := _m.Called(ctx, accessToken)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, accessToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *models.Dashboard
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) *models.Dashboard); ok {
|
||||
r1 = rf(ctx, accessToken)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*models.Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
|
||||
r2 = rf(ctx, accessToken)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
|
||||
func (_m *FakeDashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, orgId, dashboardUid)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, orgId, dashboardUid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, orgId, dashboardUid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query
|
||||
func (_m *FakeDashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error {
|
||||
ret := _m.Called(ctx, query)
|
||||
@@ -462,29 +386,6 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(cmd models.SaveDashboardC
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SavePublicDashboardConfig provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakeDashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 *models.PublicDashboard
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) *models.PublicDashboard); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.PublicDashboard)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
|
||||
r1 = rf(ctx, cmd)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UnprovisionDashboard provides a mock function with given fields: ctx, id
|
||||
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
@@ -513,20 +414,6 @@ func (_m *FakeDashboardStore) UpdateDashboardACL(ctx context.Context, uid int64,
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdatePublicDashboardConfig provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakeDashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ValidateDashboardBeforeSave provides a mock function with given fields: dashboard, overwrite
|
||||
func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
|
||||
ret := _m.Called(dashboard, overwrite)
|
||||
|
||||
Reference in New Issue
Block a user