mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
PublicDashboards: Audit table pagination (#69823)
This commit is contained in:
parent
ed5a697825
commit
ee73d41d24
@ -60,7 +60,7 @@ func (sb *SQLBuilder) AddParams(params ...interface{}) {
|
||||
sb.params = append(sb.params, params...)
|
||||
}
|
||||
|
||||
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, permission dashboards.PermissionType) {
|
||||
func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, permission dashboards.PermissionType, queryType string) {
|
||||
var (
|
||||
sql string
|
||||
params []interface{}
|
||||
@ -68,7 +68,7 @@ func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, pe
|
||||
recQryParams []interface{}
|
||||
)
|
||||
if !ac.IsDisabled(sb.cfg) {
|
||||
filterRBAC := permissions.NewAccessControlDashboardPermissionFilter(user, permission, "", sb.features, sb.recursiveQueriesAreSupported)
|
||||
filterRBAC := permissions.NewAccessControlDashboardPermissionFilter(user, permission, queryType, sb.features, sb.recursiveQueriesAreSupported)
|
||||
sql, params = filterRBAC.Where()
|
||||
recQry, recQryParams = filterRBAC.With()
|
||||
} else {
|
||||
|
@ -480,7 +480,7 @@ func getDashboards(t *testing.T, sqlStore *sqlstore.SQLStore, search Search, acl
|
||||
|
||||
var res []*dashboardResponse
|
||||
builder.Write("SELECT * FROM dashboard WHERE true")
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, search.RequiredPermission)
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, search.RequiredPermission, "")
|
||||
t.Logf("Searching for dashboards, SQL: %q\n", builder.GetSQLString())
|
||||
err = sqlStore.GetEngine().SQL(builder.GetSQLString(), builder.params...).Find(&res)
|
||||
require.NoError(t, err)
|
||||
|
@ -167,7 +167,7 @@ func (ss *sqlStore) HandleAlertsQuery(ctx context.Context, query *alertmodels.Ge
|
||||
builder.Write(")")
|
||||
}
|
||||
|
||||
builder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW)
|
||||
builder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, "")
|
||||
|
||||
builder.Write(" ORDER BY name ASC")
|
||||
|
||||
|
@ -250,7 +250,7 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed
|
||||
builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
|
||||
builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
|
||||
writeParamSelectorSQL(&builder, params...)
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW)
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
|
||||
builder.Write(` OR dashboard.id=0`)
|
||||
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil {
|
||||
return err
|
||||
@ -372,7 +372,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
|
||||
return err
|
||||
}
|
||||
if signedInUser.OrgRole != org.RoleAdmin {
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW)
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
|
||||
}
|
||||
if query.SortDirection == search.SortAlphaDesc.Name {
|
||||
builder.Write(" ORDER BY 1 DESC")
|
||||
@ -603,7 +603,7 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser *
|
||||
builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id")
|
||||
builder.Write(` WHERE lec.element_id=?`, element.ID)
|
||||
if signedInUser.OrgRole != org.RoleAdmin {
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW)
|
||||
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "")
|
||||
}
|
||||
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElementConnections); err != nil {
|
||||
return err
|
||||
|
@ -90,7 +90,24 @@ func (api *Api) RegisterAPIEndpoints() {
|
||||
// ListPublicDashboards Gets list of public dashboards by orgId
|
||||
// GET /api/dashboards/public-dashboards
|
||||
func (api *Api) ListPublicDashboards(c *contextmodel.ReqContext) response.Response {
|
||||
resp, err := api.PublicDashboardService.FindAll(c.Req.Context(), c.SignedInUser, c.OrgID)
|
||||
perPage := c.QueryInt("perpage")
|
||||
if perPage <= 0 {
|
||||
perPage = 1000
|
||||
}
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
resp, err := api.PublicDashboardService.FindAllWithPagination(c.Req.Context(), &PublicDashboardListQuery{
|
||||
OrgID: c.OrgID,
|
||||
Query: c.Query("query"),
|
||||
Page: page,
|
||||
Limit: perPage,
|
||||
User: c.SignedInUser,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return response.Err(err)
|
||||
}
|
||||
|
@ -89,19 +89,21 @@ func TestAPIFeatureFlag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIListPublicDashboard(t *testing.T) {
|
||||
successResp := []PublicDashboardListResponse{
|
||||
successResp := &PublicDashboardListResponseWithPagination{
|
||||
PublicDashboards: []*PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "1234asdfasdf",
|
||||
AccessToken: "asdfasdf",
|
||||
DashboardUid: "abc1234",
|
||||
IsEnabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
User *user.SignedInUser
|
||||
Response []PublicDashboardListResponse
|
||||
Response *PublicDashboardListResponseWithPagination
|
||||
ResponseErr error
|
||||
ExpectedHttpResponse int
|
||||
}{
|
||||
@ -131,7 +133,7 @@ func TestAPIListPublicDashboard(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
service := publicdashboards.NewFakePublicDashboardService(t)
|
||||
service.On("FindAll", mock.Anything, mock.Anything, mock.Anything).
|
||||
service.On("FindAllWithPagination", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(test.Response, test.ResponseErr).Maybe()
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
@ -143,10 +145,10 @@ func TestAPIListPublicDashboard(t *testing.T) {
|
||||
assert.Equal(t, test.ExpectedHttpResponse, response.Code)
|
||||
|
||||
if test.ExpectedHttpResponse == http.StatusOK {
|
||||
var jsonResp []PublicDashboardListResponse
|
||||
var jsonResp PublicDashboardListResponseWithPagination
|
||||
err := json.Unmarshal(response.Body.Bytes(), &jsonResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jsonResp[0].Uid, "1234asdfasdf")
|
||||
assert.Equal(t, jsonResp.PublicDashboards[0].Uid, "1234asdfasdf")
|
||||
}
|
||||
|
||||
if test.ResponseErr != nil {
|
||||
@ -155,7 +157,7 @@ func TestAPIListPublicDashboard(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Internal server error", errResp.Message)
|
||||
assert.Equal(t, "publicdashboards.internalServerError", errResp.MessageID)
|
||||
service.AssertNotCalled(t, "FindAll")
|
||||
service.AssertNotCalled(t, "FindAllWithPagination")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
annotationsService := annotationstest.NewFakeAnnotationsRepo()
|
||||
|
||||
// create public dashboard
|
||||
store := publicdashboardsStore.ProvideStore(db)
|
||||
store := publicdashboardsStore.ProvideStore(db, db.Cfg, featuremgmt.WithFeatures())
|
||||
cfg := setting.NewCfg()
|
||||
ac := acmock.New()
|
||||
ws := publicdashboardsService.ProvideServiceWrapper(store)
|
||||
|
@ -7,9 +7,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// Define the storage implementation. We're generating the mock implementation
|
||||
@ -17,6 +21,8 @@ import (
|
||||
type PublicDashboardStoreImpl struct {
|
||||
sqlStore db.DB
|
||||
log log.Logger
|
||||
cfg *setting.Cfg
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
var LogPrefix = "publicdashboards.store"
|
||||
@ -26,25 +32,54 @@ var LogPrefix = "publicdashboards.store"
|
||||
var _ publicdashboards.Store = (*PublicDashboardStoreImpl)(nil)
|
||||
|
||||
// Factory used by wire to dependency injection
|
||||
func ProvideStore(sqlStore db.DB) *PublicDashboardStoreImpl {
|
||||
func ProvideStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) *PublicDashboardStoreImpl {
|
||||
return &PublicDashboardStoreImpl{
|
||||
sqlStore: sqlStore,
|
||||
log: log.New(LogPrefix),
|
||||
cfg: cfg,
|
||||
features: features,
|
||||
}
|
||||
}
|
||||
|
||||
// FindAll Returns a list of public dashboards by orgId
|
||||
func (d *PublicDashboardStoreImpl) FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error) {
|
||||
resp := make([]PublicDashboardListResponse, 0)
|
||||
// FindAllWithPagination Returns a list of public dashboards by orgId, based on permissions and with pagination
|
||||
func (d *PublicDashboardStoreImpl) FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error) {
|
||||
resp := &PublicDashboardListResponseWithPagination{
|
||||
PublicDashboards: make([]*PublicDashboardListResponse, 0),
|
||||
TotalCount: 0,
|
||||
}
|
||||
|
||||
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
sess.Table("dashboard_public").Select(
|
||||
"dashboard_public.uid, dashboard_public.access_token, dashboard.uid as dashboard_uid, dashboard_public.is_enabled, dashboard.title").
|
||||
Join("LEFT", "dashboard", "dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id").
|
||||
Where("dashboard_public.org_id = ?", orgId).
|
||||
OrderBy(" dashboard.title IS NULL, dashboard.title ASC")
|
||||
recursiveQueriesAreSupported, err := d.sqlStore.RecursiveQueriesAreSupported()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := sess.Find(&resp)
|
||||
pubdashBuilder := db.NewSqlBuilder(d.cfg, d.features, d.sqlStore.GetDialect(), recursiveQueriesAreSupported)
|
||||
pubdashBuilder.Write("SELECT dashboard_public.uid, dashboard_public.access_token, dashboard.uid as dashboard_uid, dashboard_public.is_enabled, dashboard.title")
|
||||
pubdashBuilder.Write(" FROM dashboard_public")
|
||||
pubdashBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id")
|
||||
pubdashBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID)
|
||||
if query.User.OrgRole != org.RoleAdmin {
|
||||
pubdashBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard)
|
||||
}
|
||||
pubdashBuilder.Write(" ORDER BY dashboard.title")
|
||||
pubdashBuilder.Write(d.sqlStore.GetDialect().LimitOffset(int64(query.Limit), int64(query.Offset)))
|
||||
|
||||
counterBuilder := db.NewSqlBuilder(d.cfg, d.features, d.sqlStore.GetDialect(), recursiveQueriesAreSupported)
|
||||
counterBuilder.Write("SELECT COUNT(*)")
|
||||
counterBuilder.Write(" FROM dashboard_public")
|
||||
counterBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id")
|
||||
counterBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID)
|
||||
if query.User.OrgRole != org.RoleAdmin {
|
||||
counterBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard)
|
||||
}
|
||||
|
||||
err = d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
err := sess.SQL(pubdashBuilder.GetSQLString(), pubdashBuilder.GetParams()...).Find(&resp.PublicDashboards)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.SQL(counterBuilder.GetSQLString(), counterBuilder.GetParams()...).Get(&resp.TotalCount)
|
||||
return err
|
||||
})
|
||||
|
||||
|
@ -2,18 +2,24 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -34,38 +40,122 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore, cfg := db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := ProvideStore(sqlStore)
|
||||
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var cfg *setting.Cfg
|
||||
|
||||
var aDash *dashboards.Dashboard
|
||||
var bDash *dashboards.Dashboard
|
||||
var cDash *dashboards.Dashboard
|
||||
|
||||
var aPublicDash *PublicDashboard
|
||||
var bPublicDash *PublicDashboard
|
||||
var cPublicDash *PublicDashboard
|
||||
|
||||
var orgId int64 = 1
|
||||
|
||||
bDash := insertTestDashboard(t, dashboardStore, "b", orgId, 0, true)
|
||||
aDash := insertTestDashboard(t, dashboardStore, "a", orgId, 0, true)
|
||||
cDash := insertTestDashboard(t, dashboardStore, "c", orgId, 0, true)
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
|
||||
bDash = insertTestDashboard(t, dashboardStore, "b", orgId, 0, false)
|
||||
aDash = insertTestDashboard(t, dashboardStore, "a", orgId, 0, false)
|
||||
cDash = insertTestDashboard(t, dashboardStore, "c", orgId, 0, false)
|
||||
|
||||
// these are in order of how they should be returned from ListPUblicDashboards
|
||||
a := insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false, PublicShareType)
|
||||
b := insertPublicDashboard(t, publicdashboardStore, bDash.UID, orgId, true, PublicShareType)
|
||||
c := insertPublicDashboard(t, publicdashboardStore, cDash.UID, orgId, true, PublicShareType)
|
||||
aPublicDash = insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false, PublicShareType)
|
||||
bPublicDash = insertPublicDashboard(t, publicdashboardStore, bDash.UID, orgId, true, PublicShareType)
|
||||
cPublicDash = insertPublicDashboard(t, publicdashboardStore, cDash.UID, orgId, true, PublicShareType)
|
||||
}
|
||||
|
||||
// this is case that can happen as of now, however, postgres and mysql sort
|
||||
// null in the exact opposite fashion and there is no shared syntax to sort
|
||||
// nulls in the same way in all 3 db's.
|
||||
//d := insertPublicDashboard(t, publicdashboardStore, "missing", orgId, false)
|
||||
t.Run("FindAllWithPagination will return dashboard list based on orgId with pagination", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
// should not be included in response
|
||||
_ = insertPublicDashboard(t, publicdashboardStore, "wrongOrgId", 777, false, PublicShareType)
|
||||
|
||||
resp, err := publicdashboardStore.FindAll(context.Background(), orgId)
|
||||
permissions := []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", aDash.UID)},
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", bDash.UID)},
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)},
|
||||
}
|
||||
|
||||
err := insertPermissions(sqlStore, orgId, "viewer", permissions)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp, 3)
|
||||
assert.Equal(t, resp[0].Uid, a.Uid)
|
||||
assert.Equal(t, resp[1].Uid, b.Uid)
|
||||
assert.Equal(t, resp[2].Uid, c.Uid)
|
||||
query := &PublicDashboardListQuery{
|
||||
User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}},
|
||||
OrgID: orgId,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
Offset: 0,
|
||||
}
|
||||
resp, err := publicdashboardStore.FindAllWithPagination(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.PublicDashboards, 3)
|
||||
assert.Equal(t, resp.PublicDashboards[0].Uid, aPublicDash.Uid)
|
||||
assert.Equal(t, resp.PublicDashboards[1].Uid, bPublicDash.Uid)
|
||||
assert.Equal(t, resp.PublicDashboards[2].Uid, cPublicDash.Uid)
|
||||
assert.Equal(t, resp.TotalCount, int64(3))
|
||||
})
|
||||
|
||||
t.Run("FindAllWithPagination will return dashboard list based on read permissions with pagination", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
permissions := []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", aDash.UID)},
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)},
|
||||
}
|
||||
|
||||
err := insertPermissions(sqlStore, orgId, "viewer", permissions)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := &PublicDashboardListQuery{
|
||||
User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}},
|
||||
OrgID: orgId,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
Offset: 0,
|
||||
}
|
||||
resp, err := publicdashboardStore.FindAllWithPagination(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.PublicDashboards, 2)
|
||||
assert.Equal(t, resp.PublicDashboards[0].Uid, aPublicDash.Uid)
|
||||
assert.Equal(t, resp.PublicDashboards[1].Uid, cPublicDash.Uid)
|
||||
assert.Equal(t, resp.TotalCount, int64(2))
|
||||
})
|
||||
|
||||
t.Run("FindAllWithPagination will return empty dashboard list based on read permissions with pagination", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
permissions := []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:another-dashboard-uid"},
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:another-dashboard-2-uid"},
|
||||
}
|
||||
|
||||
err := insertPermissions(sqlStore, orgId, "viewer", permissions)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := &PublicDashboardListQuery{
|
||||
User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}},
|
||||
OrgID: orgId,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
Offset: 0,
|
||||
}
|
||||
resp, err := publicdashboardStore.FindAllWithPagination(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.PublicDashboards, 0)
|
||||
assert.Equal(t, resp.TotalCount, int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationFindDashboard(t *testing.T) {
|
||||
@ -84,7 +174,7 @@ func TestIntegrationFindDashboard(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
@ -114,7 +204,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
t.Run("ExistsEnabledByAccessToken will return true when at least one public dashboard has a matching access token", func(t *testing.T) {
|
||||
@ -187,7 +277,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
@ -252,7 +342,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
@ -319,7 +409,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
|
||||
@ -389,7 +479,7 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true)
|
||||
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, false, PublicShareType)
|
||||
@ -468,7 +558,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, true)
|
||||
}
|
||||
@ -572,7 +662,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
t.Run("GetOrgIdByAccessToken will OrgId when enabled", func(t *testing.T) {
|
||||
@ -644,7 +734,7 @@ func TestIntegrationDelete(t *testing.T) {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType)
|
||||
}
|
||||
@ -675,7 +765,7 @@ func TestGetDashboardByFolder(t *testing.T) {
|
||||
t.Run("returns nil when dashboard is not a folder", func(t *testing.T) {
|
||||
sqlStore, _ := db.InitTestDBwithCfg(t)
|
||||
dashboard := &dashboards.Dashboard{IsFolder: false}
|
||||
store := ProvideStore(sqlStore)
|
||||
store := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
pubdashes, err := store.FindByDashboardFolder(context.Background(), dashboard)
|
||||
|
||||
require.NoError(t, err)
|
||||
@ -684,7 +774,7 @@ func TestGetDashboardByFolder(t *testing.T) {
|
||||
|
||||
t.Run("returns nil when dashboard is nil", func(t *testing.T) {
|
||||
sqlStore, _ := db.InitTestDBwithCfg(t)
|
||||
store := ProvideStore(sqlStore)
|
||||
store := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
pubdashes, err := store.FindByDashboardFolder(context.Background(), nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
@ -696,7 +786,7 @@ func TestGetDashboardByFolder(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
pubdashStore := ProvideStore(sqlStore)
|
||||
pubdashStore := ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, true, PublicShareType)
|
||||
pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true, PublicShareType)
|
||||
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, true, PublicShareType)
|
||||
@ -729,7 +819,7 @@ func TestGetMetrics(t *testing.T) {
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, false)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, false)
|
||||
savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, 0, false)
|
||||
@ -827,3 +917,50 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt
|
||||
|
||||
return pubdash
|
||||
}
|
||||
|
||||
func insertPermissions(sqlStore *sqlstore.SQLStore, orgId int64, role string, permissions []accesscontrol.Permission) error {
|
||||
return sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
newRole := &accesscontrol.Role{
|
||||
OrgID: orgId,
|
||||
UID: fmt.Sprintf("basic_%s", role),
|
||||
Name: fmt.Sprintf("basic:%s", role),
|
||||
Updated: time.Now(),
|
||||
Created: time.Now(),
|
||||
}
|
||||
_, err := sess.Insert(newRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Insert(accesscontrol.BuiltinRole{
|
||||
OrgID: orgId,
|
||||
RoleID: newRole.ID,
|
||||
Role: strings.ToUpper(role[:1]) + role[1:],
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range permissions {
|
||||
permissions[i].RoleID = newRole.ID
|
||||
permissions[i].Created = time.Now()
|
||||
permissions[i].Updated = time.Now()
|
||||
}
|
||||
|
||||
_, err = sess.InsertMulti(&permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Insert(accesscontrol.UserRole{
|
||||
OrgID: orgId,
|
||||
RoleID: newRole.ID,
|
||||
UserID: 1,
|
||||
Created: time.Now(),
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/kinds/dashboard"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
// PublicDashboardErr represents a dashboard error.
|
||||
@ -103,6 +104,22 @@ func (pd PublicDashboard) TableName() string {
|
||||
return "dashboard_public"
|
||||
}
|
||||
|
||||
type PublicDashboardListQuery struct {
|
||||
OrgID int64
|
||||
Query string
|
||||
Page int
|
||||
Limit int
|
||||
Offset int
|
||||
User *user.SignedInUser
|
||||
}
|
||||
|
||||
type PublicDashboardListResponseWithPagination struct {
|
||||
PublicDashboards []*PublicDashboardListResponse `json:"publicDashboards"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
}
|
||||
|
||||
type PublicDashboardListResponse struct {
|
||||
Uid string `json:"uid" xorm:"uid"`
|
||||
AccessToken string `json:"accessToken" xorm:"access_token"`
|
||||
|
@ -139,22 +139,22 @@ func (_m *FakePublicDashboardService) Find(ctx context.Context, uid string) (*mo
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindAll provides a mock function with given fields: ctx, u, orgId
|
||||
func (_m *FakePublicDashboardService) FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]models.PublicDashboardListResponse, error) {
|
||||
ret := _m.Called(ctx, u, orgId)
|
||||
// FindAllWithPagination provides a mock function with given fields: ctx, query
|
||||
func (_m *FakePublicDashboardService) FindAllWithPagination(ctx context.Context, query *models.PublicDashboardListQuery) (*models.PublicDashboardListResponseWithPagination, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []models.PublicDashboardListResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, int64) []models.PublicDashboardListResponse); ok {
|
||||
r0 = rf(ctx, u, orgId)
|
||||
var r0 *models.PublicDashboardListResponseWithPagination
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.PublicDashboardListQuery) *models.PublicDashboardListResponseWithPagination); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]models.PublicDashboardListResponse)
|
||||
r0 = ret.Get(0).(*models.PublicDashboardListResponseWithPagination)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, int64) error); ok {
|
||||
r1 = rf(ctx, u, orgId)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.PublicDashboardListQuery) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
@ -123,22 +123,22 @@ func (_m *FakePublicDashboardStore) Find(ctx context.Context, uid string) (*mode
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindAll provides a mock function with given fields: ctx, orgId
|
||||
func (_m *FakePublicDashboardStore) FindAll(ctx context.Context, orgId int64) ([]models.PublicDashboardListResponse, error) {
|
||||
ret := _m.Called(ctx, orgId)
|
||||
// FindAllWithPagination provides a mock function with given fields: ctx, query
|
||||
func (_m *FakePublicDashboardStore) FindAllWithPagination(ctx context.Context, query *models.PublicDashboardListQuery) (*models.PublicDashboardListResponseWithPagination, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 []models.PublicDashboardListResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) []models.PublicDashboardListResponse); ok {
|
||||
r0 = rf(ctx, orgId)
|
||||
var r0 *models.PublicDashboardListResponseWithPagination
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.PublicDashboardListQuery) *models.PublicDashboardListResponseWithPagination); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]models.PublicDashboardListResponse)
|
||||
r0 = ret.Get(0).(*models.PublicDashboardListResponseWithPagination)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, orgId)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.PublicDashboardListQuery) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type Service interface {
|
||||
FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||
FindAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error)
|
||||
FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error)
|
||||
FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error)
|
||||
FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error)
|
||||
Find(ctx context.Context, uid string) (*PublicDashboard, error)
|
||||
Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
|
||||
Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
|
||||
@ -52,7 +52,7 @@ type Store interface {
|
||||
FindByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, error)
|
||||
FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||
FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error)
|
||||
FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error)
|
||||
FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error)
|
||||
Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
|
||||
Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
|
||||
Delete(ctx context.Context, uid string) (int64, error)
|
||||
|
@ -663,7 +663,7 @@ func TestGetQueryDataResponse(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
fakeQueryService := &query.FakeQueryService{}
|
||||
fakeQueryService.On("QueryData", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&backend.QueryDataResponse{}, nil)
|
||||
@ -1109,7 +1109,7 @@ func TestGetMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
publicDashboard := &PublicDashboard{
|
||||
Uid: "1",
|
||||
@ -1194,7 +1194,7 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
@ -276,14 +276,18 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context.
|
||||
return "", ErrInternalServerError.Errorf("failed to generate a unique accessToken for public dashboard")
|
||||
}
|
||||
|
||||
// FindAll Returns a list of public dashboards by orgId
|
||||
func (pd *PublicDashboardServiceImpl) FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error) {
|
||||
publicDashboards, err := pd.store.FindAll(ctx, orgId)
|
||||
// FindAllWithPagination Returns a list of public dashboards by orgId, based on permissions and with pagination
|
||||
func (pd *PublicDashboardServiceImpl) FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error) {
|
||||
query.Offset = query.Limit * (query.Page - 1)
|
||||
resp, err := pd.store.FindAllWithPagination(ctx, query)
|
||||
if err != nil {
|
||||
return nil, ErrInternalServerError.Errorf("FindAll: %w", err)
|
||||
return nil, ErrInternalServerError.Errorf("FindAllWithPagination: %w", err)
|
||||
}
|
||||
|
||||
return pd.filterDashboardsByPermissions(ctx, u, publicDashboards)
|
||||
resp.Page = query.Page
|
||||
resp.PerPage = query.Limit
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (pd *PublicDashboardServiceImpl) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) {
|
||||
@ -371,25 +375,6 @@ func (pd *PublicDashboardServiceImpl) logIsEnabledChanged(existingPubdash *Publi
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out dashboards that user does not have read access to
|
||||
func (pd *PublicDashboardServiceImpl) filterDashboardsByPermissions(ctx context.Context, u *user.SignedInUser, publicDashboards []PublicDashboardListResponse) ([]PublicDashboardListResponse, error) {
|
||||
result := make([]PublicDashboardListResponse, 0)
|
||||
|
||||
for i := range publicDashboards {
|
||||
hasAccess, err := pd.ac.Evaluate(ctx, u, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(publicDashboards[i].DashboardUid)))
|
||||
// If original dashboard does not exist, the public dashboard is an orphan. We want to list it anyway
|
||||
if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
return nil, ErrInternalServerError.Errorf("filterDashboardsByPermissions: error evaluating permissions %w", err)
|
||||
}
|
||||
|
||||
// If user has access to the original dashboard or the dashboard does not exist, add the pubdash to the result
|
||||
if hasAccess || errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
result = append(result, publicDashboards[i])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Checks to see if PublicDashboard.ExistsEnabledByDashboardUid is true on create or changed on update
|
||||
func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard) bool {
|
||||
// creating dashboard, enabled true
|
||||
|
@ -195,7 +195,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
|
||||
@ -285,7 +285,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
|
||||
@ -325,7 +325,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
|
||||
@ -359,7 +359,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
templateVars := make([]map[string]interface{}, 1)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars, nil)
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
@ -474,7 +474,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
|
||||
@ -519,7 +519,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -588,7 +588,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
@ -686,7 +686,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
|
||||
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -915,186 +915,102 @@ func TestDashboardEnabledChanged(t *testing.T) {
|
||||
func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
u *user.SignedInUser
|
||||
orgId int64
|
||||
query *PublicDashboardListQuery
|
||||
}
|
||||
|
||||
type mockResponse struct {
|
||||
PublicDashboardListResponseWithPagination *PublicDashboardListResponseWithPagination
|
||||
Err error
|
||||
}
|
||||
|
||||
mockedDashboards := []*PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "0GwW7mgVk",
|
||||
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "0S6TmO67z",
|
||||
Title: "my zero dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "1GwW7mgVk",
|
||||
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "1S6TmO67z",
|
||||
Title: "my first dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "2GwW7mgVk",
|
||||
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "2S6TmO67z",
|
||||
Title: "my second dashboard",
|
||||
IsEnabled: false,
|
||||
},
|
||||
{
|
||||
Uid: "9GwW7mgVk",
|
||||
AccessToken: "deletedashboardaccesstoken",
|
||||
DashboardUid: "9S6TmO67z",
|
||||
Title: "",
|
||||
IsEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
evaluateFunc func(c context.Context, u *user.SignedInUser, e accesscontrol.Evaluator) (bool, error)
|
||||
want []PublicDashboardListResponse
|
||||
want *PublicDashboardListResponseWithPagination
|
||||
mockResponse *mockResponse
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "should return empty list when user does not have permissions to read any dashboard",
|
||||
name: "should return correct pagination response",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
u: &user.SignedInUser{OrgID: 1},
|
||||
orgId: 1,
|
||||
},
|
||||
want: []PublicDashboardListResponse{},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "should return all dashboards when has permissions",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
u: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"dashboards:read": {
|
||||
"dashboards:uid:0S6TmO67z", "dashboards:uid:1S6TmO67z", "dashboards:uid:2S6TmO67z", "dashboards:uid:9S6TmO67z",
|
||||
}}},
|
||||
},
|
||||
orgId: 1,
|
||||
},
|
||||
want: []PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "0GwW7mgVk",
|
||||
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "0S6TmO67z",
|
||||
Title: "my zero dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "1GwW7mgVk",
|
||||
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "1S6TmO67z",
|
||||
Title: "my first dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "2GwW7mgVk",
|
||||
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "2S6TmO67z",
|
||||
Title: "my second dashboard",
|
||||
IsEnabled: false,
|
||||
},
|
||||
{
|
||||
Uid: "9GwW7mgVk",
|
||||
AccessToken: "deletedashboardaccesstoken",
|
||||
DashboardUid: "9S6TmO67z",
|
||||
Title: "",
|
||||
IsEnabled: true,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "should return only dashboards with permissions",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
u: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
query: &PublicDashboardListQuery{
|
||||
User: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"dashboards:read": {"dashboards:uid:0S6TmO67z"}}},
|
||||
},
|
||||
orgId: 1,
|
||||
OrgID: 1,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
},
|
||||
want: []PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "0GwW7mgVk",
|
||||
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "0S6TmO67z",
|
||||
Title: "my zero dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
mockResponse: &mockResponse{
|
||||
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
||||
TotalCount: int64(len(mockedDashboards)),
|
||||
PublicDashboards: mockedDashboards,
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
want: &PublicDashboardListResponseWithPagination{
|
||||
Page: 1,
|
||||
PerPage: 50,
|
||||
TotalCount: int64(len(mockedDashboards)),
|
||||
PublicDashboards: mockedDashboards,
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "should return orphaned public dashboards",
|
||||
name: "should return error when store returns error",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
u: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
query: &PublicDashboardListQuery{
|
||||
User: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"dashboards:read": {"dashboards:uid:0S6TmO67z"}}},
|
||||
},
|
||||
orgId: 1,
|
||||
},
|
||||
evaluateFunc: func(c context.Context, u *user.SignedInUser, e accesscontrol.Evaluator) (bool, error) {
|
||||
return false, dashboards.ErrDashboardNotFound
|
||||
},
|
||||
want: []PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "0GwW7mgVk",
|
||||
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "0S6TmO67z",
|
||||
Title: "my zero dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "1GwW7mgVk",
|
||||
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "1S6TmO67z",
|
||||
Title: "my first dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "2GwW7mgVk",
|
||||
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "2S6TmO67z",
|
||||
Title: "my second dashboard",
|
||||
IsEnabled: false,
|
||||
},
|
||||
{
|
||||
Uid: "9GwW7mgVk",
|
||||
AccessToken: "deletedashboardaccesstoken",
|
||||
DashboardUid: "9S6TmO67z",
|
||||
Title: "",
|
||||
IsEnabled: true,
|
||||
OrgID: 1,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "errors different than not data found should be returned",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
u: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"dashboards:read": {"dashboards:uid:0S6TmO67z"}}},
|
||||
},
|
||||
orgId: 1,
|
||||
},
|
||||
evaluateFunc: func(c context.Context, u *user.SignedInUser, e accesscontrol.Evaluator) (bool, error) {
|
||||
return false, dashboards.ErrDashboardCorrupt
|
||||
mockResponse: &mockResponse{
|
||||
PublicDashboardListResponseWithPagination: nil,
|
||||
Err: errors.New("an err"),
|
||||
},
|
||||
want: nil,
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
|
||||
mockedDashboards := []PublicDashboardListResponse{
|
||||
{
|
||||
Uid: "0GwW7mgVk",
|
||||
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "0S6TmO67z",
|
||||
Title: "my zero dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "1GwW7mgVk",
|
||||
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "1S6TmO67z",
|
||||
Title: "my first dashboard",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
Uid: "2GwW7mgVk",
|
||||
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
||||
DashboardUid: "2S6TmO67z",
|
||||
Title: "my second dashboard",
|
||||
IsEnabled: false,
|
||||
},
|
||||
{
|
||||
Uid: "9GwW7mgVk",
|
||||
AccessToken: "deletedashboardaccesstoken",
|
||||
DashboardUid: "9S6TmO67z",
|
||||
Title: "",
|
||||
IsEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
store := NewFakePublicDashboardStore(t)
|
||||
store.On("FindAll", mock.Anything, mock.Anything).
|
||||
Return(mockedDashboards, nil)
|
||||
|
||||
ac := tests.SetupMockAccesscontrol(t,
|
||||
func(c context.Context, siu *user.SignedInUser, _ accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
return []accesscontrol.Permission{}, nil
|
||||
@ -1102,21 +1018,23 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
|
||||
false,
|
||||
)
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
store := NewFakePublicDashboardStore(t)
|
||||
store.On("FindAllWithPagination", mock.Anything, mock.Anything).
|
||||
Return(tt.mockResponse.PublicDashboardListResponseWithPagination, tt.mockResponse.Err)
|
||||
|
||||
pd := &PublicDashboardServiceImpl{
|
||||
log: log.New("test.logger"),
|
||||
store: store,
|
||||
ac: ac,
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ac.EvaluateFunc = tt.evaluateFunc
|
||||
|
||||
got, err := pd.FindAll(tt.args.ctx, tt.args.u, tt.args.orgId)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("FindAll(%v, %v, %v)", tt.args.ctx, tt.args.u, tt.args.orgId)) {
|
||||
got, err := pd.FindAllWithPagination(tt.args.ctx, tt.args.query)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("FindAllWithPagination(%v, %v)", tt.args.ctx, tt.args.query)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "FindAll(%v, %v, %v)", tt.args.ctx, tt.args.u, tt.args.orgId)
|
||||
assert.Equalf(t, tt.want, got, "FindAllWithPagination(%v, %v)", tt.args.ctx, tt.args.query)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,10 @@ import {
|
||||
SessionUser,
|
||||
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { ListPublicDashboardResponse } from 'app/features/manage-dashboards/types';
|
||||
import {
|
||||
PublicDashboardListWithPagination,
|
||||
PublicDashboardListWithPaginationResponse,
|
||||
} from 'app/features/manage-dashboards/types';
|
||||
|
||||
type ReqOptions = {
|
||||
manageError?: (err: unknown) => { error: unknown };
|
||||
@ -137,9 +140,13 @@ export const publicDashboardApi = createApi({
|
||||
}),
|
||||
providesTags: (result, _, email) => [{ type: 'ActiveUserDashboards', id: email }],
|
||||
}),
|
||||
listPublicDashboards: builder.query<ListPublicDashboardResponse[], void>({
|
||||
query: () => ({
|
||||
url: '/dashboards/public-dashboards',
|
||||
listPublicDashboards: builder.query<PublicDashboardListWithPagination, number | void>({
|
||||
query: (page = 1) => ({
|
||||
url: `/dashboards/public-dashboards?page=${page}&perpage=8`,
|
||||
}),
|
||||
transformResponse: (response: PublicDashboardListWithPaginationResponse) => ({
|
||||
...response,
|
||||
totalPages: Math.ceil(response.totalCount / response.perPage),
|
||||
}),
|
||||
providesTags: ['AuditTablePublicDashboard'],
|
||||
}),
|
||||
|
@ -11,11 +11,11 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
|
||||
import { ListPublicDashboardResponse } from '../../types';
|
||||
import { PublicDashboardListResponse, PublicDashboardListWithPaginationResponse } from '../../types';
|
||||
|
||||
import { PublicDashboardListTable } from './PublicDashboardListTable';
|
||||
|
||||
const publicDashboardListResponse: ListPublicDashboardResponse[] = [
|
||||
const publicDashboardListResponse: PublicDashboardListResponse[] = [
|
||||
{
|
||||
uid: 'SdZwuCZVz',
|
||||
accessToken: 'beeaf92f6ab3467f80b2be922c7741ab',
|
||||
@ -32,7 +32,7 @@ const publicDashboardListResponse: ListPublicDashboardResponse[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const orphanedDashboardListResponse: ListPublicDashboardResponse[] = [
|
||||
const orphanedDashboardListResponse: PublicDashboardListResponse[] = [
|
||||
{
|
||||
uid: 'SdZwuCZVz2',
|
||||
accessToken: 'beeaf92f6ab3467f80b2be922c7741ab',
|
||||
@ -49,9 +49,15 @@ const orphanedDashboardListResponse: ListPublicDashboardResponse[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const paginationResponse: Omit<PublicDashboardListWithPaginationResponse, 'publicDashboards'> = {
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
totalCount: 50,
|
||||
};
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('/api/dashboards/public-dashboards', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(publicDashboardListResponse))
|
||||
res(ctx.status(200), ctx.json({ ...paginationResponse, publicDashboards: publicDashboardListResponse }))
|
||||
),
|
||||
rest.delete('/api/dashboards/uid/:dashboardUid/public-dashboards/:uid', (_, res, ctx) => res(ctx.status(200)))
|
||||
);
|
||||
@ -104,9 +110,16 @@ describe('Show table', () => {
|
||||
expect(screen.getAllByRole('listitem')).toHaveLength(publicDashboardListResponse.length);
|
||||
});
|
||||
it('renders empty list', async () => {
|
||||
const emptyListRS: PublicDashboardListWithPaginationResponse = {
|
||||
publicDashboards: [],
|
||||
totalCount: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
};
|
||||
|
||||
server.use(
|
||||
rest.get('/api/dashboards/public-dashboards', (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([]));
|
||||
return res(ctx.status(200), ctx.json(emptyListRS));
|
||||
})
|
||||
);
|
||||
|
||||
@ -149,7 +162,10 @@ describe('Delete public dashboard', () => {
|
||||
|
||||
describe('Orphaned public dashboard', () => {
|
||||
it('renders orphaned and non orphaned public dashboards items correctly', async () => {
|
||||
const response = [...publicDashboardListResponse, ...orphanedDashboardListResponse];
|
||||
const response: PublicDashboardListWithPaginationResponse = {
|
||||
...paginationResponse,
|
||||
publicDashboards: [...publicDashboardListResponse, ...orphanedDashboardListResponse],
|
||||
};
|
||||
server.use(
|
||||
rest.get('/api/dashboards/public-dashboards', (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
@ -158,13 +174,13 @@ describe('Orphaned public dashboard', () => {
|
||||
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
|
||||
|
||||
await renderPublicDashboardTable(true);
|
||||
response.forEach((pd, idx) => {
|
||||
response.publicDashboards.forEach((pd, idx) => {
|
||||
renderPublicDashboardItemCorrectly(pd, idx, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const renderPublicDashboardItemCorrectly = (pd: ListPublicDashboardResponse, idx: number, hasWriteAccess: boolean) => {
|
||||
const renderPublicDashboardItemCorrectly = (pd: PublicDashboardListResponse, idx: number, hasWriteAccess: boolean) => {
|
||||
const isOrphaned = !pd.dashboardUid;
|
||||
|
||||
const cardItems = screen.getAllByRole('listitem');
|
||||
|
@ -1,11 +1,22 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMedia } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { LinkButton, useStyles2, Spinner, Card, useTheme2, Tooltip, Icon, Switch } from '@grafana/ui/src';
|
||||
import {
|
||||
LinkButton,
|
||||
useStyles2,
|
||||
Spinner,
|
||||
Card,
|
||||
useTheme2,
|
||||
Tooltip,
|
||||
Icon,
|
||||
Switch,
|
||||
Pagination,
|
||||
HorizontalGroup,
|
||||
} from '@grafana/ui/src';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import {
|
||||
@ -19,11 +30,11 @@ import {
|
||||
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { ListPublicDashboardResponse } from '../../types';
|
||||
import { PublicDashboardListResponse } from '../../types';
|
||||
|
||||
import { DeletePublicDashboardButton } from './DeletePublicDashboardButton';
|
||||
|
||||
const PublicDashboardCard = ({ pd }: { pd: ListPublicDashboardResponse }) => {
|
||||
const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const theme = useTheme2();
|
||||
const isMobile = useMedia(`(max-width: ${theme.breakpoints.values.sm}px)`);
|
||||
@ -34,7 +45,7 @@ const PublicDashboardCard = ({ pd }: { pd: ListPublicDashboardResponse }) => {
|
||||
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
|
||||
const isOrphaned = !pd.dashboardUid;
|
||||
|
||||
const onTogglePause = (pd: ListPublicDashboardResponse, isPaused: boolean) => {
|
||||
const onTogglePause = (pd: PublicDashboardListResponse, isPaused: boolean) => {
|
||||
const req = {
|
||||
dashboard: { uid: pd.dashboardUid },
|
||||
payload: {
|
||||
@ -118,20 +129,33 @@ const PublicDashboardCard = ({ pd }: { pd: ListPublicDashboardResponse }) => {
|
||||
};
|
||||
|
||||
export const PublicDashboardListTable = () => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const { data: publicDashboards, isLoading, isFetching } = useListPublicDashboardsQuery();
|
||||
const styles = useStyles2(getStyles);
|
||||
const { data: paginatedPublicDashboards, isLoading, isFetching, isError } = useListPublicDashboardsQuery(page);
|
||||
|
||||
return (
|
||||
<Page navId="dashboards/public" actions={isFetching && <Spinner />}>
|
||||
<Page.Contents isLoading={isLoading}>
|
||||
{!isLoading && !isError && !!paginatedPublicDashboards && (
|
||||
<div>
|
||||
<ul className={styles.list}>
|
||||
{publicDashboards?.map((pd: ListPublicDashboardResponse) => (
|
||||
{paginatedPublicDashboards.publicDashboards.map((pd: PublicDashboardListResponse) => (
|
||||
<li key={pd.uid}>
|
||||
<PublicDashboardCard pd={pd} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<Pagination
|
||||
onNavigate={setPage}
|
||||
currentPage={paginatedPublicDashboards.page}
|
||||
numberOfPages={paginatedPublicDashboards.totalPages}
|
||||
hideWhenSinglePage
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
)}
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
@ -140,6 +164,7 @@ export const PublicDashboardListTable = () => {
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
list: css`
|
||||
list-style-type: none;
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
card: css`
|
||||
${theme.breakpoints.up('sm')} {
|
||||
|
@ -18,10 +18,21 @@ export type DeleteDashboardResponse = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export interface ListPublicDashboardResponse {
|
||||
export interface PublicDashboardListWithPaginationResponse {
|
||||
publicDashboards: PublicDashboardListResponse[];
|
||||
page: number;
|
||||
perPage: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface PublicDashboardListResponse {
|
||||
uid: string;
|
||||
accessToken: string;
|
||||
dashboardUid: string;
|
||||
title: string;
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface PublicDashboardListWithPagination extends PublicDashboardListWithPaginationResponse {
|
||||
totalPages: number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user