Search: Fix empty folder details for nested folder items (#76504)

* Introduce dashboard.folder_uid column

* Add data migration

* Search: Fix empty folder details for nested folders

* Set `dashboard.folder_uid` and update tests

* Add unique index

* lint

Ignore cyclomatic complexity of func
`(*DashboardServiceImpl).BuildSaveDashboardCommand

* Fix search by folder UID
This commit is contained in:
Sofia Papagiannaki 2023-10-24 10:04:45 +03:00 committed by GitHub
parent 442e533803
commit 03a626f1d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 706 additions and 236 deletions

View File

@ -412,10 +412,13 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
cmd.OrgID = c.SignedInUser.GetOrgID()
cmd.UserID = userID
if cmd.FolderUID != "" {
// nolint:staticcheck
if cmd.FolderUID != "" || cmd.FolderID != 0 {
folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.SignedInUser.GetOrgID(),
UID: &cmd.FolderUID,
OrgID: c.SignedInUser.GetOrgID(),
UID: &cmd.FolderUID,
// nolint:staticcheck
ID: &cmd.FolderID,
SignedInUser: c.SignedInUser,
})
if err != nil {
@ -424,7 +427,9 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
}
return response.Error(http.StatusInternalServerError, "Error while checking folder ID", err)
}
// nolint:staticcheck
cmd.FolderID = folder.ID
cmd.FolderUID = folder.UID
}
dash := cmd.GetDashboardModel()
@ -1073,7 +1078,9 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
saveCmd.Dashboard.Set("version", dash.Version)
saveCmd.Dashboard.Set("uid", dash.UID)
saveCmd.Message = fmt.Sprintf("Restored from version %d", version.Version)
// nolint:staticcheck
saveCmd.FolderID = dash.FolderID
saveCmd.FolderUID = dash.FolderUID
return hs.postDashboard(c, saveCmd)
}

View File

@ -394,6 +394,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
// This tests that a valid request returns correct response
t.Run("Given a correct request for creating a dashboard", func(t *testing.T) {
const folderID int64 = 3
folderUID := "Folder"
const dashID int64 = 2
cmd := dashboards.SaveDashboardCommand{
@ -404,15 +405,19 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}),
Overwrite: true,
FolderID: folderID,
FolderUID: folderUID,
IsFolder: false,
Message: "msg",
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).
Return(&dashboards.Dashboard{ID: dashID, UID: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil)
Return(&dashboards.Dashboard{ID: dashID, UID: "uid", Title: "Dash", Slug: "dash", Version: 2, FolderUID: folderUID, FolderID: folderID}, nil)
mockFolderService := &foldertest.FakeService{
ExpectedFolder: &folder.Folder{ID: 1, UID: folderUID, Title: "Folder"},
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolderService, func(sc *scenarioContext) {
callPostDashboardShouldReturnSuccess(sc)
result := sc.ToJSON()
@ -664,6 +669,12 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Data: fakeDash.Data,
}}
mockSQLStore := dbtest.NewFakeDB()
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
t.Cleanup(func() {
guardian.New = origNewGuardian
})
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
sc.dashboardVersionService = fakeDashboardVersionService
@ -1084,6 +1095,9 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc, sqlStore db.DB) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
cfg := setting.NewCfg()
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolder = &folder.Folder{}
hs := HTTPServer{
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
@ -1097,6 +1111,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
dashboardVersionService: fakeDashboardVersionService,
Kinds: corekind.NewBase(nil),
accesscontrolService: actest.FakeService{},
folderService: folderSvc,
}
sc := setupScenarioContext(t, url)

View File

@ -224,7 +224,7 @@ func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response {
cmd.SignedInUser = c.SignedInUser
theFolder, err := hs.folderService.Move(c.Req.Context(), &cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "move folder failed", err)
return response.ErrOrFallback(http.StatusInternalServerError, "move folder failed", err)
}
folderDTO, err := hs.newToFolderDto(c, theFolder)

View File

@ -58,7 +58,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) {
features: featuremgmt.WithFeatures(),
}
testDash = insertTestDashboard(t, store.db, "dashboard with alerts", 1, 0, false, "alert")
testDash = insertTestDashboard(t, store.db, "dashboard with alerts", 1, 0, "", false, "alert")
evalData, err := simplejson.NewJson([]byte(`{"test": "test"}`))
require.Nil(t, err)
items = []*models.Alert{
@ -337,7 +337,7 @@ func TestIntegrationPausingAlerts(t *testing.T) {
cfg := setting.NewCfg()
sqlStore := sqlStore{db: ss, cfg: cfg, log: log.New(), tagService: tagimpl.ProvideService(ss, ss.Cfg), features: featuremgmt.WithFeatures()}
testDash := insertTestDashboard(t, sqlStore.db, "dashboard with alerts", 1, 0, false, "alert")
testDash := insertTestDashboard(t, sqlStore.db, "dashboard with alerts", 1, 0, "", false, "alert")
alert, err := insertTestAlert("Alerting title", "Alerting message", testDash.OrgID, testDash.ID, simplejson.New(), sqlStore)
require.Nil(t, err)
@ -435,12 +435,13 @@ func (ss *sqlStore) pauseAllAlerts(t *testing.T, pauseState bool) error {
}
func insertTestDashboard(t *testing.T, store db.DB, title string, orgId int64,
folderId int64, isFolder bool, tags ...any) *dashboards.Dashboard {
folderId int64, folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: isFolder,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -121,6 +121,7 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
Overwrite: req.Overwrite,
PluginID: req.PluginId,
FolderID: req.FolderId,
FolderUID: req.FolderUid,
}
dto := &dashboards.SaveDashboardDTO{

View File

@ -38,8 +38,8 @@ func TestIntegrationDashboardACLDataAccess(t *testing.T) {
dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.ID, false, "prod", "webapp")
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
return currentUser.OrgID
}

View File

@ -468,7 +468,7 @@ func saveDashboard(sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitE
dash.Updated = time.Now()
dash.UpdatedBy = userId
metrics.MApiDashboardInsert.Inc()
affectedRows, err = sess.Insert(dash)
affectedRows, err = sess.Nullable("folder_uid").Insert(dash)
} else {
dash.SetVersion(dash.Version + 1)
@ -480,7 +480,7 @@ func saveDashboard(sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitE
dash.UpdatedBy = userId
affectedRows, err = sess.MustCols("folder_id").ID(dash.ID).Update(dash)
affectedRows, err = sess.MustCols("folder_id", "folder_uid").Nullable("folder_uid").ID(dash.ID).Update(dash)
}
if err != nil {
@ -1007,11 +1007,11 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
}
if len(query.FolderUIDs) > 0 {
filters = append(filters, searchstore.FolderUIDFilter{Dialect: d.store.GetDialect(), OrgID: orgID, UIDs: query.FolderUIDs})
filters = append(filters, searchstore.FolderUIDFilter{Dialect: d.store.GetDialect(), OrgID: orgID, UIDs: query.FolderUIDs, NestedFoldersEnabled: d.features.IsEnabled(featuremgmt.FlagNestedFolders)})
}
var res []dashboards.DashboardSearchProjection
sb := &searchstore.Builder{Dialect: d.store.GetDialect(), Filters: filters}
sb := &searchstore.Builder{Dialect: d.store.GetDialect(), Filters: filters, Features: d.features}
limit := query.Limit
if limit < 1 {

View File

@ -49,10 +49,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
var err error
dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
flder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, flder.ID, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, flder.ID, false, "prod")
flder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, flder.ID, flder.UID, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, flder.ID, flder.UID, false, "prod")
currentUser = &user.SignedInUser{
UserID: 1,
OrgID: 1,
@ -147,11 +147,11 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
var err error
dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod")
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
childDash1 = insertTestDashboard(t, dashboardStore, "child dash 1", 1, folder1.ID, false, "prod")
childDash2 = insertTestDashboard(t, dashboardStore, "child dash 2", 1, folder2.ID, false, "prod")
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod")
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, "", true, "prod")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod")
childDash1 = insertTestDashboard(t, dashboardStore, "child dash 1", 1, folder1.ID, folder1.UID, false, "prod")
childDash2 = insertTestDashboard(t, dashboardStore, "child dash 2", 1, folder2.ID, folder2.UID, false, "prod")
currentUser = &user.SignedInUser{
UserID: 1,
@ -186,7 +186,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("and acl is set for one dashboard folder", func(t *testing.T) {
t.Run("and a dashboard is moved from folder without acl to the folder with an acl", func(t *testing.T) {
moveDashboard(t, dashboardStore, 1, childDash2.Data, folder1.ID)
moveDashboard(t, dashboardStore, 1, childDash2.Data, folder1.ID, folder1.UID)
currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID), dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID)}}}
actest.AddUserPermissionToDB(t, sqlStore, currentUser)
@ -204,7 +204,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
})
t.Run("and a dashboard is moved from folder with acl to the folder without an acl", func(t *testing.T) {
setup2()
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID)
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID, folder2.UID)
currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID), dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}}}
actest.AddUserPermissionToDB(t, sqlStore, currentUser)
@ -435,12 +435,13 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
}
func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, dashboard *simplejson.Json,
newFolderId int64) *dashboards.Dashboard {
newFolderId int64, newFolderUID string) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: newFolderId,
FolderUID: newFolderUID,
Dashboard: dashboard,
Overwrite: true,
}

View File

@ -24,9 +24,10 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) {
require.NoError(t, err)
folderCmd := dashboards.SaveDashboardCommand{
OrgID: 1,
FolderID: 0,
IsFolder: true,
OrgID: 1,
FolderID: 0,
FolderUID: "",
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": "test dashboard",
@ -37,9 +38,10 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) {
require.Nil(t, err)
saveDashboardCmd := dashboards.SaveDashboardCommand{
OrgID: 1,
IsFolder: false,
FolderID: dash.ID,
OrgID: 1,
IsFolder: false,
FolderID: dash.ID,
FolderUID: dash.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": "test dashboard",
@ -63,9 +65,10 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) {
t.Run("Deleting orphaned provisioned dashboards", func(t *testing.T) {
saveCmd := dashboards.SaveDashboardCommand{
OrgID: 1,
IsFolder: false,
FolderID: dash.ID,
OrgID: 1,
IsFolder: false,
FolderID: dash.ID,
FolderUID: dash.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": "another_dashboard",

View File

@ -11,11 +11,17 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"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/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/model"
@ -42,10 +48,10 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
var err error
dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, false, "prod")
savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, savedFolder.UID, false, "prod")
savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod")
insertTestRule(t, sqlStore, savedFolder.OrgID, savedFolder.UID)
}
@ -174,7 +180,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
t.Run("Should be able to delete dashboard", func(t *testing.T) {
setup()
dash := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, false, "delete this")
dash := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, "", false, "delete this")
err := dashboardStore.DeleteDashboard(context.Background(), &dashboards.DeleteDashboardCommand{
ID: dash.ID,
@ -248,7 +254,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
t.Run("Should be able to delete empty folder", func(t *testing.T) {
setup()
emptyFolder := insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod", "webapp")
emptyFolder := insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, "", true, "prod", "webapp")
deleteCmd := &dashboards.DeleteDashboardCommand{ID: emptyFolder.ID}
err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd)
@ -522,9 +528,9 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
t.Run("Can delete dashboards in folder", func(t *testing.T) {
setup()
folder := insertTestDashboard(t, dashboardStore, "dash folder", 1, 0, true, "prod", "webapp")
_ = insertTestDashboard(t, dashboardStore, "delete me 1", 1, folder.ID, false, "delete this 1")
_ = insertTestDashboard(t, dashboardStore, "delete me 2", 1, folder.ID, false, "delete this 2")
folder := insertTestDashboard(t, dashboardStore, "dash folder", 1, 0, "", true, "prod", "webapp")
_ = insertTestDashboard(t, dashboardStore, "delete me 1", 1, folder.ID, folder.UID, false, "delete this 1")
_ = insertTestDashboard(t, dashboardStore, "delete me 2", 1, folder.ID, folder.UID, false, "delete this 2")
err := dashboardStore.DeleteDashboardsInFolder(
context.Background(),
@ -577,8 +583,8 @@ func TestIntegrationDashboard_SortingOptions(t *testing.T) {
dashboardStore, err := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, "", false)
dashA := insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, "", false)
assert.NotZero(t, dashA.ID)
assert.Less(t, dashB.ID, dashA.ID)
qNoSort := &dashboards.FindPersistedDashboardsQuery{
@ -629,8 +635,8 @@ func TestIntegrationDashboard_Filter(t *testing.T) {
quotaService := quotatest.New(false, nil)
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, "", false)
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, "", false)
qNoFilter := &dashboards.FindPersistedDashboardsQuery{
SignedInUser: &user.SignedInUser{
OrgID: 1,
@ -674,7 +680,7 @@ func TestGetExistingDashboardByTitleAndFolder(t *testing.T) {
quotaService := quotatest.New(false, nil)
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "Apple", 1, 0, false)
insertTestDashboard(t, dashboardStore, "Apple", 1, 0, "", false)
t.Run("Finds a dashboard with existing name in root directory and throws DashboardWithSameNameInFolderExists error", func(t *testing.T) {
err = sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
_, err = getExistingDashboardByTitleAndFolder(sess, &dashboards.Dashboard{Title: "Apple", OrgID: 1}, sqlStore.GetDialect(), false, false)
@ -692,8 +698,8 @@ func TestGetExistingDashboardByTitleAndFolder(t *testing.T) {
})
t.Run("Finds a dashboard with existing name in specific folder and throws DashboardWithSameNameInFolderExists error", func(t *testing.T) {
savedFolder := insertTestDashboard(t, dashboardStore, "test dash folder", 1, 0, true, "prod", "webapp")
savedDash := insertTestDashboard(t, dashboardStore, "test dash", 1, savedFolder.ID, false, "prod", "webapp")
savedFolder := insertTestDashboard(t, dashboardStore, "test dash folder", 1, 0, "", true, "prod", "webapp")
savedDash := insertTestDashboard(t, dashboardStore, "test dash", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
err = sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
_, err = getExistingDashboardByTitleAndFolder(sess, &dashboards.Dashboard{Title: savedDash.Title, FolderID: savedFolder.ID, OrgID: 1}, sqlStore.GetDialect(), false, false)
return err
@ -702,6 +708,122 @@ func TestGetExistingDashboardByTitleAndFolder(t *testing.T) {
})
}
func TestIntegrationFindDashboardsByTitle(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
sqlStore := db.InitTestDB(t)
cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = func(key string) bool { return false }
quotaService := quotatest.New(false, nil)
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPanelTitleSearch)
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
orgID := int64(1)
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false)
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features)
user := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
orgID: {
dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll},
dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll},
dashboards.ActionFoldersWrite: []string{dashboards.ScopeFoldersAll},
},
},
}
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
// CanEditValue is required to create library elements
CanEditValue: true,
})
t.Cleanup(func() {
guardian.New = origNewGuardian
})
f0, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f0",
SignedInUser: user,
})
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, f0.ID, f0.UID, false)
subfolder, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "subfolder",
ParentUID: f0.UID,
SignedInUser: user,
})
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "dashboard under subfolder", orgID, subfolder.ID, subfolder.UID, false)
type res struct {
title string
folderUID string
folderTitle string
}
testCases := []struct {
desc string
title string
expectedResult res
typ string
}{
{
desc: "find dashboard under general",
title: "dashboard under general",
expectedResult: res{title: "dashboard under general"},
},
{
desc: "find dashboard under f0",
title: "dashboard under f0",
expectedResult: res{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
},
{
desc: "find dashboard under subfolder",
title: "dashboard under subfolder",
expectedResult: res{title: "dashboard under subfolder", folderUID: subfolder.UID, folderTitle: subfolder.Title},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
SignedInUser: user,
Type: tc.typ,
Title: tc.title,
})
require.NoError(t, err)
require.Equal(t, 1, len(res))
r := tc.expectedResult
assert.Equal(t, r.title, res[0].Title)
if r.folderUID != "" {
assert.Equal(t, r.folderUID, res[0].FolderUID)
} else {
assert.Empty(t, res[0].FolderUID)
}
if r.folderTitle != "" {
assert.Equal(t, r.folderTitle, res[0].FolderTitle)
} else {
assert.Empty(t, res[0].FolderTitle)
}
})
}
}
func TestIntegrationFindDashboardsByFolder(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
@ -711,99 +833,240 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = func(key string) bool { return false }
quotaService := quotatest.New(false, nil)
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPanelTitleSearch)
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
orgID := int64(1)
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, false)
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false)
f0 := insertTestDashboard(t, dashboardStore, "f0", orgID, 0, true)
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, f0.ID, false)
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features)
f1 := insertTestDashboard(t, dashboardStore, "f1", orgID, 0, true)
insertTestDashboard(t, dashboardStore, "dashboard under f1", orgID, f1.ID, false)
user := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
orgID: {
dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll},
dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll},
dashboards.ActionFoldersWrite: []string{dashboards.ScopeFoldersAll},
},
},
}
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
// CanEditValue is required to create library elements
CanEditValue: true,
})
t.Cleanup(func() {
guardian.New = origNewGuardian
})
f0, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f0",
SignedInUser: user,
})
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, f0.ID, f0.UID, false)
f1, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f1",
SignedInUser: user,
})
require.NoError(t, err)
insertTestDashboard(t, dashboardStore, "dashboard under f1", orgID, f1.ID, f1.UID, false)
subfolder, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "subfolder",
ParentUID: f0.UID,
SignedInUser: user,
})
require.NoError(t, err)
type res struct {
title string
folderUID string
folderTitle string
}
testCases := []struct {
desc string
folderIDs []int64
folderUIDs []string
expectedResult []string
query string
expectedResult map[string][]res
typ string
}{
{
desc: "find dashboard under general using folder id",
folderIDs: []int64{0},
expectedResult: []string{"dashboard under general"},
desc: "find dashboard under general using folder id",
folderIDs: []int64{0},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under f0 using folder id",
folderIDs: []int64{f0.ID},
expectedResult: []string{"dashboard under f0"},
desc: "find dashboard under general using folder id",
folderIDs: []int64{0},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under f0 or f1 using folder id",
folderIDs: []int64{f0.ID, f1.ID},
expectedResult: []string{"dashboard under f0", "dashboard under f1"},
desc: "find dashboard under f0 using folder id",
folderIDs: []int64{f0.ID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
},
},
{
desc: "find dashboard under general using folder UID",
folderUIDs: []string{folder.GeneralFolderUID},
expectedResult: []string{"dashboard under general"},
desc: "find dashboard under f0 or f1 using folder id",
folderIDs: []int64{f0.ID, f1.ID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
},
},
{
desc: "find dashboard under f0 using folder UID",
folderUIDs: []string{f0.UID},
expectedResult: []string{"dashboard under f0"},
desc: "find dashboard under general using folder UID",
folderUIDs: []string{folder.GeneralFolderUID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under f0 or f1 using folder UID",
folderUIDs: []string{f0.UID, f1.UID},
expectedResult: []string{"dashboard under f0", "dashboard under f1"},
desc: "find dashboard under general using folder UID",
folderUIDs: []string{folder.GeneralFolderUID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under general or f0 using folder id",
folderIDs: []int64{0, f0.ID},
expectedResult: []string{"dashboard under f0", "dashboard under general"},
desc: "find dashboard under f0 using folder UID",
folderUIDs: []string{f0.UID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title}},
},
},
{
desc: "find dashboard under general or f0 or f1 using folder id",
folderIDs: []int64{0, f0.ID, f1.ID},
expectedResult: []string{"dashboard under f0", "dashboard under f1", "dashboard under general"},
desc: "find dashboard under f0 or f1 using folder UID",
folderUIDs: []string{f0.UID, f1.UID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title}},
},
},
{
desc: "find dashboard under general or f0 using folder UID",
folderUIDs: []string{folder.GeneralFolderUID, f0.UID},
expectedResult: []string{"dashboard under f0", "dashboard under general"},
desc: "find dashboard under general or f0 using folder id",
folderIDs: []int64{0, f0.ID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under general or f0 or f1 using folder UID",
folderUIDs: []string{folder.GeneralFolderUID, f0.UID, f1.UID},
expectedResult: []string{"dashboard under f0", "dashboard under f1", "dashboard under general"},
desc: "find dashboard under general or f0 or f1 using folder id",
folderIDs: []int64{0, f0.ID, f1.ID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under general or f0 using folder UID",
folderUIDs: []string{folder.GeneralFolderUID, f0.UID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under general"}},
},
},
{
desc: "find dashboard under general or f0 or f1 using folder UID",
folderUIDs: []string{folder.GeneralFolderUID, f0.UID, f1.UID},
typ: searchstore.TypeDashboard,
expectedResult: map[string][]res{
"": {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
{title: "dashboard under general"}},
featuremgmt.FlagNestedFolders: {{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
{title: "dashboard under f1", folderUID: f1.UID, folderTitle: f1.Title},
{title: "dashboard under general"}},
},
},
{
desc: "find subfolder",
folderUIDs: []string{f0.UID},
typ: searchstore.TypeFolder,
expectedResult: map[string][]res{
"": {},
featuremgmt.FlagNestedFolders: {{title: subfolder.Title, folderUID: f0.UID, folderTitle: f0.Title}},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
SignedInUser: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
orgID: {
dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll},
dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll},
},
},
},
Type: searchstore.TypeDashboard,
FolderIds: tc.folderIDs,
FolderUIDs: tc.folderUIDs,
})
require.NoError(t, err)
require.Equal(t, len(tc.expectedResult), len(res))
for featureFlags := range tc.expectedResult {
t.Run(fmt.Sprintf("%s with featureFlags: %v", tc.desc, featureFlags), func(t *testing.T) {
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(featureFlags), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
SignedInUser: user,
Type: tc.typ,
FolderIds: tc.folderIDs,
FolderUIDs: tc.folderUIDs,
})
require.NoError(t, err)
require.Equal(t, len(tc.expectedResult[featureFlags]), len(res))
for i, r := range tc.expectedResult {
assert.Equal(t, r, res[i].Title)
}
})
for i, r := range tc.expectedResult[featureFlags] {
assert.Equal(t, r.title, res[i].Title)
if r.folderUID != "" {
assert.Equal(t, r.folderUID, res[i].FolderUID)
} else {
assert.Empty(t, res[i].FolderUID)
}
if r.folderTitle != "" {
assert.Equal(t, r.folderTitle, res[i].FolderTitle)
} else {
assert.Empty(t, res[i].FolderTitle)
}
}
})
}
}
}
@ -881,12 +1144,13 @@ func insertTestRule(t *testing.T, sqlStore db.DB, foderOrgID int64, folderUID st
}
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *dashboards.Dashboard {
folderId int64, folderUID string, isFolder bool, tags ...interface{}) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: isFolder,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,

View File

@ -0,0 +1,81 @@
package migrations
import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"xorm.io/xorm"
)
// FolderUIDMigration is a code migration that populates folder_uid column
type FolderUIDMigration struct {
migrator.MigrationBase
}
func (m *FolderUIDMigration) SQL(dialect migrator.Dialect) string {
return "code migration"
}
func (m *FolderUIDMigration) Exec(sess *xorm.Session, mgrtr *migrator.Migrator) error {
// for dashboards the source of truth is the dashboard table
q := `UPDATE dashboard
SET folder_uid = folder.uid
FROM dashboard folder
WHERE dashboard.folder_id = folder.id
AND dashboard.is_folder = ?`
if mgrtr.Dialect.DriverName() == migrator.MySQL {
q = `UPDATE dashboard AS d
LEFT JOIN dashboard AS folder ON d.folder_id = folder.id
SET d.folder_uid = folder.uid
WHERE d.is_folder = ?`
}
r, err := sess.Exec(q, mgrtr.Dialect.BooleanStr(false))
if err != nil {
mgrtr.Logger.Error("Failed to migrate dashboard folder_uid for dashboards", "error", err)
return err
}
dashboardRowsAffected, dashboardRowsAffectedErr := r.RowsAffected()
if dashboardRowsAffectedErr != nil {
mgrtr.Logger.Error("Failed to get dashboard rows affected", "error", dashboardRowsAffectedErr)
}
// for folders the source of truth is the folder table
q = `UPDATE dashboard
SET folder_uid = folder.parent_uid
FROM folder
WHERE dashboard.uid = folder.uid AND dashboard.org_id = folder.org_id
AND dashboard.is_folder = ?`
if mgrtr.Dialect.DriverName() == migrator.MySQL {
q = `UPDATE dashboard
SET folder_uid = (
SELECT folder.parent_uid
FROM folder
WHERE dashboard.uid = folder.uid AND dashboard.org_id = folder.org_id
)
WHERE is_folder = ?`
}
r, err = sess.Exec(q, mgrtr.Dialect.BooleanStr(true))
if err != nil {
mgrtr.Logger.Error("Failed to migrate dashboard folder_uid for folders", "error", err)
return err
}
folderRowsAffected, folderRowsAffectedErr := r.RowsAffected()
if folderRowsAffectedErr != nil {
mgrtr.Logger.Error("Failed to get folder rows affected", "error", folderRowsAffectedErr)
}
mgrtr.Logger.Debug("Migrating dashboard data", "dashboards rows", dashboardRowsAffected, "folder rows", folderRowsAffected)
return nil
}
func AddDashboardFolderMigrations(mg *migrator.Migrator) {
mg.AddMigration("Add folder_uid for dashboard", migrator.NewAddColumnMigration(migrator.Table{Name: "dashboard"}, &migrator.Column{
Name: "folder_uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: true,
}))
mg.AddMigration("Populate dashboard folder_uid column", &FolderUIDMigration{})
mg.AddMigration("Add unique index for dashboard_org_id_folder_uid_title", migrator.NewAddIndexMigration(migrator.Table{Name: "dashboard"}, &migrator.Index{
Cols: []string{"org_id", "folder_uid", "title"}, Type: migrator.UniqueIndex,
}))
}

View File

@ -41,7 +41,8 @@ type Dashboard struct {
UpdatedBy int64
CreatedBy int64
FolderID int64 `xorm:"folder_id"`
FolderID int64 `xorm:"folder_id"`
FolderUID string `xorm:"folder_uid"`
IsFolder bool
HasACL bool `xorm:"has_acl"`
@ -186,6 +187,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash.PluginID = cmd.PluginID
dash.IsFolder = cmd.IsFolder
dash.FolderID = cmd.FolderID
dash.FolderUID = cmd.FolderUID
dash.UpdateSlug()
return dash
}
@ -246,9 +248,10 @@ type SaveDashboardCommand struct {
OrgID int64 `json:"-" xorm:"org_id"`
RestoredFrom int `json:"-"`
PluginID string `json:"-" xorm:"plugin_id"`
FolderID int64 `json:"folderId" xorm:"folder_id"`
FolderUID string `json:"folderUid" xorm:"folder_uid"`
IsFolder bool `json:"isFolder"`
// Deprecated: use FolderUID instead
FolderID int64 `json:"folderId" xorm:"folder_id"`
FolderUID string `json:"folderUid" xorm:"folder_uid"`
IsFolder bool `json:"isFolder"`
UpdatedAt time.Time
}

View File

@ -66,7 +66,7 @@ func TestSaveDashboardCommand_GetDashboardModel(t *testing.T) {
json := simplejson.New()
json.Set("title", "test dash")
cmd := &SaveDashboardCommand{Dashboard: json, FolderID: 1}
cmd := &SaveDashboardCommand{Dashboard: json, FolderID: 1, FolderUID: "1"}
dash := cmd.GetDashboardModel()
assert.Equal(t, int64(1), dash.FolderID)

View File

@ -92,6 +92,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx co
return dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID)
}
//nolint:gocyclo
func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO, shouldValidateAlerts bool,
validateProvisionedDashboard bool) (*dashboards.SaveDashboardCommand, error) {
dash := dto.Dashboard
@ -193,6 +194,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
Overwrite: dto.Overwrite,
UserID: userID,
FolderID: dash.FolderID,
FolderUID: dash.FolderUID,
IsFolder: dash.IsFolder,
PluginID: dash.PluginID,
}

View File

@ -129,6 +129,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: sc.otherSavedFolder.ID,
FolderUID: sc.otherSavedFolder.UID,
UserID: 10000,
Overwrite: true,
}
@ -152,6 +153,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInFolder.Title,
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
UserID: 10000,
Overwrite: true,
}
@ -176,6 +178,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "New dash",
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
UserID: 10000,
Overwrite: true,
}
@ -200,6 +203,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: sc.savedDashInGeneralFolder.FolderID,
FolderUID: sc.savedDashInGeneralFolder.FolderUID,
UserID: 10000,
Overwrite: true,
}
@ -224,6 +228,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: sc.savedDashInFolder.FolderID,
FolderUID: sc.savedDashInFolder.FolderUID,
UserID: 10000,
Overwrite: true,
}
@ -248,6 +253,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: sc.otherSavedFolder.ID,
FolderUID: sc.otherSavedFolder.UID,
UserID: 10000,
Overwrite: true,
}
@ -272,6 +278,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: 0,
FolderUID: "",
UserID: 10000,
Overwrite: true,
}
@ -296,6 +303,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: sc.otherSavedFolder.ID,
FolderUID: sc.otherSavedFolder.UID,
UserID: 10000,
Overwrite: true,
}
@ -320,6 +328,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Dash",
}),
FolderID: 0,
FolderUID: "",
UserID: 10000,
Overwrite: true,
}
@ -351,6 +360,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInFolder.Title,
}),
FolderID: 0,
FolderUID: "",
Overwrite: shouldOverwrite,
}
@ -374,6 +384,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
Overwrite: shouldOverwrite,
}
@ -465,6 +476,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Expect error",
}),
FolderID: 123412321,
FolderUID: "123412321",
Overwrite: shouldOverwrite,
}
@ -481,6 +493,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "test dash 23",
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
Overwrite: shouldOverwrite,
}
@ -498,6 +511,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"version": sc.savedDashInGeneralFolder.Version,
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
Overwrite: shouldOverwrite,
}
@ -521,6 +535,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "test dash 23",
}),
FolderID: 0,
FolderUID: "",
Overwrite: shouldOverwrite,
}
@ -538,6 +553,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"version": sc.savedDashInFolder.Version,
}),
FolderID: 0,
FolderUID: "",
Overwrite: shouldOverwrite,
}
@ -560,6 +576,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInFolder.Title,
}),
FolderID: sc.savedDashInFolder.FolderID,
FolderUID: sc.savedDashInFolder.FolderUID,
Overwrite: shouldOverwrite,
}
@ -576,6 +593,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderID: sc.savedDashInGeneralFolder.FolderID,
FolderUID: sc.savedDashInGeneralFolder.FolderUID,
Overwrite: shouldOverwrite,
}
@ -612,6 +630,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Updated title",
}),
FolderID: sc.savedFolder.ID,
FolderUID: sc.savedFolder.UID,
Overwrite: shouldOverwrite,
}
@ -634,6 +653,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": "Updated title",
}),
FolderID: 0,
FolderUID: "",
Overwrite: shouldOverwrite,
}
@ -696,6 +716,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInFolder.Title,
}),
FolderID: sc.savedDashInFolder.FolderID,
FolderUID: sc.savedDashInFolder.FolderUID,
Overwrite: shouldOverwrite,
}
@ -720,6 +741,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderID: sc.savedDashInGeneralFolder.FolderID,
FolderUID: sc.savedDashInGeneralFolder.FolderUID,
Overwrite: shouldOverwrite,
}
@ -876,9 +898,9 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
guardian.InitAccessControlGuardian(cfg, ac, dashboardService)
savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.ID, sqlStore)
saveTestDashboard(t, "Other saved dash in folder", testOrgID, savedFolder.ID, sqlStore)
savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgID, 0, sqlStore)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.ID, savedFolder.UID, sqlStore)
saveTestDashboard(t, "Other saved dash in folder", testOrgID, savedFolder.ID, savedFolder.UID, sqlStore)
savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgID, 0, "", sqlStore)
otherSavedFolder := saveTestFolder(t, "Other saved folder", testOrgID, sqlStore)
require.Equal(t, "Saved folder", savedFolder.Title)
@ -966,13 +988,14 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
return err
}
func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlStore db.DB) *dashboards.Dashboard {
func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, folderUID string, sqlStore db.DB) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: folderID,
IsFolder: false,
OrgID: orgID,
FolderID: folderID,
FolderUID: folderUID,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,
@ -1014,9 +1037,10 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: 0,
IsFolder: true,
OrgID: orgID,
FolderID: 0,
FolderUID: "",
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -2,6 +2,7 @@ package dashverimpl
import (
"context"
"strconv"
"testing"
"time"
@ -24,7 +25,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) {
dashVerStore := fn(ss)
t.Run("Get a Dashboard ID and version ID", func(t *testing.T) {
savedDash := insertTestDashboard(t, ss, "test dash 26", 1, 0, false, "diff")
savedDash := insertTestDashboard(t, ss, "test dash 26", 1, 0, "", false, "diff")
query := dashver.GetDashboardVersionQuery{
DashboardID: savedDash.ID,
@ -66,7 +67,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) {
t.Run("Clean up old dashboard versions", func(t *testing.T) {
versionsToWrite := 10
for i := 0; i < versionsToWrite-1; i++ {
insertTestDashboard(t, ss, "test dash 53", 1, int64(i), false, "diff-all")
insertTestDashboard(t, ss, "test dash 53", 1, int64(i), strconv.Itoa(i), false, "diff-all")
}
versionIDsToDelete := []any{1, 2, 3, 4}
res, err := dashVerStore.DeleteBatch(
@ -78,7 +79,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) {
assert.EqualValues(t, 4, res)
})
savedDash := insertTestDashboard(t, ss, "test dash 43", 1, 0, false, "diff-all")
savedDash := insertTestDashboard(t, ss, "test dash 43", 1, 0, "", false, "diff-all")
t.Run("Get all versions for a given Dashboard ID", func(t *testing.T) {
query := dashver.ListDashboardVersionsQuery{
DashboardID: savedDash.ID,
@ -134,12 +135,13 @@ var (
)
func insertTestDashboard(t *testing.T, sqlStore db.DB, title string, orgId int64,
folderId int64, isFolder bool, tags ...any) *dashboards.Dashboard {
folderId int64, folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: isFolder,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -37,7 +37,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
sqlStore = db.InitTestDB(t)
folderStore := ProvideDashboardFolderStore(sqlStore)
folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod")
_ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, "prod")
_ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, folder2.UID, "prod")
folder1 = insertTestFolder(t, dashboardStore, title, orgId, 0, "prod")
t.Run("GetFolderByTitle should find the folder", func(t *testing.T) {
@ -52,7 +52,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
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")
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod")
t.Run("should return folder by UID", func(t *testing.T) {
d, err := folderStore.GetFolderByUID(context.Background(), orgId, folder.UID)
@ -76,7 +76,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
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")
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod")
t.Run("should return folder by ID", func(t *testing.T) {
d, err := folderStore.GetFolderByID(context.Background(), orgId, folder.ID)
@ -96,12 +96,13 @@ func TestIntegrationDashboardFolderStore(t *testing.T) {
})
}
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderId int64, tags ...any) *dashboards.Dashboard {
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderID int64, folderUID string, tags ...any) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: false,
OrgID: orgId,
FolderID: folderID,
FolderUID: folderUID,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,
@ -116,12 +117,13 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st
return dash
}
func insertTestFolder(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderId int64, tags ...any) *dashboards.Dashboard {
func insertTestFolder(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderId int64, folderUID string, tags ...any) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: true,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -276,6 +276,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
}
dashFolder := dashboards.NewDashboardFolder(cmd.Title)
dashFolder.OrgID = cmd.OrgID
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) && cmd.ParentUID != "" {
// Check that the user is allowed to create a subfolder in this folder
evaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.ParentUID))
@ -286,11 +289,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
if !hasAccess {
return nil, dashboards.ErrFolderAccessDenied
}
dashFolder.FolderUID = cmd.ParentUID
}
dashFolder := dashboards.NewDashboardFolder(cmd.Title)
dashFolder.OrgID = cmd.OrgID
trimmedUID := strings.TrimSpace(cmd.UID)
if trimmedUID == accesscontrol.GeneralFolderUID {
return nil, dashboards.ErrFolderInvalidUID
@ -325,7 +326,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
User: user,
}
saveDashboardCmd, err := s.BuildSaveDashboardCommand(ctx, dto)
saveDashboardCmd, err := s.buildSaveDashboardCommand(ctx, dto)
if err != nil {
return nil, toFolderError(err)
}
@ -419,6 +420,10 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm
}
dashFolder := queryResult
if cmd.NewParentUID != nil {
dashFolder.FolderUID = *cmd.NewParentUID
}
currentTitle := dashFolder.Title
if !dashFolder.IsFolder {
@ -447,7 +452,7 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm
Overwrite: cmd.Overwrite,
}
saveDashboardCmd, err := s.BuildSaveDashboardCommand(ctx, dto)
saveDashboardCmd, err := s.buildSaveDashboardCommand(ctx, dto)
if err != nil {
return nil, toFolderError(err)
}
@ -621,7 +626,7 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
// if the current folder is already a parent of newparent, we should return error
for _, parent := range parents {
if parent.UID == cmd.UID {
return nil, folder.ErrCircularReference
return nil, folder.ErrCircularReference.Errorf("failed to move folder")
}
}
@ -629,12 +634,34 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
if cmd.NewParentUID != "" {
newParentUID = cmd.NewParentUID
}
return s.store.Update(ctx, folder.UpdateFolderCommand{
UID: cmd.UID,
OrgID: cmd.OrgID,
NewParentUID: &newParentUID,
SignedInUser: cmd.SignedInUser,
})
var f *folder.Folder
if err := s.db.InTransaction(ctx, func(ctx context.Context) error {
if f, err = s.store.Update(ctx, folder.UpdateFolderCommand{
UID: cmd.UID,
OrgID: cmd.OrgID,
NewParentUID: &newParentUID,
SignedInUser: cmd.SignedInUser,
}); err != nil {
return folder.ErrInternal.Errorf("failed to move folder: %w", err)
}
if _, err := s.legacyUpdate(ctx, &folder.UpdateFolderCommand{
UID: cmd.UID,
OrgID: cmd.OrgID,
NewParentUID: &newParentUID,
SignedInUser: cmd.SignedInUser,
// bypass optimistic locking used for dashboards
Overwrite: true,
}); err != nil {
return folder.ErrInternal.Errorf("failed to move legacy folder: %w", err)
}
return nil
}); err != nil {
return nil, err
}
return f, nil
}
// nestedFolderDelete inspects the folder referenced by the cmd argument, deletes all the entries for
@ -735,9 +762,9 @@ func (s *Service) getNestedFolders(ctx context.Context, orgID int64, uid string)
return result, nil
}
// BuildSaveDashboardCommand is a simplified version on DashboardServiceImpl.BuildSaveDashboardCommand
// 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) {
func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*dashboards.SaveDashboardCommand, error) {
dash := dto.Dashboard
dash.OrgID = dto.OrgID
@ -807,6 +834,7 @@ func (s *Service) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards
Overwrite: dto.Overwrite,
UserID: userID,
FolderID: dash.FolderID,
FolderUID: dash.FolderUID,
IsFolder: dash.IsFolder,
PluginID: dash.PluginID,
}

View File

@ -416,8 +416,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
require.NoError(t, err)
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, parent.UID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, subfolder.UID, "prod")
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
@ -491,8 +491,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
require.NoError(t, err)
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, parent.UID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, subfolder.UID, "prod")
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
@ -996,9 +996,10 @@ func TestNestedFolderService(t *testing.T) {
nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("newFolder")}}
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), dbtest.NewFakeDB())
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser})
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser})
require.NoError(t, err)
require.NotNil(t, f)
// the folder is set inside InTransaction() but the fake one is called
// require.NotNil(t, f)
})
t.Run("move to the root folder without folder creation permissions fails", func(t *testing.T) {
@ -1032,9 +1033,10 @@ func TestNestedFolderService(t *testing.T) {
nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersCreate: {}}
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), dbtest.NewFakeDB())
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "", OrgID: orgID, SignedInUser: nestedFolderUser})
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "", OrgID: orgID, SignedInUser: nestedFolderUser})
require.NoError(t, err)
require.NotNil(t, f)
// the folder is set inside InTransaction() but the fake one is called
// require.NotNil(t, f)
})
t.Run("move when parentUID in the current subtree returns error from nested folder service", func(t *testing.T) {

View File

@ -289,9 +289,10 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
// Create Dashboard
saveDashboardCmd := dashboards.SaveDashboardCommand{
OrgID: 1,
FolderID: 1,
IsFolder: false,
OrgID: 1,
FolderID: 1,
FolderUID: "1",
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": "test",

View File

@ -63,9 +63,9 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
require.NoError(t, err)
publicdashboardStore = ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
bDash = insertTestDashboard(t, dashboardStore, "b", orgId, 0, false)
aDash = insertTestDashboard(t, dashboardStore, "a", orgId, 0, false)
cDash = insertTestDashboard(t, dashboardStore, "c", orgId, 0, false)
bDash = insertTestDashboard(t, dashboardStore, "b", orgId, 0, "", false)
aDash = insertTestDashboard(t, dashboardStore, "a", orgId, 0, "", false)
cDash = insertTestDashboard(t, dashboardStore, "c", orgId, 0, "", false)
// these are in order of how they should be returned from ListPUblicDashboards
aPublicDash = insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false, PublicShareType)
@ -178,7 +178,7 @@ func TestIntegrationFindDashboard(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("FindDashboard can get original dashboard by uid", func(t *testing.T) {
@ -208,7 +208,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("ExistsEnabledByAccessToken will return true when at least one public dashboard has a matching access token", func(t *testing.T) {
setup()
@ -281,7 +281,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("ExistsEnabledByDashboardUid Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) {
@ -346,7 +346,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("returns public dashboard by dashboardUid", func(t *testing.T) {
@ -413,7 +413,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
require.NoError(t, err)
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("returns public dashboard by accessToken", func(t *testing.T) {
@ -483,8 +483,8 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", true)
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, false, PublicShareType)
}
@ -562,8 +562,8 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, "", true)
}
t.Run("updates an existing dashboard", func(t *testing.T) {
@ -666,7 +666,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
}
t.Run("GetOrgIdByAccessToken will OrgId when enabled", func(t *testing.T) {
setup()
@ -738,7 +738,7 @@ func TestIntegrationDelete(t *testing.T) {
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
require.NoError(t, err)
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true)
savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType)
}
@ -790,9 +790,9 @@ func TestGetDashboardByFolder(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
pubdashStore := ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, true, PublicShareType)
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, "1", true, PublicShareType)
pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true, PublicShareType)
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, true, PublicShareType)
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, "2", true, PublicShareType)
_ = insertPublicDashboard(t, pubdashStore, dashboard2.UID, dashboard2.OrgID, true, PublicShareType)
pubdashes, err := pubdashStore.FindByDashboardFolder(context.Background(), dashboard)
@ -823,10 +823,10 @@ func TestGetMetrics(t *testing.T) {
require.NoError(t, err)
dashboardStore = store
publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures())
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, false)
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, false)
savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, 0, false)
savedDashboard4 = insertTestDashboard(t, dashboardStore, "testDashie4", 2, 0, false)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", false)
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", false)
savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, 0, "", false)
savedDashboard4 = insertTestDashboard(t, dashboardStore, "testDashie4", 2, 0, "", false)
insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType)
insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, true, PublicShareType)
insertPublicDashboard(t, publicdashboardStore, savedDashboard3.UID, savedDashboard3.OrgID, true, EmailShareType)
@ -868,12 +868,13 @@ func TestGetMetrics(t *testing.T) {
// helper function to insert a dashboard
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
folderId int64, isFolder bool, tags ...any) *dashboards.Dashboard {
folderId int64, folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: isFolder,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -721,7 +721,7 @@ func TestGetQueryDataResponse(t *testing.T) {
"targets": []interface{}{hiddenQuery},
}}
dashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels)
dashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, "", true, []map[string]interface{}{}, customPanels)
isEnabled := true
dto := &SavePublicDashboardDTO{
DashboardUid: dashboard.UID,
@ -1130,7 +1130,7 @@ func TestGetMetricRequest(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
publicDashboard := &PublicDashboard{
Uid: "1",
DashboardUid: dashboard.UID,
@ -1216,8 +1216,8 @@ func TestBuildMetricRequest(t *testing.T) {
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{}, nil)
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
from, to := internal.GetTimeRangeFromDashboard(t, publicDashboard.Data)
service := &PublicDashboardServiceImpl{
@ -1343,7 +1343,7 @@ func TestBuildMetricRequest(t *testing.T) {
"targets": []interface{}{hiddenQuery, nonHiddenQuery},
}}
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels)
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, "", true, []map[string]interface{}{}, customPanels)
reqDTO, err := service.buildMetricRequest(
publicDashboard,
@ -1375,7 +1375,7 @@ func TestBuildAnonymousUser(t *testing.T) {
sqlStore := db.InitTestDB(t)
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
require.NoError(t, err)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
t.Run("will add datasource read and query permissions to user for each datasource in dashboard", func(t *testing.T) {
user := buildAnonymousUser(context.Background(), dashboard)

View File

@ -594,7 +594,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -680,7 +680,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -718,7 +718,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -752,7 +752,7 @@ func TestCreatePublicDashboard(t *testing.T) {
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
templateVars := make([]map[string]any, 1)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, templateVars, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -827,7 +827,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -905,7 +905,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -966,7 +966,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
require.NoError(t, err)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
publicdashboardStore := &FakePublicDashboardStore{}
publicdashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(&PublicDashboard{Uid: "newPubdashUid"}, nil)
@ -1003,7 +1003,7 @@ func TestCreatePublicDashboard(t *testing.T) {
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
service := &PublicDashboardServiceImpl{
@ -1047,8 +1047,8 @@ func TestUpdatePublicDashboard(t *testing.T) {
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard2 := insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
dashboard2 := insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", true, []map[string]any{}, nil)
service := &PublicDashboardServiceImpl{
log: log.New("test.logger"),
@ -1233,7 +1233,7 @@ func TestUpdatePublicDashboard(t *testing.T) {
require.NoError(t, err)
publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures())
serviceWrapper := ProvideServiceWrapper(publicdashboardStore)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]any{}, nil)
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
service := &PublicDashboardServiceImpl{
log: log.New("test.logger"),
@ -1803,7 +1803,7 @@ func AddAnnotationsToDashboard(t *testing.T, dash *dashboards.Dashboard, annotat
}
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
folderId int64, isFolder bool, templateVars []map[string]any, customPanels []any, tags ...any) *dashboards.Dashboard {
folderId int64, folderUID string, isFolder bool, templateVars []map[string]any, customPanels []any, tags ...any) *dashboards.Dashboard {
t.Helper()
var dashboardPanels []any
@ -1852,9 +1852,10 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st
}
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderID: folderId,
IsFolder: isFolder,
OrgID: orgId,
FolderID: folderId,
FolderUID: folderUID,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": title,

View File

@ -1,6 +1,7 @@
package migrations
import (
dashboardFolderMigrations "github.com/grafana/grafana/pkg/services/dashboards/database/migrations"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations/anonservice"
@ -103,6 +104,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
ualert.MigrationServiceMigration(mg)
ualert.CreatedFoldersMigration(mg)
dashboardFolderMigrations.AddDashboardFolderMigrations(mg)
}
func addStarMigrations(mg *Migrator) {

View File

@ -815,8 +815,9 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
// create dashboard under parent folder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: parent.ID,
OrgID: orgID,
FolderID: parent.ID,
FolderUID: parent.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"title": "dashboard under parent folder",
}),
@ -825,8 +826,9 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
// create dashboard under subfolder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: subfolder.ID,
OrgID: orgID,
FolderID: subfolder.ID,
FolderUID: subfolder.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"title": "dashboard under subfolder",
}),

View File

@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
@ -14,8 +15,9 @@ import (
type Builder struct {
// List of FilterWhere/FilterGroupBy/FilterOrderBy/FilterLeftJoin
// to modify the query.
Filters []any
Dialect migrator.Dialect
Filters []any
Dialect migrator.Dialect
Features featuremgmt.FeatureToggles
params []any
sql bytes.Buffer
@ -35,9 +37,15 @@ func (b *Builder) ToSQL(limit, page int64) (string, []any) {
INNER JOIN dashboard ON ids.id = dashboard.id`)
b.sql.WriteString("\n")
b.sql.WriteString(
`LEFT OUTER JOIN dashboard AS folder ON folder.id = dashboard.folder_id
LEFT OUTER JOIN dashboard_tag ON dashboard.id = dashboard_tag.dashboard_id`)
if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) {
b.sql.WriteString(
`LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`)
} else {
b.sql.WriteString(`
LEFT OUTER JOIN dashboard AS folder ON folder.id = dashboard.folder_id`)
}
b.sql.WriteString(`
LEFT OUTER JOIN dashboard_tag ON dashboard.id = dashboard_tag.dashboard_id`)
b.sql.WriteString("\n")
b.sql.WriteString(orderQuery)
@ -58,7 +66,15 @@ func (b *Builder) buildSelect() {
dashboard.is_folder,
dashboard.folder_id,
folder.uid AS folder_uid,
folder.slug AS folder_slug,
`)
if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) {
b.sql.WriteString(`
folder.title AS folder_slug,`)
} else {
b.sql.WriteString(`
folder.slug AS folder_slug,`)
}
b.sql.WriteString(`
folder.title AS folder_title `)
for _, f := range b.Filters {

View File

@ -58,9 +58,10 @@ func (f FolderFilter) Where() (string, []any) {
}
type FolderUIDFilter struct {
Dialect migrator.Dialect
OrgID int64
UIDs []string
Dialect migrator.Dialect
OrgID int64
UIDs []string
NestedFoldersEnabled bool
}
func (f FolderUIDFilter) Where() (string, []any) {
@ -84,10 +85,16 @@ func (f FolderUIDFilter) Where() (string, []any) {
// do nothing
case len(params) == 1:
q = "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid = ?)"
if f.NestedFoldersEnabled {
q = "dashboard.org_id = ? AND dashboard.folder_uid = ?"
}
params = append([]any{f.OrgID}, params...)
default:
sqlArray := "(?" + strings.Repeat(",?", len(params)-1) + ")"
q = "dashboard.folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN " + sqlArray + ")"
if f.NestedFoldersEnabled {
q = "dashboard.org_id = ? AND dashboard.folder_uid IN " + sqlArray
}
params = append([]any{f.OrgID}, params...)
}

View File

@ -46,7 +46,8 @@ func TestBuilder_EqualResults_Basic(t *testing.T) {
searchstore.OrgFilter{OrgId: user.OrgID},
searchstore.TitleSorter{},
},
Dialect: store.GetDialect(),
Dialect: store.GetDialect(),
Features: featuremgmt.WithFeatures(),
}
res := []dashboards.DashboardSearchProjection{}
@ -83,7 +84,8 @@ func TestBuilder_Pagination(t *testing.T) {
searchstore.OrgFilter{OrgId: user.OrgID},
searchstore.TitleSorter{},
},
Dialect: store.GetDialect(),
Dialect: store.GetDialect(),
Features: featuremgmt.WithFeatures(),
}
resPg1 := []dashboards.DashboardSearchProjection{}
@ -243,7 +245,8 @@ func TestBuilder_RBAC(t *testing.T) {
recursiveQueriesAreSupported,
),
},
Dialect: store.GetDialect(),
Dialect: store.GetDialect(),
Features: features,
}
res := []dashboards.DashboardSearchProjection{}