Nested Folders: Support getting of nested folder in folder service wh… (#58597)

* Nested Folders: Support getting of nested folder in folder service when feature flag is set

* Fix lint

* Fix some tests

* Fix ngalert test

* ngalert fix

* Fix API tests

* Fix some tests and lint

* Fix lint 2

* Fix library elements and panels

* Add access control to get folder

* Cleanup and minor test change
This commit is contained in:
idafurjes
2022-11-11 14:28:24 +01:00
committed by GitHub
parent 88a829e103
commit 080ea88af7
39 changed files with 372 additions and 420 deletions

View File

@@ -53,7 +53,7 @@ func NewFolderNameScopeResolver(db Store) (string, ac.ScopeAttributeResolver) {
if err != nil {
return nil, err
}
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.Uid)}, nil
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, nil
})
}
@@ -79,7 +79,7 @@ func NewFolderIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) {
return nil, err
}
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.Uid)}, nil
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, nil
})
}
@@ -142,7 +142,7 @@ func resolveDashboardScope(ctx context.Context, db Store, orgID int64, dashboard
if err != nil {
return nil, err
}
folderUID = folder.Uid
folderUID = folder.UID
}
return []string{

View File

@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/util"
)
@@ -29,9 +30,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
orgId := rand.Int63()
title := "Very complex :title with: and /" + util.GenerateShortUID()
db := models.NewFolder(title)
db.Id = rand.Int63()
db.Uid = util.GenerateShortUID()
db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()}
dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once()
scope := "folders:name:" + title
@@ -40,7 +39,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
require.NoError(t, err)
require.Len(t, resolvedScopes, 1)
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScopes[0])
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.UID), resolvedScopes[0])
dashboardStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title)
})
@@ -88,17 +87,17 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
orgId := rand.Int63()
uid := util.GenerateShortUID()
db := &models.Folder{Id: rand.Int63(), Uid: uid}
db := &folder.Folder{ID: rand.Int63(), UID: uid}
dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once()
scope := "folders:id:" + strconv.FormatInt(db.Id, 10)
scope := "folders:id:" + strconv.FormatInt(db.ID, 10)
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.NoError(t, err)
require.Len(t, resolvedScopes, 1)
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScopes[0])
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.UID), resolvedScopes[0])
dashboardStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.Id)
dashboardStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.ID)
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
dashboardStore := &FakeDashboardStore{}
@@ -157,18 +156,18 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
_, resolver := NewDashboardIDScopeResolver(store)
orgID := rand.Int63()
folder := &models.Folder{Id: 2, Uid: "2"}
dashboard := &models.Dashboard{Id: 1, FolderId: folder.Id, Uid: "1"}
folder := &folder.Folder{ID: 2, UID: "2"}
dashboard := &models.Dashboard{Id: 1, FolderId: folder.ID, Uid: "1"}
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, folder.Id).Return(folder, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once()
scope := ac.Scope("dashboards", "id", strconv.FormatInt(dashboard.Id, 10))
resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope)
require.NoError(t, err)
require.Len(t, resolvedScopes, 2)
require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.Uid), resolvedScopes[0])
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.Uid), resolvedScopes[1])
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1])
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
@@ -203,18 +202,18 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) {
_, resolver := NewDashboardUIDScopeResolver(store)
orgID := rand.Int63()
folder := &models.Folder{Id: 2, Uid: "2"}
dashboard := &models.Dashboard{Id: 1, FolderId: folder.Id, Uid: "1"}
folder := &folder.Folder{ID: 2, UID: "2"}
dashboard := &models.Dashboard{Id: 1, FolderId: folder.ID, Uid: "1"}
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, folder.Id).Return(folder, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once()
scope := ac.Scope("dashboards", "uid", dashboard.Uid)
resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope)
require.NoError(t, err)
require.Len(t, resolvedScopes, 2)
require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.Uid), resolvedScopes[0])
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.Uid), resolvedScopes[1])
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1])
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/folder"
)
// DashboardService is a service for operating on dashboards.
@@ -89,9 +90,9 @@ type Store interface {
//go:generate mockery --name FolderStore --structname FakeFolderStore --inpackage --filename folder_store_mock.go
type FolderStore interface {
// GetFolderByTitle retrieves a folder by its title
GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error)
GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error)
// GetFolderByUID retrieves a folder by its UID
GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error)
GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error)
// GetFolderByID retrieves a folder by its ID
GetFolderByID(ctx context.Context, orgID int64, id int64) (*models.Folder, error)
GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
@@ -74,7 +75,7 @@ func (d *DashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashbo
return isParentFolderChanged, nil
}
func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error) {
func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) {
if title == "" {
return nil, dashboards.ErrFolderTitleEmpty
}
@@ -94,10 +95,10 @@ func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, titl
dashboard.SetUid(dashboard.Uid)
return nil
})
return models.DashboardToFolder(&dashboard), err
return folder.FromDashboard(&dashboard), err
}
func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*models.Folder, error) {
func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) {
dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Id: id}
err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
has, err := sess.Table(&models.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
@@ -114,10 +115,10 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6
if err != nil {
return nil, err
}
return models.DashboardToFolder(&dashboard), nil
return folder.FromDashboard(&dashboard), nil
}
func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error) {
func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) {
if uid == "" {
return nil, dashboards.ErrDashboardIdentifierNotSet
}
@@ -138,7 +139,7 @@ func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid st
if err != nil {
return nil, err
}
return models.DashboardToFolder(&dashboard), nil
return folder.FromDashboard(&dashboard), nil
}
func (d *DashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*models.DashboardProvisioning, error) {

View File

@@ -481,7 +481,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("GetFolderByTitle should find the folder", func(t *testing.T) {
result, err := dashboardStore.GetFolderByTitle(context.Background(), orgId, title)
require.NoError(t, err)
require.Equal(t, folder1.Id, result.Id)
require.Equal(t, folder1.Id, result.ID)
})
})
@@ -494,7 +494,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("should return folder by UID", func(t *testing.T) {
d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, folder.Uid)
require.Equal(t, folder.Id, d.Id)
require.Equal(t, folder.Id, d.ID)
require.NoError(t, err)
})
t.Run("should not find dashboard", func(t *testing.T) {
@@ -518,7 +518,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("should return folder by ID", func(t *testing.T) {
d, err := dashboardStore.GetFolderByID(context.Background(), orgId, folder.Id)
require.Equal(t, folder.Id, d.Id)
require.Equal(t, folder.Id, d.ID)
require.NoError(t, err)
})
t.Run("should not find dashboard", func(t *testing.T) {

View File

@@ -600,5 +600,5 @@ func (dr DashboardServiceImpl) CountDashboardsInFolder(ctx context.Context, quer
return 0, err
}
return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.Id, OrgID: u.OrgID})
return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.ID, OrgID: u.OrgID})
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@@ -226,7 +227,7 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Count dashboards in folder", func(t *testing.T) {
fakeStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
fakeStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
fakeStore.On("CountDashboardsInFolder", mock.Anything, mock.AnythingOfType("*dashboards.CountDashboardsInFolderRequest")).Return(int64(3), nil)
// set up a ctx with signed in user

