mirror of https://github.com/grafana/grafana
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 implementationspull/62686/head
parent
151e57df70
commit
f143b0a5b2
@ -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 |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
@ -1,13 +1,12 @@ |
|||||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||||
|
|
||||||
package dashboards |
package foldertest |
||||||
|
|
||||||
import ( |
import ( |
||||||
context "context" |
context "context" |
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock" |
|
||||||
|
|
||||||
folder "github.com/grafana/grafana/pkg/services/folder" |
folder "github.com/grafana/grafana/pkg/services/folder" |
||||||
|
mock "github.com/stretchr/testify/mock" |
||||||
) |
) |
||||||
|
|
||||||
// FakeFolderStore is an autogenerated mock type for the FolderStore type
|
// FakeFolderStore is an autogenerated mock type for the FolderStore type
|
||||||
Loading…
Reference in new issue