Chore: Move folder store interface, implementation and test under pkg/services/folder (#62586)

* Chore: Move folder store into folder service package

* Split folder and dashboard store implementations
This commit is contained in:
Sofia Papagiannaki
2023-02-01 15:43:21 +02:00
committed by GitHub
parent 151e57df70
commit f143b0a5b2
24 changed files with 334 additions and 227 deletions

View File

@@ -0,0 +1,86 @@
package folderimpl
import (
"context"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
)
// DashboardStore implements the FolderStore interface
// It fetches folders from the dashboard DB table
type DashboardFolderStoreImpl struct {
store db.DB
}
func ProvideDashboardFolderStore(sqlStore db.DB) *DashboardFolderStoreImpl {
return &DashboardFolderStoreImpl{store: sqlStore}
}
func (d *DashboardFolderStoreImpl) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) {
if title == "" {
return nil, dashboards.ErrFolderTitleEmpty
}
// there is a unique constraint on org_id, folder_id, title
// there are no nested folders so the parent folder id is always 0
dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, Title: title}
err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
if err != nil {
return err
}
if !has {
return dashboards.ErrFolderNotFound
}
dashboard.SetID(dashboard.ID)
dashboard.SetUID(dashboard.UID)
return nil
})
return dashboards.FromDashboard(&dashboard), err
}
func (d *DashboardFolderStoreImpl) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) {
dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, ID: id}
err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
if err != nil {
return err
}
if !has {
return dashboards.ErrFolderNotFound
}
dashboard.SetID(dashboard.ID)
dashboard.SetUID(dashboard.UID)
return nil
})
if err != nil {
return nil, err
}
return dashboards.FromDashboard(&dashboard), nil
}
func (d *DashboardFolderStoreImpl) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) {
if uid == "" {
return nil, dashboards.ErrDashboardIdentifierNotSet
}
dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, UID: uid}
err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
if err != nil {
return err
}
if !has {
return dashboards.ErrFolderNotFound
}
dashboard.SetID(dashboard.ID)
dashboard.SetUID(dashboard.UID)
return nil
})
if err != nil {
return nil, err
}
return dashboards.FromDashboard(&dashboard), nil
}

View File

@@ -0,0 +1,137 @@
package folderimpl
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestIntegrationDashboardFolderStore(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var cfg *setting.Cfg
var dashboardStore *database.DashboardStore
setup := func() {
sqlStore, cfg = db.InitTestDBwithCfg(t)
quotaService := quotatest.New(false, nil)
var err error
dashboardStore, err = database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
}
t.Run("Given dashboard and folder with the same title", func(t *testing.T) {
setup()
var orgId int64 = 1
title := "Very Unique Name"
var sqlStore *sqlstore.SQLStore
var folder1, folder2 *dashboards.Dashboard
sqlStore = db.InitTestDB(t)
folderStore := ProvideDashboardFolderStore(sqlStore)
folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod")
_ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, "prod")
folder1 = insertTestFolder(t, dashboardStore, title, orgId, 0, "prod")
t.Run("GetFolderByTitle should find the folder", func(t *testing.T) {
result, err := folderStore.GetFolderByTitle(context.Background(), orgId, title)
require.NoError(t, err)
require.Equal(t, folder1.ID, result.ID)
})
})
t.Run("GetFolderByUID", func(t *testing.T) {
var orgId int64 = 1
sqlStore := db.InitTestDB(t)
folderStore := ProvideDashboardFolderStore(sqlStore)
folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod")
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, "prod")
t.Run("should return folder by UID", func(t *testing.T) {
d, err := folderStore.GetFolderByUID(context.Background(), orgId, folder.UID)
require.Equal(t, folder.ID, d.ID)
require.NoError(t, err)
})
t.Run("should not find dashboard", func(t *testing.T) {
d, err := folderStore.GetFolderByUID(context.Background(), orgId, dash.UID)
require.Nil(t, d)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
})
t.Run("should search in organization", func(t *testing.T) {
d, err := folderStore.GetFolderByUID(context.Background(), orgId+1, folder.UID)
require.Nil(t, d)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
})
})
t.Run("GetFolderByID", func(t *testing.T) {
var orgId int64 = 1
sqlStore := db.InitTestDB(t)
folderStore := ProvideDashboardFolderStore(sqlStore)
folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod")
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, "prod")
t.Run("should return folder by ID", func(t *testing.T) {
d, err := folderStore.GetFolderByID(context.Background(), orgId, folder.ID)
require.Equal(t, folder.ID, d.ID)
require.NoError(t, err)
})
t.Run("should not find dashboard", func(t *testing.T) {
d, err := folderStore.GetFolderByID(context.Background(), orgId, dash.ID)
require.Nil(t, d)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
})
t.Run("should search in organization", func(t *testing.T) {
d, err := folderStore.GetFolderByID(context.Background(), orgId+1, folder.ID)
require.Nil(t, d)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
})
})
}
func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64, folderId int64, tags ...interface{}) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.ID)
dash.Data.Set("uid", dash.UID)
return dash
}
func insertTestFolder(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64, folderId int64, tags ...interface{}) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.ID)
dash.Data.Set("uid", dash.UID)
return dash
}

