mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
nested folders: support creation of nested folders in folder service when feature flag is set (#58364)
* nested folders: support creation of nested folders in folder service when feature flag is set
This commit is contained in:
@@ -7,12 +7,14 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/events"
|
"github.com/grafana/grafana/pkg/events"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"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/folder"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/search"
|
"github.com/grafana/grafana/pkg/services/search"
|
||||||
@@ -29,7 +31,7 @@ type Service struct {
|
|||||||
dashboardService dashboards.DashboardService
|
dashboardService dashboards.DashboardService
|
||||||
dashboardStore dashboards.Store
|
dashboardStore dashboards.Store
|
||||||
searchService *search.SearchService
|
searchService *search.SearchService
|
||||||
features featuremgmt.FeatureToggles
|
features *featuremgmt.FeatureManager
|
||||||
permissions accesscontrol.FolderPermissionsService
|
permissions accesscontrol.FolderPermissionsService
|
||||||
|
|
||||||
// bus is currently used to publish events that cause scheduler to update rules.
|
// bus is currently used to publish events that cause scheduler to update rules.
|
||||||
@@ -42,17 +44,20 @@ func ProvideService(
|
|||||||
cfg *setting.Cfg,
|
cfg *setting.Cfg,
|
||||||
dashboardService dashboards.DashboardService,
|
dashboardService dashboards.DashboardService,
|
||||||
dashboardStore dashboards.Store,
|
dashboardStore dashboards.Store,
|
||||||
features featuremgmt.FeatureToggles,
|
db db.DB, // DB for the (new) nested folder store
|
||||||
|
features *featuremgmt.FeatureManager,
|
||||||
folderPermissionsService accesscontrol.FolderPermissionsService,
|
folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||||
searchService *search.SearchService,
|
searchService *search.SearchService,
|
||||||
) folder.Service {
|
) folder.Service {
|
||||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore))
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore))
|
||||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore))
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore))
|
||||||
|
store := ProvideStore(db, cfg, features)
|
||||||
return &Service{
|
return &Service{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: log.New("folder-service"),
|
log: log.New("folder-service"),
|
||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
dashboardStore: dashboardStore,
|
dashboardStore: dashboardStore,
|
||||||
|
store: store,
|
||||||
searchService: searchService,
|
searchService: searchService,
|
||||||
features: features,
|
features: features,
|
||||||
permissions: folderPermissionsService,
|
permissions: folderPermissionsService,
|
||||||
@@ -178,8 +183,8 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
|||||||
return nil, toFolderError(err)
|
return nil, toFolderError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var folder *models.Folder
|
var createdFolder *models.Folder
|
||||||
folder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
|
createdFolder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -198,16 +203,45 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
|||||||
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
|
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
_, permissionErr = s.permissions.SetPermissions(ctx, orgID, folder.Uid, permissions...)
|
_, permissionErr = s.permissions.SetPermissions(ctx, orgID, createdFolder.Uid, permissions...)
|
||||||
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
|
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
|
||||||
permissionErr = s.MakeUserAdmin(ctx, orgID, userID, folder.Id, true)
|
permissionErr = s.MakeUserAdmin(ctx, orgID, userID, createdFolder.Id, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if permissionErr != nil {
|
if permissionErr != nil {
|
||||||
s.log.Error("Could not make user admin", "folder", folder.Title, "user", userID, "error", permissionErr)
|
s.log.Error("Could not make user admin", "folder", createdFolder.Title, "user", userID, "error", permissionErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return folder, nil
|
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||||
|
var description string
|
||||||
|
if dash.Data != nil {
|
||||||
|
description = dash.Data.Get("description").MustString()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.store.Create(ctx, folder.CreateFolderCommand{
|
||||||
|
// TODO: Today, if a UID isn't specified, the dashboard store
|
||||||
|
// generates a new UID. The new folder store will need to do this as
|
||||||
|
// well, but for now we take the UID from the newly created folder.
|
||||||
|
UID: dash.Uid,
|
||||||
|
OrgID: orgID,
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
ParentUID: folder.RootFolderUID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// We'll log the error and also roll back the previously-created
|
||||||
|
// (legacy) folder.
|
||||||
|
s.log.Error("error saving folder to nested folder store", err)
|
||||||
|
_, err = s.DeleteFolder(ctx, user, orgID, createdFolder.Uid, true)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("error deleting folder after failed save to nested folder store", err)
|
||||||
|
}
|
||||||
|
return createdFolder, err
|
||||||
|
}
|
||||||
|
// The folder UID is specified (or generated) during creation, so we'll
|
||||||
|
// stop here and return the created model.Folder.
|
||||||
|
}
|
||||||
|
return createdFolder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package folderimpl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@@ -19,6 +21,7 @@ import (
|
|||||||
"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/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"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/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
@@ -34,7 +37,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
|
|||||||
t.Run("should register scope resolvers", func(t *testing.T) {
|
t.Run("should register scope resolvers", func(t *testing.T) {
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
ac := acmock.New()
|
ac := acmock.New()
|
||||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, nil, nil)
|
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{}, nil, nil)
|
||||||
|
|
||||||
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
|
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
|
||||||
})
|
})
|
||||||
@@ -45,20 +48,24 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
t.Skip("skipping integration test")
|
t.Skip("skipping integration test")
|
||||||
}
|
}
|
||||||
t.Run("Folder service tests", func(t *testing.T) {
|
t.Run("Folder service tests", func(t *testing.T) {
|
||||||
store := &dashboards.FakeDashboardStore{}
|
dashStore := &dashboards.FakeDashboardStore{}
|
||||||
|
db := sqlstore.InitTestDB(t)
|
||||||
|
store := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"}))
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.RBACEnabled = false
|
cfg.RBACEnabled = false
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||||
folderPermissions := acmock.NewMockedPermissionsService()
|
folderPermissions := acmock.NewMockedPermissionsService()
|
||||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||||
dashboardService := dashboardsvc.ProvideDashboardService(cfg, store, nil, features, folderPermissions, dashboardPermissions, acmock.New())
|
dashboardService := dashboardsvc.ProvideDashboardService(cfg, dashStore, nil, features, folderPermissions, dashboardPermissions, acmock.New())
|
||||||
|
|
||||||
service := &Service{
|
service := &Service{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: log.New("test-folder-service"),
|
log: log.New("test-folder-service"),
|
||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
dashboardStore: store,
|
dashboardStore: dashStore,
|
||||||
|
store: store,
|
||||||
searchService: nil,
|
searchService: nil,
|
||||||
features: features,
|
features: features,
|
||||||
permissions: folderPermissions,
|
permissions: folderPermissions,
|
||||||
@@ -76,8 +83,8 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
folder.Id = folderId
|
folder.Id = folderId
|
||||||
folder.Uid = folderUID
|
folder.Uid = folderUID
|
||||||
|
|
||||||
store.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
|
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
|
||||||
store.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
|
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
|
||||||
|
|
||||||
t.Run("When get folder by id should return access denied error", func(t *testing.T) {
|
t.Run("When get folder by id should return access denied error", func(t *testing.T) {
|
||||||
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
|
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
|
||||||
@@ -96,13 +103,13 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("When creating folder should return access denied error", func(t *testing.T) {
|
t.Run("When creating folder should return access denied error", func(t *testing.T) {
|
||||||
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
|
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
|
||||||
_, err := service.CreateFolder(context.Background(), usr, orgID, folder.Title, folderUID)
|
_, err := service.CreateFolder(context.Background(), usr, orgID, folder.Title, folderUID)
|
||||||
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
|
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("When updating folder should return access denied error", func(t *testing.T) {
|
t.Run("When updating folder should return access denied error", func(t *testing.T) {
|
||||||
store.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
|
dashStore.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
|
||||||
folder := args.Get(1).(*models.GetDashboardQuery)
|
folder := args.Get(1).(*models.GetDashboardQuery)
|
||||||
folder.Result = models.NewDashboard("dashboard-test")
|
folder.Result = models.NewDashboard("dashboard-test")
|
||||||
folder.Result.IsFolder = true
|
folder.Result.IsFolder = true
|
||||||
@@ -134,11 +141,11 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
dash.Id = rand.Int63()
|
dash.Id = rand.Int63()
|
||||||
f := models.DashboardToFolder(dash)
|
f := models.DashboardToFolder(dash)
|
||||||
|
|
||||||
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
|
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
|
||||||
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
|
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
|
||||||
store.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
|
dashStore.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
|
||||||
|
|
||||||
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "")
|
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "someuid")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, f, actualFolder)
|
require.Equal(t, f, actualFolder)
|
||||||
})
|
})
|
||||||
@@ -157,9 +164,9 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
dashboardFolder.Uid = util.GenerateShortUID()
|
dashboardFolder.Uid = util.GenerateShortUID()
|
||||||
f := models.DashboardToFolder(dashboardFolder)
|
f := models.DashboardToFolder(dashboardFolder)
|
||||||
|
|
||||||
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
|
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
|
||||||
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
|
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
|
||||||
store.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil)
|
dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil)
|
||||||
|
|
||||||
req := &models.UpdateFolderCommand{
|
req := &models.UpdateFolderCommand{
|
||||||
Uid: dashboardFolder.Uid,
|
Uid: dashboardFolder.Uid,
|
||||||
@@ -175,10 +182,10 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
f := models.NewFolder(util.GenerateShortUID())
|
f := models.NewFolder(util.GenerateShortUID())
|
||||||
f.Id = rand.Int63()
|
f.Id = rand.Int63()
|
||||||
f.Uid = util.GenerateShortUID()
|
f.Uid = util.GenerateShortUID()
|
||||||
store.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
|
dashStore.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
|
||||||
|
|
||||||
var actualCmd *models.DeleteDashboardCommand
|
var actualCmd *models.DeleteDashboardCommand
|
||||||
store.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||||
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
|
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
|
||||||
}).Return(nil).Once()
|
}).Return(nil).Once()
|
||||||
|
|
||||||
@@ -204,7 +211,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
expected := models.NewFolder(util.GenerateShortUID())
|
expected := models.NewFolder(util.GenerateShortUID())
|
||||||
expected.Id = rand.Int63()
|
expected.Id = rand.Int63()
|
||||||
|
|
||||||
store.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil)
|
dashStore.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil)
|
||||||
|
|
||||||
actual, err := service.GetFolderByID(context.Background(), usr, expected.Id, orgID)
|
actual, err := service.GetFolderByID(context.Background(), usr, expected.Id, orgID)
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
@@ -215,7 +222,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
expected := models.NewFolder(util.GenerateShortUID())
|
expected := models.NewFolder(util.GenerateShortUID())
|
||||||
expected.Uid = util.GenerateShortUID()
|
expected.Uid = util.GenerateShortUID()
|
||||||
|
|
||||||
store.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil)
|
dashStore.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil)
|
||||||
|
|
||||||
actual, err := service.GetFolderByUID(context.Background(), usr, orgID, expected.Uid)
|
actual, err := service.GetFolderByUID(context.Background(), usr, orgID, expected.Uid)
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
@@ -225,7 +232,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
t.Run("When get folder by title should return folder", func(t *testing.T) {
|
t.Run("When get folder by title should return folder", func(t *testing.T) {
|
||||||
expected := models.NewFolder("TEST-" + util.GenerateShortUID())
|
expected := models.NewFolder("TEST-" + util.GenerateShortUID())
|
||||||
|
|
||||||
store.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil)
|
dashStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil)
|
||||||
|
|
||||||
actual, err := service.GetFolderByTitle(context.Background(), usr, orgID, expected.Title)
|
actual, err := service.GetFolderByTitle(context.Background(), usr, orgID, expected.Title)
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
@@ -326,3 +333,98 @@ func TestFolderService(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreate_NestedFolders(t *testing.T) {
|
||||||
|
t.Run("with feature flag unset", func(t *testing.T) {
|
||||||
|
ctx := appcontext.WithUser(context.Background(), usr)
|
||||||
|
store := &FakeStore{}
|
||||||
|
dashStore := dashboards.FakeDashboardStore{}
|
||||||
|
dashboardsvc := dashboards.FakeDashboardService{}
|
||||||
|
// nothing enabled yet
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.RBACEnabled = false
|
||||||
|
features := featuremgmt.WithFeatures()
|
||||||
|
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||||
|
foldersvc := &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("test-folder-service"),
|
||||||
|
dashboardService: &dashboardsvc,
|
||||||
|
dashboardStore: &dashStore,
|
||||||
|
store: store,
|
||||||
|
features: features,
|
||||||
|
}
|
||||||
|
|
||||||
|
// dashboard store & service commands that should be called.
|
||||||
|
dashboardsvc.On("BuildSaveDashboardCommand",
|
||||||
|
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
|
||||||
|
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
|
||||||
|
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
|
||||||
|
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
|
||||||
|
|
||||||
|
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
|
||||||
|
require.False(t, store.CreateCalled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with nested folder feature flag on", func(t *testing.T) {
|
||||||
|
ctx := appcontext.WithUser(context.Background(), usr)
|
||||||
|
store := &FakeStore{}
|
||||||
|
dashStore := &dashboards.FakeDashboardStore{}
|
||||||
|
dashboardsvc := &dashboards.FakeDashboardService{}
|
||||||
|
// nothing enabled yet
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.RBACEnabled = false
|
||||||
|
features := featuremgmt.WithFeatures("nestedFolders")
|
||||||
|
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||||
|
foldersvc := &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("test-folder-service"),
|
||||||
|
dashboardService: dashboardsvc,
|
||||||
|
dashboardStore: dashStore,
|
||||||
|
store: store,
|
||||||
|
features: features,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("create, no error", func(t *testing.T) {
|
||||||
|
// dashboard store & service commands that should be called.
|
||||||
|
dashboardsvc.On("BuildSaveDashboardCommand",
|
||||||
|
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
|
||||||
|
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
|
||||||
|
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
|
||||||
|
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
|
||||||
|
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// CreateFolder should also call the folder store's create method.
|
||||||
|
require.True(t, store.CreateCalled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("create returns error from nested folder service", func(t *testing.T) {
|
||||||
|
// This test creates and deletes the dashboard, so needs some extra setup.
|
||||||
|
g := guardian.New
|
||||||
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{})
|
||||||
|
|
||||||
|
// dashboard store & service commands that should be called.
|
||||||
|
dashboardsvc.On("BuildSaveDashboardCommand",
|
||||||
|
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
|
||||||
|
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
|
||||||
|
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
|
||||||
|
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
|
||||||
|
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
|
||||||
|
|
||||||
|
// return an error from the folder store
|
||||||
|
store.ExpectedError = errors.New("FAILED")
|
||||||
|
|
||||||
|
// the service return success as long as the legacy create succeeds
|
||||||
|
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||||
|
require.Error(t, err, "FAILED")
|
||||||
|
|
||||||
|
// CreateFolder should also call the folder store's create method.
|
||||||
|
require.True(t, store.CreateCalled)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
guardian.New = g
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ type sqlStore struct {
|
|||||||
db db.DB
|
db db.DB
|
||||||
log log.Logger
|
log log.Logger
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
fm featuremgmt.FeatureManager
|
fm featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
// sqlStore implements the store interface.
|
// sqlStore implements the store interface.
|
||||||
var _ store = (*sqlStore)(nil)
|
var _ store = (*sqlStore)(nil)
|
||||||
|
|
||||||
func ProvideStore(db db.DB, cfg *setting.Cfg, features featuremgmt.FeatureManager) *sqlStore {
|
func ProvideStore(db db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) *sqlStore {
|
||||||
return &sqlStore{db: db, log: log.New("folder-store"), cfg: cfg, fm: features}
|
return &sqlStore{db: db, log: log.New("folder-store"), cfg: cfg, fm: features}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"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"
|
||||||
@@ -13,8 +16,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntegrationCreate(t *testing.T) {
|
func TestIntegrationCreate(t *testing.T) {
|
||||||
@@ -24,7 +25,7 @@ func TestIntegrationCreate(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@@ -162,7 +163,7 @@ func TestIntegrationDelete(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@@ -210,7 +211,7 @@ func TestIntegrationUpdate(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@@ -315,7 +316,7 @@ func TestIntegrationGet(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@@ -396,7 +397,7 @@ func TestIntegrationGetParents(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@@ -460,7 +461,7 @@ func TestIntegrationGetChildren(t *testing.T) {
|
|||||||
t.Skip("skipping until folder migration is merged")
|
t.Skip("skipping until folder migration is merged")
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures())
|
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ type FakeStore struct {
|
|||||||
ExpectedFolders []*folder.Folder
|
ExpectedFolders []*folder.Folder
|
||||||
ExpectedFolder *folder.Folder
|
ExpectedFolder *folder.Folder
|
||||||
ExpectedError error
|
ExpectedError error
|
||||||
|
|
||||||
|
CreateCalled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeStore() *FakeStore {
|
func NewFakeStore() *FakeStore {
|
||||||
@@ -19,6 +21,7 @@ func NewFakeStore() *FakeStore {
|
|||||||
var _ store = (*FakeStore)(nil)
|
var _ store = (*FakeStore)(nil)
|
||||||
|
|
||||||
func (f *FakeStore) Create(ctx context.Context, cmd folder.CreateFolderCommand) (*folder.Folder, error) {
|
func (f *FakeStore) Create(ctx context.Context, cmd folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||||
|
f.CreateCalled = true
|
||||||
return f.ExpectedFolder, f.ExpectedError
|
return f.ExpectedFolder, f.ExpectedError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var ErrInternal = errutil.NewBase(errutil.StatusInternal, "folder.internal")
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
GeneralFolderUID = "general"
|
GeneralFolderUID = "general"
|
||||||
|
RootFolderUID = ""
|
||||||
MaxNestedFolderDepth = 8
|
MaxNestedFolderDepth = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S
|
|||||||
cfg, dashboardStore, nil,
|
cfg, dashboardStore, nil,
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
)
|
)
|
||||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil)
|
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
|
||||||
t.Logf("Creating folder with title and UID %q", title)
|
t.Logf("Creating folder with title and UID %q", title)
|
||||||
folder, err := s.CreateFolder(context.Background(), &user, user.OrgID, title, title)
|
folder, err := s.CreateFolder(context.Background(), &user, user.OrgID, title, title)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -420,7 +420,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
service := LibraryElementService{
|
service := LibraryElementService{
|
||||||
Cfg: sqlStore.Cfg,
|
Cfg: sqlStore.Cfg,
|
||||||
SQLStore: sqlStore,
|
SQLStore: sqlStore,
|
||||||
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, features, folderPermissions, nil),
|
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
usr := user.SignedInUser{
|
usr := user.SignedInUser{
|
||||||
|
|||||||
@@ -717,7 +717,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.
|
|||||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||||
d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac)
|
d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac)
|
||||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil)
|
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
|
||||||
|
|
||||||
t.Logf("Creating folder with title and UID %q", title)
|
t.Logf("Creating folder with title and UID %q", title)
|
||||||
folder, err := s.CreateFolder(context.Background(), user, user.OrgID, title, title)
|
folder, err := s.CreateFolder(context.Background(), user, user.OrgID, title, title)
|
||||||
@@ -819,7 +819,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
cfg, dashboardStore, &alerting.DashAlertExtractorService{},
|
cfg, dashboardStore, &alerting.DashAlertExtractorService{},
|
||||||
features, folderPermissions, dashboardPermissions, ac,
|
features, folderPermissions, dashboardPermissions, ac,
|
||||||
)
|
)
|
||||||
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardService, dashboardStore, features, folderPermissions, nil)
|
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil)
|
||||||
|
|
||||||
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
|
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
|
||||||
service := LibraryPanelService{
|
service := LibraryPanelService{
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
|||||||
)
|
)
|
||||||
|
|
||||||
bus := bus.ProvideBus(tracing.InitializeTracerForTest())
|
bus := bus.ProvideBus(tracing.InitializeTracerForTest())
|
||||||
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardService, dashboardStore, features, folderPermissions, nil)
|
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil)
|
||||||
|
|
||||||
ng, err := ngalert.ProvideService(
|
ng, err := ngalert.ProvideService(
|
||||||
cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil,
|
cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil,
|
||||||
|
|||||||
Reference in New Issue
Block a user