mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Folders: Show dashboards and folders with directly assigned permissions in "Shared" folder (#78465)
* Folders: Show folders user has access to at the root level * Refactor * Refactor * Hide parent folders user has no access to * Skip expensive computation if possible * Fix tests * Fix potential nil access * Fix duplicated folders * Fix linter error * Fix querying folders if no managed permissions set * Update benchmark * Add special shared with me folder and fetch available non-root folders on demand * Fix parents query * Improve db query for folders * Reset benchmark changes * Fix permissions for shared with me folder * Simplify dedup * Add option to include shared folder permission to user's permissions * Fix nil UID * Remove duplicated folders from shared list * Folders: Fix fetching empty folder * Nested folders: Show dashboards with directly assigned permissions * Fix slow dashboards fetch * Refactor * Fix cycle dependencies * Move shared folder to models * Fix shared folder links * Refactor * Use feature flag for permissions * Use feature flag * Review comments * Expose shared folder UID through frontend settings * Add frontend type for sharedWithMeFolderUID option * Refactor: apply review suggestions * Fix parent uid for shared folder * Fix listing shared dashboards for users with access to all folders * Prevent creating folder with "shared" UID * Add tests for shared folders * Add test for shared dashboards * Fix linter * Add metrics for shared with me folder * Add metrics for shared with me dashboards * Fix tests * Tests: add metrics as a dependency * Fix access control metadata for shared with me folder * Use constant for shared with me * Optimize parent folders access check, fetch all folders in one query. * Use labels for metrics
This commit is contained in:
parent
647f576359
commit
959ebf82da
@ -221,6 +221,7 @@ export interface GrafanaConfig {
|
|||||||
rudderstackConfigUrl: string | undefined;
|
rudderstackConfigUrl: string | undefined;
|
||||||
rudderstackIntegrationsUrl: string | undefined;
|
rudderstackIntegrationsUrl: string | undefined;
|
||||||
sqlConnectionLimits: SqlConnectionLimits;
|
sqlConnectionLimits: SqlConnectionLimits;
|
||||||
|
sharedWithMeFolderUID?: string;
|
||||||
|
|
||||||
// The namespace to use for kubernetes apiserver requests
|
// The namespace to use for kubernetes apiserver requests
|
||||||
namespace: string;
|
namespace: string;
|
||||||
|
@ -838,19 +838,19 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
|||||||
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
|
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
|
||||||
|
|
||||||
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()),
|
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||||
cfg, dashboardStore, folderStore, db.InitTestDB(t), features)
|
cfg, dashboardStore, folderStore, db.InitTestDB(t), features, nil)
|
||||||
|
|
||||||
if dashboardService == nil {
|
if dashboardService == nil {
|
||||||
dashboardService, err = service.ProvideDashboardServiceImpl(
|
dashboardService, err = service.ProvideDashboardServiceImpl(
|
||||||
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
|
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
|
||||||
ac, folderSvc,
|
ac, folderSvc, nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
|
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
|
||||||
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
|
cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions,
|
||||||
ac, folderSvc,
|
ac, folderSvc, nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -220,6 +220,7 @@ type FrontendSettingsDTO struct {
|
|||||||
SamlEnabled bool `json:"samlEnabled"`
|
SamlEnabled bool `json:"samlEnabled"`
|
||||||
SamlName string `json:"samlName"`
|
SamlName string `json:"samlName"`
|
||||||
TokenExpirationDayLimit int `json:"tokenExpirationDayLimit"`
|
TokenExpirationDayLimit int `json:"tokenExpirationDayLimit"`
|
||||||
|
SharedWithMeFolderUID string `json:"sharedWithMeFolderUID"`
|
||||||
|
|
||||||
GeomapDefaultBaseLayerConfig *map[string]any `json:"geomapDefaultBaseLayerConfig,omitempty"`
|
GeomapDefaultBaseLayerConfig *map[string]any `json:"geomapDefaultBaseLayerConfig,omitempty"`
|
||||||
GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"`
|
GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"`
|
||||||
|
@ -467,7 +467,7 @@ func setupServer(b testing.TB, sc benchScenario, features *featuremgmt.FeatureMa
|
|||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sc.db)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sc.db)
|
||||||
|
|
||||||
ac := acimpl.ProvideAccessControl(sc.cfg)
|
ac := acimpl.ProvideAccessControl(sc.cfg)
|
||||||
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sc.cfg, dashStore, folderStore, sc.db, features)
|
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sc.cfg, dashStore, folderStore, sc.db, features, nil)
|
||||||
|
|
||||||
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
||||||
features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc)
|
features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc)
|
||||||
@ -479,7 +479,7 @@ func setupServer(b testing.TB, sc benchScenario, features *featuremgmt.FeatureMa
|
|||||||
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
|
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||||
sc.cfg, dashStore, folderStore, nil,
|
sc.cfg, dashStore, folderStore, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
folderServiceWithFlagOn,
|
folderServiceWithFlagOn, nil,
|
||||||
)
|
)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
@ -156,6 +157,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||||||
SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI,
|
SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI,
|
||||||
DisableFrontendSandboxForPlugins: hs.Cfg.DisableFrontendSandboxForPlugins,
|
DisableFrontendSandboxForPlugins: hs.Cfg.DisableFrontendSandboxForPlugins,
|
||||||
PublicDashboardAccessToken: c.PublicDashboardAccessToken,
|
PublicDashboardAccessToken: c.PublicDashboardAccessToken,
|
||||||
|
SharedWithMeFolderUID: folder.SharedWithMeFolderUID,
|
||||||
|
|
||||||
Auth: dtos.FrontendSettingsAuthDTO{
|
Auth: dtos.FrontendSettingsAuthDTO{
|
||||||
OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
||||||
|
@ -22,7 +22,9 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@ -33,6 +35,11 @@ const (
|
|||||||
cacheTTL = 10 * time.Second
|
cacheTTL = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SharedWithMeFolderPermission = accesscontrol.Permission{
|
||||||
|
Action: dashboards.ActionFoldersRead,
|
||||||
|
Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.SharedWithMeFolderUID),
|
||||||
|
}
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
|
func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
|
||||||
accessControl accesscontrol.AccessControl, features *featuremgmt.FeatureManager) (*Service, error) {
|
accessControl accesscontrol.AccessControl, features *featuremgmt.FeatureManager) (*Service, error) {
|
||||||
service := ProvideOSSService(cfg, database.ProvideService(db), cache, features)
|
service := ProvideOSSService(cfg, database.ProvideService(db), cache, features)
|
||||||
@ -115,6 +122,10 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||||
|
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||||
|
}
|
||||||
|
|
||||||
userID, err := identity.UserIdentifier(user.GetNamespacedID())
|
userID, err := identity.UserIdentifier(user.GetNamespacedID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -6,6 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
@ -26,8 +29,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||||
@ -220,7 +221,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ac := acimpl.ProvideAccessControl(sql.Cfg)
|
ac := acimpl.ProvideAccessControl(sql.Cfg)
|
||||||
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sql.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features)
|
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sql.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, nil)
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.AnnotationMaximumTagsLength = 60
|
cfg.AnnotationMaximumTagsLength = 60
|
||||||
|
@ -24,6 +24,7 @@ type DashboardService interface {
|
|||||||
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error)
|
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error)
|
||||||
SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error)
|
SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error)
|
||||||
CountInFolder(ctx context.Context, orgID int64, folderUID string, user identity.Requester) (int64, error)
|
CountInFolder(ctx context.Context, orgID int64, folderUID string, user identity.Requester) (int64, error)
|
||||||
|
GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginService is a service for operating on plugin dashboards.
|
// PluginService is a service for operating on plugin dashboards.
|
||||||
|
@ -286,6 +286,31 @@ func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *Fin
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_m *FakeDashboardService) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) {
|
||||||
|
ret := _m.Called(ctx, user)
|
||||||
|
|
||||||
|
var r0 []*Dashboard
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) ([]*Dashboard, error)); ok {
|
||||||
|
return rf(ctx, user)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) []*Dashboard); ok {
|
||||||
|
r0 = rf(ctx, user)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*Dashboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) error); ok {
|
||||||
|
r1 = rf(ctx, user)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
type mockConstructorTestingTNewFakeDashboardService interface {
|
type mockConstructorTestingTNewFakeDashboardService interface {
|
||||||
mock.TestingT
|
mock.TestingT
|
||||||
Cleanup(func())
|
Cleanup(func())
|
||||||
|
@ -294,7 +294,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
|
|||||||
guardian.New = origNewGuardian
|
guardian.New = origNewGuardian
|
||||||
})
|
})
|
||||||
|
|
||||||
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features)
|
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, nil)
|
||||||
|
|
||||||
parentUID := ""
|
parentUID := ""
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
|
@ -689,7 +689,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
|
|||||||
|
|
||||||
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
|
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||||
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features)
|
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil)
|
||||||
|
|
||||||
user := &user.SignedInUser{
|
user := &user.SignedInUser{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
@ -806,7 +806,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
|
|||||||
|
|
||||||
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
|
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||||
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features)
|
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil)
|
||||||
|
|
||||||
user := &user.SignedInUser{
|
user := &user.SignedInUser{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
|
@ -4,6 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||||
|
|
||||||
@ -49,6 +54,7 @@ type DashboardServiceImpl struct {
|
|||||||
folderPermissions accesscontrol.FolderPermissionsService
|
folderPermissions accesscontrol.FolderPermissionsService
|
||||||
dashboardPermissions accesscontrol.DashboardPermissionsService
|
dashboardPermissions accesscontrol.DashboardPermissionsService
|
||||||
ac accesscontrol.AccessControl
|
ac accesscontrol.AccessControl
|
||||||
|
metrics *dashboardsMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the uber service that implements a three smaller services
|
// This is the uber service that implements a three smaller services
|
||||||
@ -56,7 +62,7 @@ func ProvideDashboardServiceImpl(
|
|||||||
cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, dashAlertExtractor alerting.DashAlertExtractor,
|
cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, dashAlertExtractor alerting.DashAlertExtractor,
|
||||||
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
|
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||||
dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl,
|
dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl,
|
||||||
folderSvc folder.Service,
|
folderSvc folder.Service, r prometheus.Registerer,
|
||||||
) (*DashboardServiceImpl, error) {
|
) (*DashboardServiceImpl, error) {
|
||||||
dashSvc := &DashboardServiceImpl{
|
dashSvc := &DashboardServiceImpl{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@ -69,6 +75,7 @@ func ProvideDashboardServiceImpl(
|
|||||||
ac: ac,
|
ac: ac,
|
||||||
folderStore: folderStore,
|
folderStore: folderStore,
|
||||||
folderService: folderSvc,
|
folderService: folderSvc,
|
||||||
|
metrics: newDashboardsMetrics(r),
|
||||||
}
|
}
|
||||||
|
|
||||||
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc))
|
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc))
|
||||||
@ -537,7 +544,112 @@ func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashbo
|
|||||||
return dr.dashboardStore.GetDashboards(ctx, query)
|
return dr.dashboardStore.GetDashboards(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardServiceImpl) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*dashboards.Dashboard, error) {
|
||||||
|
return dr.getDashboardsSharedWithUser(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardServiceImpl) getDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*dashboards.Dashboard, error) {
|
||||||
|
permissions := user.GetPermissions()
|
||||||
|
dashboardPermissions := permissions[dashboards.ActionDashboardsRead]
|
||||||
|
sharedDashboards := make([]*dashboards.Dashboard, 0)
|
||||||
|
dashboardUids := make([]string, 0)
|
||||||
|
for _, p := range dashboardPermissions {
|
||||||
|
if dashboardUid, found := strings.CutPrefix(p, dashboards.ScopeDashboardsPrefix); found {
|
||||||
|
if !slices.Contains(dashboardUids, dashboardUid) {
|
||||||
|
dashboardUids = append(dashboardUids, dashboardUid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dashboardUids) == 0 {
|
||||||
|
return sharedDashboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardsQuery := &dashboards.GetDashboardsQuery{
|
||||||
|
DashboardUIDs: dashboardUids,
|
||||||
|
OrgID: user.GetOrgID(),
|
||||||
|
}
|
||||||
|
sharedDashboards, err := dr.dashboardStore.GetDashboards(ctx, dashboardsQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dr.filterUserSharedDashboards(ctx, user, sharedDashboards)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterUserSharedDashboards filter dashboards directly assigned to user, but not located in folders with view permissions
|
||||||
|
func (dr *DashboardServiceImpl) filterUserSharedDashboards(ctx context.Context, user identity.Requester, userDashboards []*dashboards.Dashboard) ([]*dashboards.Dashboard, error) {
|
||||||
|
filteredDashboards := make([]*dashboards.Dashboard, 0)
|
||||||
|
|
||||||
|
folderUIDs := make([]string, 0)
|
||||||
|
for _, dashboard := range userDashboards {
|
||||||
|
folderUIDs = append(folderUIDs, dashboard.FolderUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
dashFolders, err := dr.folderStore.GetFolders(ctx, user.GetOrgID(), folderUIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, folder.ErrInternal.Errorf("failed to fetch parent folders from store: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dashboard := range userDashboards {
|
||||||
|
// Filter out dashboards if user has access to parent folder
|
||||||
|
if dashboard.FolderUID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dashFolder, ok := dashFolders[dashboard.FolderUID]
|
||||||
|
if !ok {
|
||||||
|
dr.log.Error("failed to fetch folder by UID from store", "uid", dashboard.FolderUID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := guardian.NewByFolder(ctx, dashFolder, user.GetOrgID(), user)
|
||||||
|
if err != nil {
|
||||||
|
dr.log.Error("failed to check folder permissions", "folder uid", dashboard.FolderUID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
canView, err := g.CanView()
|
||||||
|
if err != nil {
|
||||||
|
dr.log.Error("failed to fetch dashboard", "uid", dashboard.UID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !canView {
|
||||||
|
filteredDashboards = append(filteredDashboards, dashboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredDashboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardServiceImpl) getUserSharedDashboardUIDs(ctx context.Context, user identity.Requester) ([]string, error) {
|
||||||
|
userDashboards, err := dr.getDashboardsSharedWithUser(ctx, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userDashboardUIDs := make([]string, 0)
|
||||||
|
for _, dashboard := range userDashboards {
|
||||||
|
userDashboardUIDs = append(userDashboardUIDs, dashboard.UID)
|
||||||
|
}
|
||||||
|
return userDashboardUIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
|
func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
|
||||||
|
if dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && len(query.FolderUIDs) > 0 && slices.Contains(query.FolderUIDs, folder.SharedWithMeFolderUID) {
|
||||||
|
start := time.Now()
|
||||||
|
userDashboardUIDs, err := dr.getUserSharedDashboardUIDs(ctx, query.SignedInUser)
|
||||||
|
if err != nil {
|
||||||
|
dr.metrics.sharedWithMeFetchDashboardsRequestsDuration.WithLabelValues("failure").Observe(time.Since(start).Seconds())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(userDashboardUIDs) == 0 {
|
||||||
|
return []dashboards.DashboardSearchProjection{}, nil
|
||||||
|
}
|
||||||
|
query.DashboardUIDs = userDashboardUIDs
|
||||||
|
query.FolderUIDs = []string{}
|
||||||
|
|
||||||
|
defer func(t time.Time) {
|
||||||
|
dr.metrics.sharedWithMeFetchDashboardsRequestsDuration.WithLabelValues("success").Observe(time.Since(start).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
}
|
||||||
return dr.dashboardStore.FindDashboards(ctx, query)
|
return dr.dashboardStore.FindDashboards(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -893,6 +893,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
|
|||||||
dashboardPermissions,
|
dashboardPermissions,
|
||||||
ac,
|
ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
guardian.InitAccessControlGuardian(cfg, ac, dashboardService)
|
guardian.InitAccessControlGuardian(cfg, ac, dashboardService)
|
||||||
@ -961,6 +962,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
|
|||||||
dashboardPermissions,
|
dashboardPermissions,
|
||||||
actest.FakeAccessControl{},
|
actest.FakeAccessControl{},
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||||
@ -984,6 +986,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
|
|||||||
accesscontrolmock.NewMockedPermissionsService(),
|
accesscontrolmock.NewMockedPermissionsService(),
|
||||||
actest.FakeAccessControl{},
|
actest.FakeAccessControl{},
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = service.SaveDashboard(context.Background(), &dto, false)
|
_, err = service.SaveDashboard(context.Background(), &dto, false)
|
||||||
@ -1027,6 +1030,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, folder
|
|||||||
dashboardPermissions,
|
dashboardPermissions,
|
||||||
actest.FakeAccessControl{},
|
actest.FakeAccessControl{},
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||||
@ -1077,6 +1081,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
|
|||||||
accesscontrolmock.NewMockedPermissionsService(),
|
accesscontrolmock.NewMockedPermissionsService(),
|
||||||
actest.FakeAccessControl{},
|
actest.FakeAccessControl{},
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||||
|
30
pkg/services/dashboards/service/metrics.go
Normal file
30
pkg/services/dashboards/service/metrics.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricsNamespace = "grafana"
|
||||||
|
metricsSubSystem = "dashboards"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dashboardsMetrics struct {
|
||||||
|
sharedWithMeFetchDashboardsRequestsDuration *prometheus.HistogramVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDashboardsMetrics(r prometheus.Registerer) *dashboardsMetrics {
|
||||||
|
return &dashboardsMetrics{
|
||||||
|
sharedWithMeFetchDashboardsRequestsDuration: promauto.With(r).NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "sharedwithme_fetch_dashboards_duration_seconds",
|
||||||
|
Help: "Duration of fetching dashboards with permissions directly assigned to user",
|
||||||
|
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 50, 100},
|
||||||
|
Namespace: metricsNamespace,
|
||||||
|
Subsystem: metricsSubSystem,
|
||||||
|
},
|
||||||
|
[]string{"status"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
@ -41,6 +43,7 @@ type Service struct {
|
|||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
registry map[string]folder.RegistryService
|
registry map[string]folder.RegistryService
|
||||||
|
metrics *foldersMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(
|
func ProvideService(
|
||||||
@ -51,6 +54,7 @@ func ProvideService(
|
|||||||
folderStore folder.FolderStore,
|
folderStore folder.FolderStore,
|
||||||
db db.DB, // DB for the (new) nested folder store
|
db db.DB, // DB for the (new) nested folder store
|
||||||
features featuremgmt.FeatureToggles,
|
features featuremgmt.FeatureToggles,
|
||||||
|
r prometheus.Registerer,
|
||||||
) folder.Service {
|
) folder.Service {
|
||||||
store := ProvideStore(db, cfg, features)
|
store := ProvideStore(db, cfg, features)
|
||||||
srv := &Service{
|
srv := &Service{
|
||||||
@ -64,6 +68,7 @@ func ProvideService(
|
|||||||
bus: bus,
|
bus: bus,
|
||||||
db: db,
|
db: db,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
|
metrics: newFoldersMetrics(r),
|
||||||
}
|
}
|
||||||
srv.DBMigration(db)
|
srv.DBMigration(db)
|
||||||
|
|
||||||
@ -115,6 +120,10 @@ func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.
|
|||||||
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.UID != nil && *cmd.UID == folder.SharedWithMeFolderUID {
|
||||||
|
return folder.SharedWithMeFolder.WithURL(), nil
|
||||||
|
}
|
||||||
|
|
||||||
var dashFolder *folder.Folder
|
var dashFolder *folder.Folder
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch {
|
||||||
@ -185,6 +194,10 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery)
|
|||||||
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.UID == folder.SharedWithMeFolderUID {
|
||||||
|
return s.GetSharedWithMe(ctx, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.UID != "" {
|
if cmd.UID != "" {
|
||||||
g, err := guardian.NewByUID(ctx, cmd.UID, cmd.OrgID, cmd.SignedInUser)
|
g, err := guardian.NewByUID(ctx, cmd.UID, cmd.OrgID, cmd.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -249,31 +262,40 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(filtered) < len(children) {
|
||||||
|
// add "shared with me" folder
|
||||||
|
filtered = append(filtered, &folder.SharedWithMeFolder)
|
||||||
|
}
|
||||||
|
|
||||||
return filtered, nil
|
return filtered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSharedWithMe returns folders available to user, which cannot be accessed from the root folders
|
// GetSharedWithMe returns folders available to user, which cannot be accessed from the root folders
|
||||||
func (s *Service) GetSharedWithMe(ctx context.Context, cmd *folder.GetChildrenQuery) ([]*folder.Folder, error) {
|
func (s *Service) GetSharedWithMe(ctx context.Context, cmd *folder.GetChildrenQuery) ([]*folder.Folder, error) {
|
||||||
|
start := time.Now()
|
||||||
availableNonRootFolders, err := s.getAvailableNonRootFolders(ctx, cmd.OrgID, cmd.SignedInUser)
|
availableNonRootFolders, err := s.getAvailableNonRootFolders(ctx, cmd.OrgID, cmd.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.metrics.sharedWithMeFetchFoldersRequestsDuration.WithLabelValues("failure").Observe(time.Since(start).Seconds())
|
||||||
return nil, folder.ErrInternal.Errorf("failed to fetch subfolders to which the user has explicit access: %w", err)
|
return nil, folder.ErrInternal.Errorf("failed to fetch subfolders to which the user has explicit access: %w", err)
|
||||||
}
|
}
|
||||||
rootFolders, err := s.GetChildren(ctx, &folder.GetChildrenQuery{UID: "", OrgID: cmd.OrgID, SignedInUser: cmd.SignedInUser})
|
rootFolders, err := s.GetChildren(ctx, &folder.GetChildrenQuery{UID: "", OrgID: cmd.OrgID, SignedInUser: cmd.SignedInUser})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.metrics.sharedWithMeFetchFoldersRequestsDuration.WithLabelValues("failure").Observe(time.Since(start).Seconds())
|
||||||
return nil, folder.ErrInternal.Errorf("failed to fetch root folders to which the user has access: %w", err)
|
return nil, folder.ErrInternal.Errorf("failed to fetch root folders to which the user has access: %w", err)
|
||||||
}
|
}
|
||||||
availableNonRootFolders = s.deduplicateAvailableFolders(ctx, availableNonRootFolders, rootFolders)
|
availableNonRootFolders = s.deduplicateAvailableFolders(ctx, availableNonRootFolders, rootFolders)
|
||||||
|
s.metrics.sharedWithMeFetchFoldersRequestsDuration.WithLabelValues("success").Observe(time.Since(start).Seconds())
|
||||||
return availableNonRootFolders, nil
|
return availableNonRootFolders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getAvailableNonRootFolders(ctx context.Context, orgID int64, user identity.Requester) ([]*folder.Folder, error) {
|
func (s *Service) getAvailableNonRootFolders(ctx context.Context, orgID int64, user identity.Requester) ([]*folder.Folder, error) {
|
||||||
permissions := user.GetPermissions()
|
permissions := user.GetPermissions()
|
||||||
folderPermissions := permissions["folders:read"]
|
folderPermissions := permissions[dashboards.ActionFoldersRead]
|
||||||
folderPermissions = append(folderPermissions, permissions["dashboards:read"]...)
|
folderPermissions = append(folderPermissions, permissions[dashboards.ActionDashboardsRead]...)
|
||||||
nonRootFolders := make([]*folder.Folder, 0)
|
nonRootFolders := make([]*folder.Folder, 0)
|
||||||
folderUids := make([]string, 0)
|
folderUids := make([]string, 0)
|
||||||
for _, p := range folderPermissions {
|
for _, p := range folderPermissions {
|
||||||
if folderUid, found := strings.CutPrefix(p, "folders:uid:"); found {
|
if folderUid, found := strings.CutPrefix(p, dashboards.ScopeFoldersPrefix); found {
|
||||||
if !slices.Contains(folderUids, folderUid) {
|
if !slices.Contains(folderUids, folderUid) {
|
||||||
folderUids = append(folderUids, folderUid)
|
folderUids = append(folderUids, folderUid)
|
||||||
}
|
}
|
||||||
@ -335,6 +357,9 @@ func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*
|
|||||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if q.UID == folder.SharedWithMeFolderUID {
|
||||||
|
return []*folder.Folder{&folder.SharedWithMeFolder}, nil
|
||||||
|
}
|
||||||
return s.store.GetParents(ctx, q)
|
return s.store.GetParents(ctx, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,6 +402,10 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
|
|||||||
dashFolder.FolderUID = cmd.ParentUID
|
dashFolder.FolderUID = cmd.ParentUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.UID == folder.SharedWithMeFolderUID {
|
||||||
|
return nil, folder.ErrBadRequest.Errorf("cannot create folder with UID %s", folder.SharedWithMeFolderUID)
|
||||||
|
}
|
||||||
|
|
||||||
trimmedUID := strings.TrimSpace(cmd.UID)
|
trimmedUID := strings.TrimSpace(cmd.UID)
|
||||||
if trimmedUID == accesscontrol.GeneralFolderUID {
|
if trimmedUID == accesscontrol.GeneralFolderUID {
|
||||||
return nil, dashboards.ErrFolderInvalidUID
|
return nil, dashboards.ErrFolderInvalidUID
|
||||||
|
@ -22,9 +22,11 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||||
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
|
alertmodels "github.com/grafana/grafana/pkg/services/alerting/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards/service"
|
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||||
@ -54,7 +56,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
|
|||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
ac := acmock.New()
|
ac := acmock.New()
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, db, &featuremgmt.FeatureManager{})
|
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, db, &featuremgmt.FeatureManager{}, nil)
|
||||||
|
|
||||||
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3)
|
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3)
|
||||||
})
|
})
|
||||||
@ -94,6 +96,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||||
db: db,
|
db: db,
|
||||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
accessControl: acimpl.ProvideAccessControl(cfg),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,6 +394,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
db: db,
|
db: db,
|
||||||
accessControl: ac,
|
accessControl: ac,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||||
@ -434,7 +438,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
CanEditValue: true,
|
CanEditValue: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
|
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac)
|
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac)
|
||||||
@ -502,6 +506,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
bus: b,
|
bus: b,
|
||||||
db: db,
|
db: db,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
origNewGuardian := guardian.New
|
origNewGuardian := guardian.New
|
||||||
@ -512,8 +517,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
CanEditValue: true,
|
CanEditValue: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff,
|
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff,
|
||||||
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
|
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac)
|
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac)
|
||||||
@ -577,6 +582,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
bus: b,
|
bus: b,
|
||||||
db: db,
|
db: db,
|
||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -655,7 +661,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
|||||||
tc.service.dashboardStore = dashStore
|
tc.service.dashboardStore = dashStore
|
||||||
tc.service.store = nestedFolderStore
|
tc.service.store = nestedFolderStore
|
||||||
|
|
||||||
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service)
|
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac)
|
alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -750,6 +756,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
|
|||||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||||
log: log.New("test-folder-service"),
|
log: log.New("test-folder-service"),
|
||||||
accessControl: acimpl.ProvideAccessControl(cfg),
|
accessControl: acimpl.ProvideAccessControl(cfg),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
}
|
}
|
||||||
t.Run("create folder", func(t *testing.T) {
|
t.Run("create folder", func(t *testing.T) {
|
||||||
nestedFolderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()}
|
nestedFolderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()}
|
||||||
@ -1211,6 +1218,157 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
db := sqlstore.InitTestDB(t)
|
||||||
|
quotaService := quotatest.New(false, nil)
|
||||||
|
folderStore := ProvideDashboardFolderStore(db)
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
|
||||||
|
featuresFlagOn := featuremgmt.WithFeatures("nestedFolders")
|
||||||
|
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOn, tagimpl.ProvideService(db), quotaService)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
|
||||||
|
|
||||||
|
b := bus.ProvideBus(tracing.InitializeTracerForTest())
|
||||||
|
ac := acimpl.ProvideAccessControl(cfg)
|
||||||
|
|
||||||
|
serviceWithFlagOn := &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("test-folder-service"),
|
||||||
|
dashboardStore: dashStore,
|
||||||
|
dashboardFolderStore: folderStore,
|
||||||
|
store: nestedFolderStore,
|
||||||
|
features: featuresFlagOn,
|
||||||
|
bus: b,
|
||||||
|
db: db,
|
||||||
|
accessControl: ac,
|
||||||
|
registry: make(map[string]folder.RegistryService),
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||||
|
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||||
|
cfg, dashStore, folderStore, &dummyDashAlertExtractor{},
|
||||||
|
featuresFlagOn,
|
||||||
|
acmock.NewMockedPermissionsService(),
|
||||||
|
dashboardPermissions,
|
||||||
|
actest.FakeAccessControl{},
|
||||||
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {
|
||||||
|
dashboards.ActionFoldersRead: {},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {
|
||||||
|
dashboards.ActionFoldersCreate: {},
|
||||||
|
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
createCmd := folder.CreateFolderCommand{
|
||||||
|
OrgID: orgID,
|
||||||
|
ParentUID: "",
|
||||||
|
SignedInUser: &signedInAdminUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should get folders shared with given user", func(t *testing.T) {
|
||||||
|
depth := 3
|
||||||
|
origNewGuardian := guardian.New
|
||||||
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||||
|
CanSaveValue: true,
|
||||||
|
CanViewValue: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
ancestorUIDsFolderWithPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withPermissions", createCmd)
|
||||||
|
ancestorUIDsFolderWithoutPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withoutPermissions", createCmd)
|
||||||
|
|
||||||
|
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDsFolderWithoutPermissions[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDsFolderWithoutPermissions[1])
|
||||||
|
require.NoError(t, err)
|
||||||
|
// nolint:staticcheck
|
||||||
|
dash1 := insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, parent.UID, "prod")
|
||||||
|
// nolint:staticcheck
|
||||||
|
dash2 := insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, subfolder.UID, "prod")
|
||||||
|
|
||||||
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||||
|
CanSaveValue: true,
|
||||||
|
CanViewValue: true,
|
||||||
|
CanViewUIDs: []string{
|
||||||
|
ancestorUIDsFolderWithPermissions[0],
|
||||||
|
ancestorUIDsFolderWithPermissions[1],
|
||||||
|
ancestorUIDsFolderWithoutPermissions[1],
|
||||||
|
dash1.UID,
|
||||||
|
dash2.UID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
signedInUser.Permissions[orgID][dashboards.ActionFoldersRead] = []string{
|
||||||
|
dashboards.ScopeFoldersProvider.GetResourceScopeUID(ancestorUIDsFolderWithPermissions[0]),
|
||||||
|
// Add permission to the subfolder of folder with permission (to check deduplication)
|
||||||
|
dashboards.ScopeFoldersProvider.GetResourceScopeUID(ancestorUIDsFolderWithPermissions[1]),
|
||||||
|
// Add permission to the subfolder of folder without permission
|
||||||
|
dashboards.ScopeFoldersProvider.GetResourceScopeUID(ancestorUIDsFolderWithoutPermissions[1]),
|
||||||
|
}
|
||||||
|
signedInUser.Permissions[orgID][dashboards.ActionDashboardsRead] = []string{
|
||||||
|
dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dash1.UID),
|
||||||
|
dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dash2.UID),
|
||||||
|
}
|
||||||
|
|
||||||
|
getSharedCmd := folder.GetChildrenQuery{
|
||||||
|
UID: folder.SharedWithMeFolderUID,
|
||||||
|
OrgID: orgID,
|
||||||
|
SignedInUser: &signedInUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedFolders, err := serviceWithFlagOn.GetChildren(context.Background(), &getSharedCmd)
|
||||||
|
sharedFoldersUIDs := make([]string, 0)
|
||||||
|
for _, f := range sharedFolders {
|
||||||
|
sharedFoldersUIDs = append(sharedFoldersUIDs, f.UID)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, sharedFolders, 1)
|
||||||
|
require.Contains(t, sharedFoldersUIDs, ancestorUIDsFolderWithoutPermissions[1])
|
||||||
|
require.NotContains(t, sharedFoldersUIDs, ancestorUIDsFolderWithPermissions[1])
|
||||||
|
|
||||||
|
sharedDashboards, err := dashboardService.GetDashboardsSharedWithUser(context.Background(), &signedInUser)
|
||||||
|
sharedDashboardsUIDs := make([]string, 0)
|
||||||
|
for _, d := range sharedDashboards {
|
||||||
|
sharedDashboardsUIDs = append(sharedDashboardsUIDs, d.UID)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, sharedDashboards, 1)
|
||||||
|
require.Contains(t, sharedDashboardsUIDs, dash1.UID)
|
||||||
|
require.NotContains(t, sharedDashboardsUIDs, dash2.UID)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
guardian.New = origNewGuardian
|
||||||
|
for _, uid := range ancestorUIDsFolderWithPermissions {
|
||||||
|
err := serviceWithFlagOn.store.Delete(context.Background(), uid, orgID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Cleanup(func() {
|
||||||
|
guardian.New = origNewGuardian
|
||||||
|
for _, uid := range ancestorUIDsFolderWithoutPermissions {
|
||||||
|
err := serviceWithFlagOn.store.Delete(context.Background(), uid, orgID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func CreateSubtreeInStore(t *testing.T, store *sqlStore, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []string {
|
func CreateSubtreeInStore(t *testing.T, store *sqlStore, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@ -1263,6 +1421,7 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
|
|||||||
features: features,
|
features: features,
|
||||||
accessControl: ac,
|
accessControl: ac,
|
||||||
db: db,
|
db: db,
|
||||||
|
metrics: newFoldersMetrics(nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1287,3 +1446,14 @@ func createRule(t *testing.T, store *ngstore.DBstore, folderUID, title string) *
|
|||||||
|
|
||||||
return &rule
|
return &rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyDashAlertExtractor struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDashAlertExtractor) GetAlerts(ctx context.Context, dashAlertInfo alerting.DashAlertInfo) ([]*alertmodels.Alert, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDashAlertExtractor) ValidateAlerts(ctx context.Context, dashAlertInfo alerting.DashAlertInfo) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
30
pkg/services/folder/folderimpl/metrics.go
Normal file
30
pkg/services/folder/folderimpl/metrics.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package folderimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricsNamespace = "grafana"
|
||||||
|
metricsSubSystem = "folders"
|
||||||
|
)
|
||||||
|
|
||||||
|
type foldersMetrics struct {
|
||||||
|
sharedWithMeFetchFoldersRequestsDuration *prometheus.HistogramVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFoldersMetrics(r prometheus.Registerer) *foldersMetrics {
|
||||||
|
return &foldersMetrics{
|
||||||
|
sharedWithMeFetchFoldersRequestsDuration: promauto.With(r).NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "sharedwithme_fetch_folders_duration_seconds",
|
||||||
|
Help: "Duration of fetching folders with permissions directly assigned to user",
|
||||||
|
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 50, 100},
|
||||||
|
Namespace: metricsNamespace,
|
||||||
|
Subsystem: metricsSubSystem,
|
||||||
|
},
|
||||||
|
[]string{"status"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
@ -19,9 +19,10 @@ var ErrTargetRegistrySrvConflict = errutil.Internal("folder.target-registry-srv-
|
|||||||
var ErrFolderNotEmpty = errutil.BadRequest("folder.not-empty", errutil.WithPublicMessage("Folder cannot be deleted: folder is not empty"))
|
var ErrFolderNotEmpty = errutil.BadRequest("folder.not-empty", errutil.WithPublicMessage("Folder cannot be deleted: folder is not empty"))
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GeneralFolderUID = "general"
|
GeneralFolderUID = "general"
|
||||||
RootFolderUID = ""
|
RootFolderUID = ""
|
||||||
MaxNestedFolderDepth = 4
|
MaxNestedFolderDepth = 4
|
||||||
|
SharedWithMeFolderUID = "sharedwithme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrFolderNotFound = errutil.NotFound("folder.notFound")
|
var ErrFolderNotFound = errutil.NotFound("folder.notFound")
|
||||||
@ -49,6 +50,14 @@ type Folder struct {
|
|||||||
|
|
||||||
var GeneralFolder = Folder{ID: 0, Title: "General"}
|
var GeneralFolder = Folder{ID: 0, Title: "General"}
|
||||||
|
|
||||||
|
var SharedWithMeFolder = Folder{
|
||||||
|
Title: "Shared with me",
|
||||||
|
Description: "Dashboards and folders shared with me",
|
||||||
|
UID: SharedWithMeFolderUID,
|
||||||
|
ParentUID: "",
|
||||||
|
ID: -1,
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Folder) IsGeneral() bool {
|
func (f *Folder) IsGeneral() bool {
|
||||||
// nolint:staticcheck
|
// nolint:staticcheck
|
||||||
return f.ID == GeneralFolder.ID && f.Title == GeneralFolder.Title
|
return f.ID == GeneralFolder.ID && f.Title == GeneralFolder.Title
|
||||||
|
@ -2,6 +2,7 @@ package guardian
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
@ -59,6 +60,7 @@ type FakeDashboardGuardian struct {
|
|||||||
CanEditValue bool
|
CanEditValue bool
|
||||||
CanViewValue bool
|
CanViewValue bool
|
||||||
CanAdminValue bool
|
CanAdminValue bool
|
||||||
|
CanViewUIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *FakeDashboardGuardian) CanSave() (bool, error) {
|
func (g *FakeDashboardGuardian) CanSave() (bool, error) {
|
||||||
@ -70,6 +72,9 @@ func (g *FakeDashboardGuardian) CanEdit() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *FakeDashboardGuardian) CanView() (bool, error) {
|
func (g *FakeDashboardGuardian) CanView() (bool, error) {
|
||||||
|
if g.CanViewUIDs != nil {
|
||||||
|
return slices.Contains(g.CanViewUIDs, g.DashUID), nil
|
||||||
|
}
|
||||||
return g.CanViewValue, nil
|
return g.CanViewValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +302,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
|
|||||||
cfg, dashboardStore, folderStore, dashAlertExtractor,
|
cfg, dashboardStore, folderStore, dashAlertExtractor,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
||||||
@ -321,7 +322,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
|
||||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features)
|
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, nil)
|
||||||
t.Logf("Creating folder with title and UID %q", title)
|
t.Logf("Creating folder with title and UID %q", title)
|
||||||
ctx := appcontext.WithUser(context.Background(), &sc.user)
|
ctx := appcontext.WithUser(context.Background(), &sc.user)
|
||||||
folder, err := s.Create(ctx, &folder.CreateFolderCommand{
|
folder, err := s.Create(ctx, &folder.CreateFolderCommand{
|
||||||
@ -385,6 +386,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena
|
|||||||
sqlStore.Cfg, dashboardStore, folderStore, nil,
|
sqlStore.Cfg, dashboardStore, folderStore, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, svcErr)
|
require.NoError(t, svcErr)
|
||||||
guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashboardService)
|
guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashboardService)
|
||||||
@ -445,6 +447,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
sqlStore.Cfg, dashboardStore, folderStore, nil,
|
sqlStore.Cfg, dashboardStore, folderStore, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, dashSvcErr)
|
require.NoError(t, dashSvcErr)
|
||||||
guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashService)
|
guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashService)
|
||||||
@ -452,7 +455,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
Cfg: sqlStore.Cfg,
|
Cfg: sqlStore.Cfg,
|
||||||
features: featuremgmt.WithFeatures(),
|
features: featuremgmt.WithFeatures(),
|
||||||
SQLStore: sqlStore,
|
SQLStore: sqlStore,
|
||||||
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features),
|
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
// deliberate difference between signed in user and user in db to make it crystal clear
|
// deliberate difference between signed in user and user in db to make it crystal clear
|
||||||
|
@ -734,6 +734,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
|
|||||||
cfg, dashboardStore, folderStore, dashAlertService,
|
cfg, dashboardStore, folderStore, dashAlertService,
|
||||||
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
||||||
@ -752,7 +753,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
|
|||||||
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService)
|
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
|
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
|
||||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features)
|
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, nil)
|
||||||
|
|
||||||
t.Logf("Creating folder with title and UID %q", title)
|
t.Logf("Creating folder with title and UID %q", title)
|
||||||
ctx := appcontext.WithUser(context.Background(), sc.user)
|
ctx := appcontext.WithUser(context.Background(), sc.user)
|
||||||
@ -826,6 +827,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
setting.NewCfg(), dashStore, folderStore, dashAlertService,
|
setting.NewCfg(), dashStore, folderStore, dashAlertService,
|
||||||
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
guardian.InitAccessControlGuardian(setting.NewCfg(), ac, dashService)
|
guardian.InitAccessControlGuardian(setting.NewCfg(), ac, dashService)
|
||||||
@ -833,7 +835,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService)
|
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features)
|
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, nil)
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures(), ac)
|
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures(), ac)
|
||||||
service := LibraryPanelService{
|
service := LibraryPanelService{
|
||||||
|
@ -67,7 +67,7 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti
|
|||||||
|
|
||||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService)
|
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, sqlStore, features)
|
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, sqlStore, features, nil)
|
||||||
|
|
||||||
err = folderService.RegisterService(alertingStore)
|
err = folderService.RegisterService(alertingStore)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -83,6 +83,7 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti
|
|||||||
cfg, dashboardStore, folderStore, nil,
|
cfg, dashboardStore, folderStore, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
folderService,
|
folderService,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
guardian.InitAccessControlGuardian(setting.NewCfg(), ac, dashboardService)
|
guardian.InitAccessControlGuardian(setting.NewCfg(), ac, dashboardService)
|
||||||
|
@ -30,7 +30,7 @@ func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStor
|
|||||||
ac := acmock.New()
|
ac := acmock.New()
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
|
|
||||||
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, db, features)
|
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, db, features, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
||||||
@ -61,6 +61,7 @@ func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folde
|
|||||||
cfg, dashboardStore, fs, nil,
|
cfg, dashboardStore, fs, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
foldertest.NewFakeService(),
|
foldertest.NewFakeService(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
@ -843,7 +843,7 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
|
|||||||
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil))
|
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features)
|
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, nil)
|
||||||
|
|
||||||
// create parent folder
|
// create parent folder
|
||||||
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
|
@ -83,7 +83,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe
|
|||||||
dashboardWriteStore, err := database.ProvideDashboardStore(store, store.Cfg, features, tagimpl.ProvideService(store), quotaService)
|
dashboardWriteStore, err := database.ProvideDashboardStore(store, store.Cfg, features, tagimpl.ProvideService(store), quotaService)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), store.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features)
|
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), store.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, nil)
|
||||||
|
|
||||||
origNewGuardian := guardian.New
|
origNewGuardian := guardian.New
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
|
||||||
|
Loading…
Reference in New Issue
Block a user