View File

@@ -30,7 +30,7 @@ type Service struct {
log log.Logger
cfg *setting.Cfg
dashboardStore dashboards.Store
dashboardFolderStore dashboards.FolderStore
dashboardFolderStore folder.FolderStore
features featuremgmt.FeatureToggles
accessControl accesscontrol.AccessControl
@@ -43,7 +43,7 @@ func ProvideService(
bus bus.Bus,
cfg *setting.Cfg,
dashboardStore dashboards.Store,
folderStore dashboards.FolderStore,
folderStore folder.FolderStore,
db db.DB, // DB for the (new) nested folder store
features featuremgmt.FeatureToggles,
) folder.Service {

View File

@@ -20,6 +20,7 @@ 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/foldertest"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
@@ -52,7 +53,7 @@ func TestIntegrationFolderService(t *testing.T) {
db := sqlstore.InitTestDB(t)
nestedFolderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"}))
folderStore := dashboards.NewFakeFolderStore(t)
folderStore := foldertest.NewFakeFolderStore(t)
cfg := setting.NewCfg()
cfg.RBACEnabled = false
@@ -325,7 +326,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil)
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
cfg := setting.NewCfg()
@@ -361,7 +362,7 @@ func TestNestedFolderService(t *testing.T) {
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil)
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
nestedFolderStore := NewFakeStore()
@@ -385,7 +386,7 @@ func TestNestedFolderService(t *testing.T) {
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
}).Return(nil).Once()
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
nestedFolderStore := NewFakeStore()
@@ -412,7 +413,7 @@ func TestNestedFolderService(t *testing.T) {
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil)
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
nestedFolderStore := NewFakeStore()
@@ -443,7 +444,7 @@ func TestNestedFolderService(t *testing.T) {
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{UID: "newUID"}, nil)
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
nestedFolderStore := NewFakeStore()
@@ -483,7 +484,7 @@ func TestNestedFolderService(t *testing.T) {
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
}).Return(nil).Once()
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil)
nestedFolderStore := NewFakeStore()
@@ -529,7 +530,7 @@ func TestNestedFolderService(t *testing.T) {
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
}).Return(nil).Once()
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
// return an error from the folder store
@@ -562,7 +563,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -583,7 +584,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -604,7 +605,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -630,7 +631,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -652,7 +653,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -678,7 +679,7 @@ func TestNestedFolderService(t *testing.T) {
})
dashStore := &dashboards.FakeDashboardStore{}
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
nestedFolderStore := NewFakeStore()
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
@@ -705,7 +706,7 @@ func TestNestedFolderService(t *testing.T) {
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
}).Return(nil).Once()
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
nestedFolderStore := NewFakeStore()
@@ -738,7 +739,7 @@ func TestNestedFolderService(t *testing.T) {
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
}).Return(nil).Once()
dashboardFolderStore := dashboards.NewFakeFolderStore(t)
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
parents := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth)
@@ -766,7 +767,7 @@ func TestNestedFolderService(t *testing.T) {
})
}
func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore dashboards.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
t.Helper()
// nothing enabled yet