Folders: Split legacy out of folder.Service (and remove folder.FolderStore) (#110734)
This commit is contained in:
@@ -407,8 +407,6 @@ func TestAPI_Annotations(t *testing.T) {
|
||||
dashService.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{UID: dashUID, FolderUID: folderUID, FolderID: 1}, nil)
|
||||
folderService := &foldertest.FakeService{}
|
||||
folderService.ExpectedFolder = &folder.Folder{UID: folderUID, ID: 1}
|
||||
folderDB := &foldertest.FakeFolderStore{}
|
||||
folderDB.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(&folder.Folder{UID: folderUID, ID: 1}, nil)
|
||||
hs.DashboardService = dashService
|
||||
hs.folderService = folderService
|
||||
hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
||||
|
||||
@@ -430,14 +430,12 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
dashStore, err := database.ProvideDashboardStore(sc.db, sc.cfg, features, tagimpl.ProvideService(sc.db))
|
||||
require.NoError(b, err)
|
||||
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sc.db)
|
||||
|
||||
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
||||
cfg := setting.NewCfg()
|
||||
actionSets := resourcepermissions.NewActionSetService()
|
||||
fStore := folderimpl.ProvideStore(sc.db)
|
||||
folderServiceWithFlagOn := folderimpl.ProvideService(
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
|
||||
nil, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
acSvc := acimpl.ProvideOSSService(
|
||||
sc.cfg, acdb.ProvideService(sc.db), actionSets, localcache.ProvideService(),
|
||||
@@ -449,7 +447,6 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
sc.cfg,
|
||||
dashStore,
|
||||
folderStore,
|
||||
features,
|
||||
folderPermissions,
|
||||
ac,
|
||||
|
||||
@@ -47,7 +47,6 @@ import (
|
||||
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
@@ -89,7 +88,6 @@ type DashboardsAPIBuilder struct {
|
||||
scheme *runtime.Scheme
|
||||
search *SearchHandler
|
||||
dashStore dashboards.Store
|
||||
folderStore folder.FolderStore
|
||||
QuotaService quota.Service
|
||||
ProvisioningService provisioning.ProvisioningService
|
||||
cfg *setting.Cfg
|
||||
@@ -125,7 +123,6 @@ func RegisterAPIService(
|
||||
dual dualwrite.Service,
|
||||
sorter sort.Service,
|
||||
quotaService quota.Service,
|
||||
folderStore folder.FolderStore,
|
||||
libraryPanelSvc librarypanels.Service,
|
||||
restConfigProvider apiserver.RestConfigProvider,
|
||||
userService user.Service,
|
||||
@@ -149,7 +146,6 @@ func RegisterAPIService(
|
||||
dashboardProvisioningService: provisioningDashboardService,
|
||||
search: NewSearchHandler(tracing, dual, legacyDashboardSearcher, unified, features),
|
||||
dashStore: dashStore,
|
||||
folderStore: folderStore,
|
||||
QuotaService: quotaService,
|
||||
ProvisioningService: provisioning,
|
||||
cfg: cfg,
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
)
|
||||
|
||||
type legacyStorage struct {
|
||||
service folder.Service
|
||||
service folder.LegacyService
|
||||
namespacer request.NamespaceMapper
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
@@ -243,6 +243,7 @@ func (s *legacyStorage) Update(ctx context.Context,
|
||||
return nil, created, fmt.Errorf("expected old object to be a folder also")
|
||||
}
|
||||
|
||||
changed := false
|
||||
mOld, _ := utils.MetaAccessor(old)
|
||||
mNew, _ := utils.MetaAccessor(f)
|
||||
oldParent := mOld.GetFolder()
|
||||
@@ -257,9 +258,9 @@ func (s *legacyStorage) Update(ctx context.Context,
|
||||
if err != nil {
|
||||
return nil, created, err
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
changed := false
|
||||
cmd := &folder.UpdateFolderCommand{
|
||||
SignedInUser: user,
|
||||
UID: name,
|
||||
|
||||
@@ -17,17 +17,16 @@ import (
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
|
||||
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -48,7 +47,7 @@ var errNoResource = errors.New("resource name is required")
|
||||
type FolderAPIBuilder struct {
|
||||
features featuremgmt.FeatureToggles
|
||||
namespacer request.NamespaceMapper
|
||||
folderSvc folder.Service
|
||||
folderSvc folder.LegacyService
|
||||
folderPermissionsSvc accesscontrol.FolderPermissionsService
|
||||
acService accesscontrol.Service
|
||||
ac accesscontrol.AccessControl
|
||||
@@ -66,7 +65,7 @@ type FolderAPIBuilder struct {
|
||||
func RegisterAPIService(cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles,
|
||||
apiregistration builder.APIRegistrar,
|
||||
folderSvc folder.Service,
|
||||
folderSvc folder.LegacyService,
|
||||
folderPermissionsSvc accesscontrol.FolderPermissionsService,
|
||||
accessControl accesscontrol.AccessControl,
|
||||
acService accesscontrol.Service,
|
||||
@@ -190,13 +189,13 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API
|
||||
}
|
||||
storage[resourceInfo.StoragePath()] = folderStore
|
||||
|
||||
b.parents = newParentsGetter(folderStore, folderValidationRules.maxDepth) // used for validation
|
||||
b.parents = newParentsGetter(folderStore, folder.MaxNestedFolderDepth) // used for validation
|
||||
storage[resourceInfo.StoragePath("parents")] = &subParentsREST{
|
||||
getter: folderStore,
|
||||
parents: b.parents,
|
||||
}
|
||||
storage[resourceInfo.StoragePath("counts")] = &subCountREST{searcher: b.searcher}
|
||||
storage[resourceInfo.StoragePath("access")] = &subAccessREST{b.folderSvc, b.ac}
|
||||
storage[resourceInfo.StoragePath("access")] = &subAccessREST{folderStore, b.ac}
|
||||
|
||||
// Adds a path to return children of a given folder
|
||||
storage[resourceInfo.StoragePath("children")] = &subChildrenREST{
|
||||
@@ -226,12 +225,6 @@ func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return b.authorizer
|
||||
}
|
||||
|
||||
var folderValidationRules = struct {
|
||||
maxDepth int
|
||||
}{
|
||||
maxDepth: 5, // why different than folder.MaxNestedFolderDepth?? (4)
|
||||
}
|
||||
|
||||
func (b *FolderAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
|
||||
verb := a.GetOperation()
|
||||
if verb == admission.Create || verb == admission.Update {
|
||||
@@ -259,7 +252,7 @@ func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes,
|
||||
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
return validateOnCreate(ctx, f, b.parents, folderValidationRules.maxDepth)
|
||||
return validateOnCreate(ctx, f, b.parents, folder.MaxNestedFolderDepth)
|
||||
case admission.Delete:
|
||||
return validateOnDelete(ctx, f, b.searcher)
|
||||
case admission.Update:
|
||||
@@ -267,7 +260,7 @@ func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes,
|
||||
if !ok {
|
||||
return fmt.Errorf("obj is not folders.Folder")
|
||||
}
|
||||
return validateOnUpdate(ctx, f, old, b.storage, b.parents, folderValidationRules.maxDepth)
|
||||
return validateOnUpdate(ctx, f, old, b.storage, b.parents, folder.MaxNestedFolderDepth)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
@@ -16,8 +15,8 @@ import (
|
||||
)
|
||||
|
||||
type subAccessREST struct {
|
||||
service folder.Service
|
||||
ac accesscontrol.AccessControl
|
||||
getter rest.Getter
|
||||
ac accesscontrol.AccessControl
|
||||
}
|
||||
|
||||
var _ = rest.Connecter(&subAccessREST{})
|
||||
@@ -47,36 +46,28 @@ func (r *subAccessREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||
}
|
||||
|
||||
func (r *subAccessREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Can view is managed here (and in the Authorizer)
|
||||
f, err := r.service.Get(ctx, &folder.GetFolderQuery{
|
||||
UID: &name,
|
||||
OrgID: ns.OrgID,
|
||||
SignedInUser: user,
|
||||
})
|
||||
// Must be able to get the resource
|
||||
_, err = r.getter.Get(ctx, name, &v1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
access := &folders.FolderAccessInfo{}
|
||||
canEditEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID))
|
||||
canEditEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name))
|
||||
access.CanEdit, _ = r.ac.Evaluate(ctx, user, canEditEvaluator)
|
||||
access.CanSave = access.CanEdit
|
||||
canAdminEvaluator := accesscontrol.EvalAll(
|
||||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)),
|
||||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)),
|
||||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)),
|
||||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)),
|
||||
)
|
||||
access.CanAdmin, _ = r.ac.Evaluate(ctx, user, canAdminEvaluator)
|
||||
canDeleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID))
|
||||
canDeleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name))
|
||||
access.CanDelete, _ = r.ac.Evaluate(ctx, user, canDeleteEvaluator)
|
||||
responder.Object(http.StatusOK, access)
|
||||
}), nil
|
||||
|
||||
+1
-2
@@ -344,10 +344,9 @@ var wireBasicSet = wire.NewSet(
|
||||
dashboardstore.ProvideDashboardStore,
|
||||
folderimpl.ProvideService,
|
||||
wire.Bind(new(folder.Service), new(*folderimpl.Service)),
|
||||
wire.Bind(new(folder.LegacyService), new(*folderimpl.Service)),
|
||||
folderimpl.ProvideStore,
|
||||
wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)),
|
||||
folderimpl.ProvideDashboardFolderStore,
|
||||
wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)),
|
||||
dashboardimportservice.ProvideService,
|
||||
wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)),
|
||||
plugindashboardsservice.ProvideService,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -47,9 +47,8 @@ func ProvideFolderPermissions(
|
||||
}
|
||||
|
||||
fStore := folderimpl.ProvideStore(sqlStore)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
fService := folderimpl.ProvideService(
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore,
|
||||
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
|
||||
acSvc := acimpl.ProvideOSSService(
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,8 +46,10 @@ var (
|
||||
tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards")
|
||||
)
|
||||
|
||||
type UIDLookup = func(ctx context.Context, orgID int64, id int64) (string, error)
|
||||
|
||||
// NewFolderIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope.
|
||||
func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
|
||||
func NewFolderIDScopeResolver(lookup UIDLookup, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
|
||||
prefix := ScopeFoldersProvider.GetResourceScope("")
|
||||
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.NewFolderIDScopeResolver")
|
||||
@@ -66,17 +69,17 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv
|
||||
}
|
||||
|
||||
return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) {
|
||||
folder, err := folderDB.GetFolderByID(ctx, orgID, id)
|
||||
uid, err := lookup(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
|
||||
result, err := GetInheritedScopes(ctx, orgID, uid, folderSvc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append([]string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, result...), nil
|
||||
return append([]string{ScopeFoldersProvider.GetResourceScopeUID(uid)}, result...), nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,24 +2,28 @@ package dashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
)
|
||||
|
||||
func noOpLookup(ctx context.Context, orgID int64, id int64) (string, error) {
|
||||
return fmt.Sprintf("%d", id), nil
|
||||
}
|
||||
|
||||
func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
prefix, _ := NewFolderIDScopeResolver(noOpLookup, foldertest.NewFakeService())
|
||||
require.Equal(t, "folders:id:", prefix)
|
||||
})
|
||||
|
||||
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
|
||||
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
_, resolver := NewFolderIDScopeResolver(noOpLookup, foldertest.NewFakeService())
|
||||
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
@@ -29,7 +33,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
var (
|
||||
orgId = rand.Int63()
|
||||
scope = "folders:id:0"
|
||||
_, resolver = NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
_, resolver = NewFolderIDScopeResolver(noOpLookup, foldertest.NewFakeService())
|
||||
)
|
||||
|
||||
resolved, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
@@ -40,15 +44,15 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
|
||||
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
_, resolver := NewFolderIDScopeResolver(noOpLookup, foldertest.NewFakeService())
|
||||
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
t.Run("returns 'not found' if folder does not exist", func(t *testing.T) {
|
||||
folderStore := foldertest.NewFakeFolderStore(t)
|
||||
folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
|
||||
_, resolver := NewFolderIDScopeResolver(folderStore, foldertest.NewFakeService())
|
||||
_, resolver := NewFolderIDScopeResolver(func(ctx context.Context, orgID int64, id int64) (string, error) {
|
||||
return "", ErrDashboardNotFound
|
||||
}, foldertest.NewFakeService())
|
||||
|
||||
orgId := rand.Int63()
|
||||
scope := "folders:id:10"
|
||||
|
||||
@@ -83,7 +83,6 @@ type DashboardServiceImpl struct {
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
dashboardStore dashboards.Store
|
||||
folderStore folder.FolderStore
|
||||
folderService folder.Service
|
||||
orgService org.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
@@ -373,7 +372,6 @@ var _ registry.BackgroundService = (*DashboardServiceImpl)(nil)
|
||||
func ProvideDashboardServiceImpl(
|
||||
cfg *setting.Cfg,
|
||||
dashboardStore dashboards.Store,
|
||||
folderStore folder.FolderStore,
|
||||
features featuremgmt.FeatureToggles,
|
||||
folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||
ac accesscontrol.AccessControl,
|
||||
@@ -396,7 +394,6 @@ func ProvideDashboardServiceImpl(
|
||||
folderPermissions: folderPermissionsService,
|
||||
ac: ac,
|
||||
acService: acService,
|
||||
folderStore: folderStore,
|
||||
folderService: folderSvc,
|
||||
orgService: orgService,
|
||||
k8sclient: k8sClient,
|
||||
|
||||
@@ -2094,7 +2094,6 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) {
|
||||
|
||||
// Setup mocks and service
|
||||
dashboardStore := &dashboards.FakeDashboardStore{}
|
||||
folderStore := foldertest.FakeFolderStore{}
|
||||
features := featuremgmt.WithFeatures()
|
||||
if tc.featureKubernetesDashboards {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesDashboards)
|
||||
@@ -2107,7 +2106,6 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) {
|
||||
cfg: setting.NewCfg(),
|
||||
log: log.New("test-logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
folderStore: &folderStore,
|
||||
features: features,
|
||||
dashboardPermissions: permService,
|
||||
folderPermissions: permService,
|
||||
|
||||
@@ -17,7 +17,7 @@ type DashboardFolderStoreImpl struct {
|
||||
store db.DB
|
||||
}
|
||||
|
||||
func ProvideDashboardFolderStore(sqlStore db.DB) *DashboardFolderStoreImpl {
|
||||
func newDashboardFolderStore(sqlStore db.DB) *DashboardFolderStoreImpl {
|
||||
return &DashboardFolderStoreImpl{store: sqlStore}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
|
||||
setup()
|
||||
var orgId int64 = 1
|
||||
sqlStore := db.InitTestDB(t)
|
||||
folderStore := ProvideDashboardFolderStore(sqlStore)
|
||||
folderStore := newDashboardFolderStore(sqlStore)
|
||||
folder := insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod")
|
||||
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod")
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
|
||||
setup()
|
||||
var orgId int64 = 1
|
||||
sqlStore := db.InitTestDB(t)
|
||||
folderStore := ProvideDashboardFolderStore(sqlStore)
|
||||
folderStore := newDashboardFolderStore(sqlStore)
|
||||
folder := insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod")
|
||||
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod")
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestIntegrationGetDashFolderStore(t *testing.T) {
|
||||
folderStore := ProvideStore(db)
|
||||
dashboardStore, err := database.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db))
|
||||
require.NoError(t, err)
|
||||
dashFolderStore := ProvideDashboardFolderStore(db)
|
||||
dashFolderStore := newDashboardFolderStore(db)
|
||||
|
||||
orgID := CreateOrg(t, db, cfg)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/grafana/dskit/concurrency"
|
||||
|
||||
dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
@@ -50,13 +49,18 @@ import (
|
||||
|
||||
const FULLPATH_SEPARATOR = "/"
|
||||
|
||||
var (
|
||||
_ folder.LegacyService = (*Service)(nil)
|
||||
_ folder.Service = (*Service)(nil)
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store folder.Store
|
||||
unifiedStore folder.Store
|
||||
db db.DB
|
||||
log *slog.Logger
|
||||
dashboardStore dashboards.Store
|
||||
dashboardFolderStore folder.FolderStore
|
||||
dashboardStore dashboards.Store // folders are saved in the dashboard table
|
||||
dashboardFolderStore *DashboardFolderStoreImpl
|
||||
features featuremgmt.FeatureToggles
|
||||
accessControl accesscontrol.AccessControl
|
||||
k8sclient client.K8sHandler
|
||||
@@ -77,7 +81,6 @@ func ProvideService(
|
||||
ac accesscontrol.AccessControl,
|
||||
bus bus.Bus,
|
||||
dashboardStore dashboards.Store,
|
||||
folderStore folder.FolderStore,
|
||||
userService user.Service,
|
||||
db db.DB, // DB for the (new) nested folder store
|
||||
features featuremgmt.FeatureToggles,
|
||||
@@ -94,7 +97,7 @@ func ProvideService(
|
||||
srv := &Service{
|
||||
log: slog.Default().With("logger", "folder-service"),
|
||||
dashboardStore: dashboardStore,
|
||||
dashboardFolderStore: folderStore,
|
||||
dashboardFolderStore: newDashboardFolderStore(db),
|
||||
store: store,
|
||||
features: features,
|
||||
accessControl: ac,
|
||||
@@ -109,7 +112,7 @@ func ProvideService(
|
||||
|
||||
supportBundles.RegisterSupportItemCollector(srv.supportBundleCollector())
|
||||
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(srv.getUIDFromLegacyID, srv))
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
|
||||
|
||||
k8sHandler := client.NewK8sHandler(
|
||||
@@ -194,6 +197,14 @@ func (s *Service) DBMigration(db db.DB) {
|
||||
s.log.Debug("syncing dashboard and folder tables finished")
|
||||
}
|
||||
|
||||
func (s *Service) getUIDFromLegacyID(ctx context.Context, orgID int64, id int64) (string, error) {
|
||||
f, err := s.dashboardFolderStore.GetFolderByID(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.UID, nil
|
||||
}
|
||||
|
||||
func (s *Service) CountFoldersInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.CountFoldersInOrg")
|
||||
defer span.End()
|
||||
@@ -315,7 +326,7 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, forceLegacy
|
||||
var parents []*folder.Folder
|
||||
var err error
|
||||
if forceLegacy {
|
||||
parents, err = s.GetParentsLegacy(ctx, folder.GetParentsQuery{
|
||||
parents, err = s.getParentsLegacy(ctx, folder.GetParentsQuery{
|
||||
UID: f.UID,
|
||||
OrgID: f.OrgID,
|
||||
})
|
||||
@@ -340,8 +351,8 @@ func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) (
|
||||
return s.getChildrenFromApiServer(ctx, q)
|
||||
}
|
||||
|
||||
func (s *Service) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.GetChildrenLegacy")
|
||||
func (s *Service) getChildrenLegacy(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.getChildrenLegacy")
|
||||
defer span.End()
|
||||
defer func(t time.Time) {
|
||||
parent := q.UID
|
||||
@@ -493,7 +504,7 @@ func (s *Service) GetSharedWithMe(ctx context.Context, q *folder.GetChildrenQuer
|
||||
}
|
||||
var rootFolders []*folder.FolderReference
|
||||
if forceLegacy {
|
||||
rootFolders, err = s.GetChildrenLegacy(ctx, &folder.GetChildrenQuery{UID: "", OrgID: q.OrgID, SignedInUser: q.SignedInUser, Permission: q.Permission})
|
||||
rootFolders, err = s.getChildrenLegacy(ctx, &folder.GetChildrenQuery{UID: "", OrgID: q.OrgID, SignedInUser: q.SignedInUser, Permission: q.Permission})
|
||||
} else {
|
||||
rootFolders, err = s.GetChildren(ctx, &folder.GetChildrenQuery{UID: "", OrgID: q.OrgID, SignedInUser: q.SignedInUser, Permission: q.Permission})
|
||||
}
|
||||
@@ -620,8 +631,8 @@ func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*
|
||||
return s.getParentsFromApiServer(ctx, q)
|
||||
}
|
||||
|
||||
func (s *Service) GetParentsLegacy(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.GetParentsLegacy")
|
||||
func (s *Service) getParentsLegacy(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.getParentsLegacy")
|
||||
defer span.End()
|
||||
if q.UID == accesscontrol.GeneralFolderUID {
|
||||
return nil, nil
|
||||
@@ -1250,42 +1261,6 @@ func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescenda
|
||||
return s.getDescendantCountsFromApiServer(ctx, q)
|
||||
}
|
||||
|
||||
func (s *Service) GetDescendantCountsLegacy(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.GetDescendantCountsLegacy")
|
||||
defer span.End()
|
||||
if q.SignedInUser == nil {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed-in user")
|
||||
}
|
||||
if q.UID == nil || *q.UID == "" {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing UID")
|
||||
}
|
||||
if q.OrgID < 1 {
|
||||
return nil, folder.ErrBadRequest.Errorf("invalid orgID")
|
||||
}
|
||||
|
||||
folders := []string{*q.UID}
|
||||
countsMap := make(folder.DescendantCounts, len(s.registry)+1)
|
||||
descendantFolders, err := s.store.GetDescendants(ctx, q.OrgID, *q.UID)
|
||||
if err != nil {
|
||||
s.log.ErrorContext(ctx, "failed to get descendant folders", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range descendantFolders {
|
||||
folders = append(folders, f.UID)
|
||||
}
|
||||
countsMap[entity.StandardKindFolder] = int64(len(descendantFolders))
|
||||
|
||||
for _, v := range s.registry {
|
||||
c, err := v.CountInFolders(ctx, q.OrgID, folders, q.SignedInUser)
|
||||
if err != nil {
|
||||
s.log.ErrorContext(ctx, "failed to count folder descendants", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
countsMap[v.Kind()] = c
|
||||
}
|
||||
return countsMap, nil
|
||||
}
|
||||
|
||||
// buildSaveDashboardCommand is a simplified version on DashboardServiceImpl.buildSaveDashboardCommand
|
||||
// keeping only the meaningful functionality for folders
|
||||
func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*dashboards.SaveDashboardCommand, error) {
|
||||
|
||||
@@ -12,12 +12,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
|
||||
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -26,6 +22,8 @@ import (
|
||||
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package foldertest
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
folder "github.com/grafana/grafana/pkg/services/folder"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// FakeFolderStore is an autogenerated mock type for the FolderStore type
|
||||
type FakeFolderStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, q
|
||||
func (_m *FakeFolderStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.Folder, error) {
|
||||
ret := _m.Called(ctx, q)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 *folder.Folder
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, folder.GetFolderQuery) (*folder.Folder, error)); ok {
|
||||
return rf(ctx, q)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, folder.GetFolderQuery) *folder.Folder); ok {
|
||||
r0 = rf(ctx, q)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*folder.Folder)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, folder.GetFolderQuery) error); ok {
|
||||
r1 = rf(ctx, q)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetFolderByID provides a mock function with given fields: ctx, orgID, id
|
||||
func (_m *FakeFolderStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) {
|
||||
ret := _m.Called(ctx, orgID, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFolderByID")
|
||||
}
|
||||
|
||||
var r0 *folder.Folder
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (*folder.Folder, error)); ok {
|
||||
return rf(ctx, orgID, id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *folder.Folder); ok {
|
||||
r0 = rf(ctx, orgID, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*folder.Folder)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok {
|
||||
r1 = rf(ctx, orgID, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetFolderByUID provides a mock function with given fields: ctx, orgID, uid
|
||||
func (_m *FakeFolderStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) {
|
||||
ret := _m.Called(ctx, orgID, uid)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFolderByUID")
|
||||
}
|
||||
|
||||
var r0 *folder.Folder
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*folder.Folder, error)); ok {
|
||||
return rf(ctx, orgID, uid)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok {
|
||||
r0 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*folder.Folder)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
|
||||
r1 = rf(ctx, orgID, uid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetFolders provides a mock function with given fields: ctx, orgID, uids
|
||||
func (_m *FakeFolderStore) GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*folder.Folder, error) {
|
||||
ret := _m.Called(ctx, orgID, uids)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFolders")
|
||||
}
|
||||
|
||||
var r0 map[string]*folder.Folder
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, []string) (map[string]*folder.Folder, error)); ok {
|
||||
return rf(ctx, orgID, uids)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, []string) map[string]*folder.Folder); ok {
|
||||
r0 = rf(ctx, orgID, uids)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]*folder.Folder)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, []string) error); ok {
|
||||
r1 = rf(ctx, orgID, uids)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewFakeFolderStore creates a new instance of FakeFolderStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewFakeFolderStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *FakeFolderStore {
|
||||
mock := &FakeFolderStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -59,17 +59,9 @@ func (s *FakeService) GetChildren(ctx context.Context, q *folder.GetChildrenQuer
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (s *FakeService) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
|
||||
return s.ExpectedFoldersRef, s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
return s.ExpectedFolders, s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) GetParentsLegacy(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
return s.ExpectedFolders, s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
return s.ExpectedFolder, s.ExpectedError
|
||||
}
|
||||
@@ -117,9 +109,6 @@ func (s *FakeService) RegisterService(service folder.RegistryService) error {
|
||||
func (s *FakeService) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
return s.ExpectedDescendantCounts, s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) GetDescendantCountsLegacy(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
return s.ExpectedDescendantCounts, s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
|
||||
if s.foldersByUID != nil && len(q.UIDs) > 0 {
|
||||
|
||||
@@ -6,11 +6,20 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
)
|
||||
|
||||
type LegacyService interface {
|
||||
CreateLegacy(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error)
|
||||
GetLegacy(ctx context.Context, q *GetFolderQuery) (*Folder, error)
|
||||
UpdateLegacy(ctx context.Context, cmd *UpdateFolderCommand) (*Folder, error)
|
||||
DeleteLegacy(ctx context.Context, cmd *DeleteFolderCommand) error
|
||||
MoveLegacy(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error)
|
||||
GetFoldersLegacy(ctx context.Context, q GetFoldersQuery) ([]*Folder, error)
|
||||
}
|
||||
|
||||
// The folder.Service is backed by calls forwarding to an apiserver
|
||||
type Service interface {
|
||||
RegisterService(service RegistryService) error
|
||||
|
||||
Create(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error)
|
||||
CreateLegacy(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error)
|
||||
|
||||
// GetFolder takes a GetFolderCommand and returns a folder matching the
|
||||
// request. One of UID, ID or Title must be included. If multiple values
|
||||
@@ -20,19 +29,15 @@ type Service interface {
|
||||
// If ParentUID is not set then the folder will be fetched from the root level.
|
||||
// If WithFullpath is true it computes also the full path of a folder.
|
||||
Get(ctx context.Context, q *GetFolderQuery) (*Folder, error)
|
||||
GetLegacy(ctx context.Context, q *GetFolderQuery) (*Folder, error)
|
||||
|
||||
// Update is used to update a folder's UID, Title and Description. To change
|
||||
// a folder's parent folder, use Move.
|
||||
Update(ctx context.Context, cmd *UpdateFolderCommand) (*Folder, error)
|
||||
UpdateLegacy(ctx context.Context, cmd *UpdateFolderCommand) (*Folder, error)
|
||||
|
||||
Delete(ctx context.Context, cmd *DeleteFolderCommand) error
|
||||
DeleteLegacy(ctx context.Context, cmd *DeleteFolderCommand) error
|
||||
|
||||
// Move changes a folder's parent folder to the requested new parent.
|
||||
Move(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error)
|
||||
MoveLegacy(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error)
|
||||
|
||||
// GetFolders returns org folders that are accessible by the signed in user by their UIDs.
|
||||
// If WithFullpath is true it computes also the full path of a folder.
|
||||
@@ -40,36 +45,18 @@ type Service interface {
|
||||
// If a folder contains a slash in its title, it is escaped with a backslash.
|
||||
// If FullpathUIDs is true it computes a string that contains the UIDs of all parent folders separated by slash.
|
||||
GetFolders(ctx context.Context, q GetFoldersQuery) ([]*Folder, error)
|
||||
GetFoldersLegacy(ctx context.Context, q GetFoldersQuery) ([]*Folder, error)
|
||||
|
||||
// SearchFolders returns a list of folders that match the query.
|
||||
SearchFolders(ctx context.Context, q SearchFoldersQuery) (model.HitList, error)
|
||||
|
||||
// GetChildren returns an array containing all child folders.
|
||||
GetChildren(ctx context.Context, q *GetChildrenQuery) ([]*FolderReference, error)
|
||||
GetChildrenLegacy(ctx context.Context, q *GetChildrenQuery) ([]*FolderReference, error)
|
||||
|
||||
// GetParents returns an array containing add parent folders if nested folders are enabled
|
||||
// otherwise it returns an empty array
|
||||
GetParents(ctx context.Context, q GetParentsQuery) ([]*Folder, error)
|
||||
GetParentsLegacy(ctx context.Context, q GetParentsQuery) ([]*Folder, error)
|
||||
|
||||
GetDescendantCounts(ctx context.Context, q *GetDescendantCountsQuery) (DescendantCounts, error)
|
||||
GetDescendantCountsLegacy(ctx context.Context, q *GetDescendantCountsQuery) (DescendantCounts, error)
|
||||
|
||||
CountFoldersInOrg(ctx context.Context, orgID int64) (int64, error)
|
||||
}
|
||||
|
||||
// FolderStore is a folder store.
|
||||
//
|
||||
//go:generate mockery --name FolderStore --structname FakeFolderStore --outpkg foldertest --output foldertest --filename folder_store_mock.go
|
||||
type FolderStore interface {
|
||||
// Get joins on the dashboard and folder table to return all information needed for a folder
|
||||
Get(ctx context.Context, q GetFolderQuery) (*Folder, error)
|
||||
// GetFolderByUID retrieves a folder by its UID
|
||||
GetFolderByUID(ctx context.Context, orgID int64, uid string) (*Folder, error)
|
||||
// GetFolderByID retrieves a folder by its ID
|
||||
GetFolderByID(ctx context.Context, orgID int64, id int64) (*Folder, error)
|
||||
// GetFolders returns all folders for the given orgID and UIDs.
|
||||
GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*Folder, error)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -1612,10 +1611,9 @@ func createRule(tb testing.TB, store *DBstore, generator *models.AlertRuleGenera
|
||||
func setupFolderService(t testing.TB, sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) folder.Service {
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
inProcBus := bus.ProvideBus(tracer)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
|
||||
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, cfg)
|
||||
|
||||
return testutil.SetupFolderService(t, cfg, sqlStore, dashboardStore, folderStore, inProcBus, features, &actest.FakeAccessControl{ExpectedEvaluate: true})
|
||||
return testutil.SetupFolderService(t, cfg, sqlStore, dashboardStore, inProcBus, features, &actest.FakeAccessControl{ExpectedEvaluate: true})
|
||||
}
|
||||
|
||||
func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) {
|
||||
|
||||
@@ -34,14 +34,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
||||
)
|
||||
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
|
||||
tb.Helper()
|
||||
fStore := folderimpl.ProvideStore(db)
|
||||
return folderimpl.ProvideService(fStore, ac, bus, dashboardStore, folderStore, nil, db,
|
||||
return folderimpl.ProvideService(fStore, ac, bus, dashboardStore, nil, db,
|
||||
features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
}
|
||||
|
||||
func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
||||
func SetupDashboardService(tb testing.TB, sqlStore db.DB, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
|
||||
tb.Helper()
|
||||
|
||||
ac := acmock.New()
|
||||
@@ -58,7 +58,6 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa
|
||||
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
cfg,
|
||||
dashboardStore,
|
||||
fs,
|
||||
features,
|
||||
folderPermissions,
|
||||
ac,
|
||||
|
||||
@@ -134,9 +134,8 @@ func TestIntegrationDashboardFileReader(t *testing.T) {
|
||||
tagService := tagimpl.ProvideService(sql)
|
||||
dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService)
|
||||
require.NoError(t, err)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sql)
|
||||
folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(),
|
||||
dashStore, nil, sql, featuremgmt.WithFeatures(),
|
||||
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
|
||||
t.Run("Reading dashboards from disk", func(t *testing.T) {
|
||||
|
||||
@@ -53,9 +53,8 @@ func TestIntegrationDuplicatesValidator(t *testing.T) {
|
||||
tagService := tagimpl.ProvideService(sql)
|
||||
dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService)
|
||||
require.NoError(t, err)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sql)
|
||||
folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(),
|
||||
dashStore, nil, sql, featuremgmt.WithFeatures(),
|
||||
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), grafanasort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
|
||||
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
|
||||
|
||||
@@ -502,20 +502,18 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
|
||||
require.NoError(t, err)
|
||||
_, err = authimpl.ProvideUserAuthTokenService(sqlStore, nil, quotaService, fakes.NewFakeSecretsService(), cfg, tracing.InitializeTracerForTest(), featuremgmt.WithFeatures())
|
||||
require.NoError(t, err)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
fStore := folderimpl.ProvideStore(sqlStore)
|
||||
dashStore, err := dashboardStore.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
||||
folderSvc := folderimpl.ProvideService(
|
||||
fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
|
||||
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
dashService, err := dashService.ProvideDashboardServiceImpl(
|
||||
cfg,
|
||||
dashStore,
|
||||
folderStore,
|
||||
featuremgmt.WithFeatures(),
|
||||
acmock.NewMockedPermissionsService(),
|
||||
ac,
|
||||
|
||||
@@ -77,7 +77,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe
|
||||
|
||||
fStore := folderimpl.ProvideStore(store)
|
||||
folderSvc := folderimpl.ProvideService(
|
||||
fStore, mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store),
|
||||
fStore, mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore,
|
||||
nil, store, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
||||
|
||||
rootFolders := make([]*folder.Folder, 0, numFolders)
|
||||
|
||||
Reference in New Issue
Block a user