View File

@@ -5,6 +5,7 @@ package dashboards
import (
context "context"
folder "github.com/grafana/grafana/pkg/services/folder"
models "github.com/grafana/grafana/pkg/models"
mock "github.com/stretchr/testify/mock"
)
@@ -194,15 +195,15 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query
}
// GetFolderByID provides a mock function with given fields: ctx, orgID, id
func (_m *FakeDashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*models.Folder, error) {
func (_m *FakeDashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) {
ret := _m.Called(ctx, orgID, id)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *models.Folder); ok {
var r0 *folder.Folder
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).(*models.Folder)
r0 = ret.Get(0).(*folder.Folder)
}
}
@@ -217,15 +218,15 @@ func (_m *FakeDashboardStore) GetFolderByID(ctx context.Context, orgID int64, id
}
// GetFolderByTitle provides a mock function with given fields: ctx, orgID, title
func (_m *FakeDashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error) {
func (_m *FakeDashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) {
ret := _m.Called(ctx, orgID, title)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.Folder); ok {
var r0 *folder.Folder
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok {
r0 = rf(ctx, orgID, title)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
r0 = ret.Get(0).(*folder.Folder)
}
}
@@ -240,15 +241,15 @@ func (_m *FakeDashboardStore) GetFolderByTitle(ctx context.Context, orgID int64,
}
// GetFolderByUID provides a mock function with given fields: ctx, orgID, uid
func (_m *FakeDashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error) {
func (_m *FakeDashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) {
ret := _m.Called(ctx, orgID, uid)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.Folder); ok {
var r0 *folder.Folder
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).(*models.Folder)
r0 = ret.Get(0).(*folder.Folder)
}
}