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

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

* Fix lint

* Fix some tests

* Fix ngalert test

* ngalert fix

* Fix API tests

* Fix some tests and lint

* Fix lint 2

* Fix library elements and panels

* Add access control to get folder

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

View File

@ -24,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
pref "github.com/grafana/grafana/pkg/services/preference" pref "github.com/grafana/grafana/pkg/services/preference"
@ -394,14 +395,17 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
cmd.OrgId = c.OrgID cmd.OrgId = c.OrgID
cmd.UserId = c.UserID cmd.UserId = c.UserID
if cmd.FolderUid != "" { if cmd.FolderUid != "" {
folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgID, cmd.FolderUid) folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.OrgID,
UID: &cmd.FolderUid,
})
if err != nil { if err != nil {
if errors.Is(err, dashboards.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(400, "Folder not found", err) return response.Error(400, "Folder not found", err)
} }
return response.Error(500, "Error while checking folder ID", err) return response.Error(500, "Error while checking folder ID", err)
} }
cmd.FolderId = folder.Id cmd.FolderId = folder.ID
} }
dash := cmd.GetDashboardModel() dash := cmd.GetDashboardModel()

View File

@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboardversion/dashvertest" "github.com/grafana/grafana/pkg/services/dashboardversion/dashvertest"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/live"
@ -642,8 +643,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")). dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).
Return(&models.Dashboard{Id: dashID, Uid: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil) Return(&models.Dashboard{Id: dashID, Uid: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil)
mockFolder := &fakeFolderService{ mockFolder := &foldertest.FakeService{
GetFolderByUIDResult: &models.Folder{Id: 1, Uid: "folderUID", Title: "Folder"}, ExpectedFolder: &folder.Folder{ID: 1, UID: "folderUID", Title: "Folder"},
} }
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) { postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) {
@ -673,8 +674,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashboardService := dashboards.NewFakeDashboardService(t) dashboardService := dashboards.NewFakeDashboardService(t)
mockFolder := &fakeFolderService{ mockFolder := &foldertest.FakeService{
GetFolderByUIDError: errors.New("Error while searching Folder ID"), ExpectedError: errors.New("Error while searching Folder ID"),
} }
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) { postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) {

View File

@ -67,13 +67,14 @@ func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
// 404: notFoundError // 404: notFoundError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgID, web.Params(c.Req)[":uid"]) uid := web.Params(c.Req)[":uid"]
folder, err := hs.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, UID: &uid})
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser) g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, folder)) return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, folder))
} }
// swagger:route GET /folders/id/{folder_id} folders getFolderByID // swagger:route GET /folders/id/{folder_id} folders getFolderByID
@ -93,13 +94,13 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
if err != nil { if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err) return response.Error(http.StatusBadRequest, "id is invalid", err)
} }
folder, err := hs.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, id, c.OrgID) folder, err := hs.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{ID: &id, OrgID: c.OrgID})
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser) g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, folder)) return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, folder))
} }
// swagger:route POST /folders folders createFolder // swagger:route POST /folders folders createFolder
@ -182,8 +183,8 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response {
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g := guardian.New(c.Req.Context(), result.Id, c.OrgID, c.SignedInUser) g := guardian.New(c.Req.Context(), result.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, result)) return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, result))
} }
// swagger:route DELETE /folders/{folder_uid} folders deleteFolder // swagger:route DELETE /folders/{folder_uid} folders deleteFolder
@ -218,40 +219,6 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { //
return response.JSON(http.StatusOK, "") return response.JSON(http.StatusOK, "")
} }
func (hs *HTTPServer) toFolderDto(c *models.ReqContext, g guardian.DashboardGuardian, folder *models.Folder) dtos.Folder {
canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave()
canAdmin, _ := g.CanAdmin()
canDelete, _ := g.CanDelete()
// Finding creator and last updater of the folder
updater, creator := anonString, anonString
if folder.CreatedBy > 0 {
creator = hs.getUserLogin(c.Req.Context(), folder.CreatedBy)
}
if folder.UpdatedBy > 0 {
updater = hs.getUserLogin(c.Req.Context(), folder.UpdatedBy)
}
return dtos.Folder{
Id: folder.Id,
Uid: folder.Uid,
Title: folder.Title,
Url: folder.Url,
HasACL: folder.HasACL,
CanSave: canSave,
CanEdit: canEdit,
CanAdmin: canAdmin,
CanDelete: canDelete,
CreatedBy: creator,
Created: folder.Created,
UpdatedBy: updater,
Updated: folder.Updated,
Version: folder.Version,
AccessControl: hs.getAccessControlMetadata(c, c.OrgID, dashboards.ScopeFoldersPrefix, folder.Uid),
}
}
func (hs *HTTPServer) newToFolderDto(c *models.ReqContext, g guardian.DashboardGuardian, folder *folder.Folder) dtos.Folder { func (hs *HTTPServer) newToFolderDto(c *models.ReqContext, g guardian.DashboardGuardian, folder *folder.Folder) dtos.Folder {
canEdit, _ := g.CanEdit() canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave() canSave, _ := g.CanSave()

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
@ -26,13 +27,14 @@ import (
// 404: notFoundError // 404: notFoundError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Response {
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgID, web.Params(c.Req)[":uid"]) uid := web.Params(c.Req)[":uid"]
folder, err := hs.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, UID: &uid})
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser) g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied) return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied)
@ -49,7 +51,7 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res
continue continue
} }
perm.FolderId = folder.Id perm.FolderId = folder.ID
perm.DashboardId = 0 perm.DashboardId = 0
perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail) perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail)
@ -87,12 +89,13 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
return response.Error(400, err.Error(), err) return response.Error(400, err.Error(), err)
} }
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgID, web.Params(c.Req)[":uid"]) uid := web.Params(c.Req)[":uid"]
folder, err := hs.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, UID: &uid})
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser) g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
canAdmin, err := g.CanAdmin() canAdmin, err := g.CanAdmin()
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
@ -106,7 +109,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
for _, item := range apiCmd.Items { for _, item := range apiCmd.Items {
items = append(items, &models.DashboardACL{ items = append(items, &models.DashboardACL{
OrgID: c.OrgID, OrgID: c.OrgID,
DashboardID: folder.Id, DashboardID: folder.ID,
UserID: item.UserID, UserID: item.UserID,
TeamID: item.TeamID, TeamID: item.TeamID,
Role: item.Role, Role: item.Role,
@ -140,13 +143,13 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
if err != nil { if err != nil {
return response.Error(500, "Error while checking dashboard permissions", err) return response.Error(500, "Error while checking dashboard permissions", err)
} }
if err := hs.updateDashboardAccessControl(c.Req.Context(), c.OrgID, folder.Uid, true, items, old); err != nil { if err := hs.updateDashboardAccessControl(c.Req.Context(), c.OrgID, folder.UID, true, items, old); err != nil {
return response.Error(500, "Failed to create permission", err) return response.Error(500, "Failed to create permission", err)
} }
return response.Success("Dashboard permissions updated") return response.Success("Dashboard permissions updated")
} }
if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil { if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), folder.ID, items); err != nil {
if errors.Is(err, models.ErrDashboardACLInfoMissing) { if errors.Is(err, models.ErrDashboardACLInfoMissing) {
err = models.ErrFolderACLInfoMissing err = models.ErrFolderACLInfoMissing
} }
@ -163,7 +166,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
return response.JSON(http.StatusOK, util.DynMap{ return response.JSON(http.StatusOK, util.DynMap{
"message": "Folder permissions updated", "message": "Folder permissions updated",
"id": folder.Id, "id": folder.ID,
"title": folder.Title, "title": folder.Title,
}) })
} }

View File

@ -1,7 +1,6 @@
package api package api
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -294,47 +293,3 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
fn(sc) fn(sc)
}) })
} }
type fakeFolderService struct {
folder.Service
GetFoldersResult []*models.Folder
GetFoldersError error
GetFolderByUIDResult *models.Folder
GetFolderByUIDError error
GetFolderByIDResult *models.Folder
GetFolderByIDError error
CreateFolderResult *models.Folder
CreateFolderError error
UpdateFolderResult *models.Folder
UpdateFolderError error
DeleteFolderResult *folder.Folder
DeleteFolderError error
DeletedFolderUids []string
}
func (s *fakeFolderService) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
return s.GetFoldersResult, s.GetFoldersError
}
func (s *fakeFolderService) GetFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
return s.GetFolderByIDResult, s.GetFolderByIDError
}
func (s *fakeFolderService) GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
return s.GetFolderByUIDResult, s.GetFolderByUIDError
}
func (s *fakeFolderService) CreateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
return s.CreateFolderResult, s.CreateFolderError
}
func (s *fakeFolderService) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
cmd.Result = s.UpdateFolderResult
return s.UpdateFolderError
}
func (s *fakeFolderService) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
s.DeletedFolderUids = append(s.DeletedFolderUids, cmd.UID)
return s.DeleteFolderError
}

View File

@ -85,17 +85,20 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
// here we need to get FolderId from FolderUID if it present in the request, if both exist, FolderUID would overwrite FolderID // here we need to get FolderId from FolderUID if it present in the request, if both exist, FolderUID would overwrite FolderID
if req.FolderUid != "" { if req.FolderUid != "" {
folder, err := s.folderService.GetFolderByUID(ctx, req.User, req.User.OrgID, req.FolderUid) folder, err := s.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: req.User.OrgID,
UID: &req.FolderUid,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.FolderId = folder.Id req.FolderId = folder.ID
} else { } else {
folder, err := s.folderService.GetFolderByID(ctx, req.User, req.FolderId, req.User.OrgID) folder, err := s.folderService.Get(ctx, &folder.GetFolderQuery{ID: &req.FolderId, OrgID: req.User.OrgID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.FolderUid = folder.Uid req.FolderUid = folder.UID
} }
saveCmd := models.SaveDashboardCommand{ saveCmd := models.SaveDashboardCommand{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,7 @@ type Service struct {
searchService *search.SearchService searchService *search.SearchService
features *featuremgmt.FeatureManager features *featuremgmt.FeatureManager
permissions accesscontrol.FolderPermissionsService permissions accesscontrol.FolderPermissionsService
accessControl accesscontrol.AccessControl
// bus is currently used to publish events that cause scheduler to update rules. // bus is currently used to publish events that cause scheduler to update rules.
bus bus.Bus bus bus.Bus
@ -62,10 +63,41 @@ func ProvideService(
searchService: searchService, searchService: searchService,
features: features, features: features,
permissions: folderPermissionsService, permissions: folderPermissionsService,
accessControl: ac,
bus: bus, bus: bus,
} }
} }
func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.Folder, error) {
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
}
if s.cfg.IsFeatureToggleEnabled(featuremgmt.FlagNestedFolders) {
if ok, err := s.accessControl.Evaluate(ctx, user, accesscontrol.EvalPermission(
dashboards.ActionFoldersRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(*cmd.UID),
)); !ok {
if err != nil {
return nil, toFolderError(err)
}
return nil, dashboards.ErrFolderAccessDenied
}
return s.store.Get(ctx, *cmd)
}
switch {
case cmd.UID != nil:
return s.getFolderByUID(ctx, user, cmd.OrgID, *cmd.UID)
case cmd.ID != nil:
return s.getFolderByID(ctx, user, *cmd.ID, cmd.OrgID)
case cmd.Title != nil:
return s.getFolderByTitle(ctx, user, cmd.OrgID, *cmd.Title)
default:
return nil, folder.ErrBadRequest.Errorf("either on of UID, ID, Title fields must be present")
}
}
func (s *Service) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) { func (s *Service) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
searchQuery := search.Query{ searchQuery := search.Query{
SignedInUser: user, SignedInUser: user,
@ -95,9 +127,9 @@ func (s *Service) GetFolders(ctx context.Context, user *user.SignedInUser, orgID
return folders, nil return folders, nil
} }
func (s *Service) GetFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*models.Folder, error) { func (s *Service) getFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*folder.Folder, error) {
if id == 0 { if id == 0 {
return &models.Folder{Id: id, Title: "General"}, nil return &folder.Folder{ID: id, Title: "General"}, nil
} }
dashFolder, err := s.dashboardStore.GetFolderByID(ctx, orgID, id) dashFolder, err := s.dashboardStore.GetFolderByID(ctx, orgID, id)
@ -105,7 +137,7 @@ func (s *Service) GetFolderByID(ctx context.Context, user *user.SignedInUser, id
return nil, err return nil, err
} }
g := guardian.New(ctx, dashFolder.Id, orgID, user) g := guardian.New(ctx, dashFolder.ID, orgID, user)
if canView, err := g.CanView(); err != nil || !canView { if canView, err := g.CanView(); err != nil || !canView {
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
@ -116,13 +148,13 @@ func (s *Service) GetFolderByID(ctx context.Context, user *user.SignedInUser, id
return dashFolder, nil return dashFolder, nil
} }
func (s *Service) GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error) { func (s *Service) getFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*folder.Folder, error) {
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid) dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
g := guardian.New(ctx, dashFolder.Id, orgID, user) g := guardian.New(ctx, dashFolder.ID, orgID, user)
if canView, err := g.CanView(); err != nil || !canView { if canView, err := g.CanView(); err != nil || !canView {
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
@ -133,13 +165,13 @@ func (s *Service) GetFolderByUID(ctx context.Context, user *user.SignedInUser, o
return dashFolder, nil return dashFolder, nil
} }
func (s *Service) GetFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*models.Folder, error) { func (s *Service) getFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*folder.Folder, error) {
dashFolder, err := s.dashboardStore.GetFolderByTitle(ctx, orgID, title) dashFolder, err := s.dashboardStore.GetFolderByTitle(ctx, orgID, title)
if err != nil { if err != nil {
return nil, err return nil, err
} }
g := guardian.New(ctx, dashFolder.Id, orgID, user) g := guardian.New(ctx, dashFolder.ID, orgID, user)
if canView, err := g.CanView(); err != nil || !canView { if canView, err := g.CanView(); err != nil || !canView {
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
@ -189,7 +221,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
return nil, toFolderError(err) return nil, toFolderError(err)
} }
var createdFolder *models.Folder var createdFolder *folder.Folder
createdFolder, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.Id) createdFolder, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.Id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -209,9 +241,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()}, {BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
}...) }...)
_, permissionErr = s.permissions.SetPermissions(ctx, cmd.OrgID, createdFolder.Uid, permissions...) _, permissionErr = s.permissions.SetPermissions(ctx, cmd.OrgID, createdFolder.UID, permissions...)
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous { } else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.Id, true) permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.ID, true)
} }
if permissionErr != nil { if permissionErr != nil {
@ -242,7 +274,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
// We'll log the error and also roll back the previously-created // We'll log the error and also roll back the previously-created
// (legacy) folder. // (legacy) folder.
s.log.Error("error saving folder to nested folder store", err) s.log.Error("error saving folder to nested folder store", err)
err = s.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: createdFolder.Uid, OrgID: cmd.OrgID, ForceDeleteRules: true}) err = s.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: createdFolder.UID, OrgID: cmd.OrgID, ForceDeleteRules: true})
if err != nil { if err != nil {
s.log.Error("error deleting folder after failed save to nested folder store", err) s.log.Error("error deleting folder after failed save to nested folder store", err)
} }
@ -254,7 +286,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
return folder.FromDashboard(dash), nil return folder.FromDashboard(dash), nil
} }
func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*models.Folder, error) { func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*folder.Folder, error) {
foldr, err := s.legacyUpdate(ctx, user, orgID, existingUid, cmd) foldr, err := s.legacyUpdate(ctx, user, orgID, existingUid, cmd)
if err != nil { if err != nil {
return nil, err return nil, err
@ -276,7 +308,7 @@ func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = s.store.Update(ctx, folder.UpdateFolderCommand{ foldr, err := s.store.Update(ctx, folder.UpdateFolderCommand{
Folder: getFolder, Folder: getFolder,
NewUID: &cmd.Uid, NewUID: &cmd.Uid,
NewTitle: &cmd.Title, NewTitle: &cmd.Title,
@ -285,11 +317,12 @@ func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int
if err != nil { if err != nil {
return nil, err return nil, err
} }
return foldr, nil
} }
return foldr, nil return foldr, nil
} }
func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*models.Folder, error) { func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*folder.Folder, error) {
query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid} query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid}
_, err := s.dashboardStore.GetDashboard(ctx, &query) _, err := s.dashboardStore.GetDashboard(ctx, &query)
if err != nil { if err != nil {
@ -322,7 +355,7 @@ func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, org
return nil, toFolderError(err) return nil, toFolderError(err)
} }
var foldr *models.Folder var foldr *folder.Folder
foldr, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id) foldr, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -359,7 +392,7 @@ func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderComm
return err return err
} }
guard := guardian.New(ctx, dashFolder.Id, cmd.OrgID, user) guard := guardian.New(ctx, dashFolder.ID, cmd.OrgID, user)
if canSave, err := guard.CanDelete(); err != nil || !canSave { if canSave, err := guard.CanDelete(); err != nil || !canSave {
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
@ -367,7 +400,7 @@ func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderComm
return dashboards.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
deleteCmd := models.DeleteDashboardCommand{OrgId: cmd.OrgID, Id: dashFolder.Id, ForceDeleteFolderRules: cmd.ForceDeleteRules} deleteCmd := models.DeleteDashboardCommand{OrgId: cmd.OrgID, Id: dashFolder.ID, ForceDeleteFolderRules: cmd.ForceDeleteRules}
if err := s.dashboardStore.DeleteDashboard(ctx, &deleteCmd); err != nil { if err := s.dashboardStore.DeleteDashboard(ctx, &deleteCmd); err != nil {
return toFolderError(err) return toFolderError(err)
@ -416,12 +449,6 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
return nil return nil
} }
func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.Folder, error) {
// check the flag, if old - do whatever did before
// for new only the store
return s.store.Get(ctx, *cmd)
}
func (s *Service) GetParents(ctx context.Context, cmd *folder.GetParentsQuery) ([]*folder.Folder, error) { func (s *Service) GetParents(ctx context.Context, cmd *folder.GetParentsQuery) ([]*folder.Folder, error) {
// check the flag, if old - do whatever did before // check the flag, if old - do whatever did before
// for new only the store // for new only the store

View File

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
dashboardsvc "github.com/grafana/grafana/pkg/services/dashboards/service" dashboardsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
@ -79,26 +80,26 @@ func TestIntegrationFolderService(t *testing.T) {
folderId := rand.Int63() folderId := rand.Int63()
folderUID := util.GenerateShortUID() folderUID := util.GenerateShortUID()
f := models.NewFolder("Folder") f := folder.NewFolder("Folder", "")
f.Id = folderId f.ID = folderId
f.Uid = folderUID f.UID = folderUID
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(f, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(f, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(f, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(f, nil)
t.Run("When get folder by id should return access denied error", func(t *testing.T) { t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID) _, err := service.getFolderByID(context.Background(), usr, folderId, orgID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) { t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) {
folder, err := service.GetFolderByID(context.Background(), usr, 0, orgID) foldr, err := service.getFolderByID(context.Background(), usr, 0, orgID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, folder, &models.Folder{Id: 0, Title: "General"}) require.Equal(t, foldr, &folder.Folder{ID: 0, Title: "General"})
}) })
t.Run("When get folder by uid should return access denied error", func(t *testing.T) { t.Run("When get folder by uid should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByUID(context.Background(), usr, orgID, folderUID) _, err := service.getFolderByUID(context.Background(), usr, orgID, folderUID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
@ -157,7 +158,7 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When creating folder should not return access denied error", func(t *testing.T) { t.Run("When creating folder should not return access denied error", func(t *testing.T) {
dash := models.NewDashboardFolder("Test-Folder") dash := models.NewDashboardFolder("Test-Folder")
dash.Id = rand.Int63() dash.Id = rand.Int63()
f := models.DashboardToFolder(dash) f := folder.FromDashboard(dash)
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once() dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
@ -170,7 +171,7 @@ func TestIntegrationFolderService(t *testing.T) {
UID: "someuid", UID: "someuid",
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, f, actualFolder.ToLegacyModel()) require.Equal(t, f, actualFolder)
}) })
t.Run("When creating folder should return error if uid is general", func(t *testing.T) { t.Run("When creating folder should return error if uid is general", func(t *testing.T) {
@ -190,7 +191,7 @@ func TestIntegrationFolderService(t *testing.T) {
dashboardFolder := models.NewDashboardFolder("Folder") dashboardFolder := models.NewDashboardFolder("Folder")
dashboardFolder.Id = rand.Int63() dashboardFolder.Id = rand.Int63()
dashboardFolder.Uid = util.GenerateShortUID() dashboardFolder.Uid = util.GenerateShortUID()
f := models.DashboardToFolder(dashboardFolder) f := folder.FromDashboard(dashboardFolder)
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
@ -207,10 +208,10 @@ func TestIntegrationFolderService(t *testing.T) {
}) })
t.Run("When deleting folder by uid should not return access denied error", func(t *testing.T) { t.Run("When deleting folder by uid should not return access denied error", func(t *testing.T) {
f := models.NewFolder(util.GenerateShortUID()) f := folder.NewFolder(util.GenerateShortUID(), "")
f.Id = rand.Int63() f.ID = rand.Int63()
f.Uid = util.GenerateShortUID() f.UID = util.GenerateShortUID()
dashStore.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, f.UID).Return(f, nil)
var actualCmd *models.DeleteDashboardCommand var actualCmd *models.DeleteDashboardCommand
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
@ -221,13 +222,13 @@ func TestIntegrationFolderService(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ctx = appcontext.WithUser(ctx, usr) ctx = appcontext.WithUser(ctx, usr)
err := service.DeleteFolder(ctx, &folder.DeleteFolderCommand{ err := service.DeleteFolder(ctx, &folder.DeleteFolderCommand{
UID: f.Uid, UID: f.UID,
OrgID: orgID, OrgID: orgID,
ForceDeleteRules: expectedForceDeleteRules, ForceDeleteRules: expectedForceDeleteRules,
}) })
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, actualCmd) require.NotNil(t, actualCmd)
require.Equal(t, f.Id, actualCmd.Id) require.Equal(t, f.ID, actualCmd.Id)
require.Equal(t, orgID, actualCmd.OrgId) require.Equal(t, orgID, actualCmd.OrgId)
require.Equal(t, expectedForceDeleteRules, actualCmd.ForceDeleteFolderRules) require.Equal(t, expectedForceDeleteRules, actualCmd.ForceDeleteFolderRules)
}) })
@ -242,33 +243,33 @@ func TestIntegrationFolderService(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true})
t.Run("When get folder by id should return folder", func(t *testing.T) { t.Run("When get folder by id should return folder", func(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID()) expected := folder.NewFolder(util.GenerateShortUID(), "")
expected.Id = rand.Int63() expected.ID = rand.Int63()
dashStore.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, expected.ID).Return(expected, nil)
actual, err := service.GetFolderByID(context.Background(), usr, expected.Id, orgID) actual, err := service.getFolderByID(context.Background(), usr, expected.ID, orgID)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("When get folder by uid should return folder", func(t *testing.T) { t.Run("When get folder by uid should return folder", func(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID()) expected := folder.NewFolder(util.GenerateShortUID(), "")
expected.Uid = util.GenerateShortUID() expected.UID = util.GenerateShortUID()
dashStore.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, expected.UID).Return(expected, nil)
actual, err := service.GetFolderByUID(context.Background(), usr, orgID, expected.Uid) actual, err := service.getFolderByUID(context.Background(), usr, orgID, expected.UID)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("When get folder by title should return folder", func(t *testing.T) { t.Run("When get folder by title should return folder", func(t *testing.T) {
expected := models.NewFolder("TEST-" + util.GenerateShortUID()) expected := folder.NewFolder("TEST-"+util.GenerateShortUID(), "")
dashStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil) dashStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil)
actual, err := service.GetFolderByTitle(context.Background(), usr, orgID, expected.Title) actual, err := service.getFolderByTitle(context.Background(), usr, orgID, expected.Title)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -311,7 +312,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil) mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore := dashboards.FakeDashboardStore{} dashStore := dashboards.FakeDashboardStore{}
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false cfg.RBACEnabled = false
nestedFoldersEnabled := true nestedFoldersEnabled := true
@ -338,18 +339,6 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
require.NotNil(t, res.UID) require.NotNil(t, res.UID)
}) })
t.Run("delete folder", func(t *testing.T) {
folderStore.ExpectedFolder = &folder.Folder{}
err := folderService.Delete(context.Background(), &folder.DeleteFolderCommand{})
require.NoError(t, err)
})
t.Run("get folder", func(t *testing.T) {
folderStore.ExpectedFolder = &folder.Folder{}
_, err := folderService.Get(context.Background(), &folder.GetFolderQuery{})
require.NoError(t, err)
})
t.Run("get parents folder", func(t *testing.T) { t.Run("get parents folder", func(t *testing.T) {
folderStore.ExpectedFolder = &folder.Folder{} folderStore.ExpectedFolder = &folder.Folder{}
_, err := folderService.GetParents(context.Background(), &folder.GetParentsQuery{}) _, err := folderService.GetParents(context.Background(), &folder.GetParentsQuery{})
@ -378,12 +367,6 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 4, len(res)) require.Equal(t, 4, len(res))
}) })
t.Run("move folder", func(t *testing.T) {
folderStore.ExpectedFolder = &folder.Folder{}
_, err := folderService.Move(context.Background(), &folder.MoveFolderCommand{})
require.NoError(t, err)
})
} }
func TestNestedFolderService(t *testing.T) { func TestNestedFolderService(t *testing.T) {
@ -412,7 +395,7 @@ func TestNestedFolderService(t *testing.T) {
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil) mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
ctx = appcontext.WithUser(ctx, usr) ctx = appcontext.WithUser(ctx, usr)
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{ _, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
@ -430,7 +413,7 @@ func TestNestedFolderService(t *testing.T) {
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand) actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).Return(nil).Once() }).Return(nil).Once()
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
g := guardian.New g := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
@ -463,6 +446,9 @@ func TestNestedFolderService(t *testing.T) {
dashboardStore: dashStore, dashboardStore: dashStore,
store: store, store: store,
features: features, features: features,
accessControl: actest.FakeAccessControl{
ExpectedEvaluate: true,
},
} }
t.Run("create, no error", func(t *testing.T) { t.Run("create, no error", func(t *testing.T) {
@ -471,7 +457,7 @@ func TestNestedFolderService(t *testing.T) {
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil) mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
ctx = appcontext.WithUser(ctx, usr) ctx = appcontext.WithUser(ctx, usr)
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{ _, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
OrgID: orgID, OrgID: orgID,
@ -493,8 +479,8 @@ func TestNestedFolderService(t *testing.T) {
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil) mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil)
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil) dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
// return an error from the folder store // return an error from the folder store
store.ExpectedError = errors.New("FAILED") store.ExpectedError = errors.New("FAILED")

View File

@ -14,29 +14,25 @@ type FakeService struct {
ExpectedError error ExpectedError error
} }
func NewFakeService() *FakeService {
return &FakeService{}
}
var _ folder.Service = (*FakeService)(nil) var _ folder.Service = (*FakeService)(nil)
func (s *FakeService) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) { func (s *FakeService) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
return s.ExpectedFolders, s.ExpectedError return s.ExpectedFolders, s.ExpectedError
} }
func (s *FakeService) GetFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
}
func (s *FakeService) GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
if s.ExpectedFolder == nil {
return nil, s.ExpectedError
}
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
}
func (s *FakeService) GetFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*models.Folder, error) {
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
}
func (s *FakeService) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) { func (s *FakeService) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
return s.ExpectedFolder, s.ExpectedError return s.ExpectedFolder, s.ExpectedError
} }
func (s *FakeService) Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*models.Folder, error) { func (s *FakeService) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.Folder, error) {
return s.ExpectedFolder, s.ExpectedError
}
func (s *FakeService) Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*folder.Folder, error) {
cmd.Result = s.ExpectedFolder.ToLegacyModel() cmd.Result = s.ExpectedFolder.ToLegacyModel()
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError return s.ExpectedFolder, s.ExpectedError
} }
func (s *FakeService) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error { func (s *FakeService) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
return s.ExpectedError return s.ExpectedError

View File

@ -9,13 +9,18 @@ import (
type Service interface { type Service interface {
GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error)
GetFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*models.Folder, error)
GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error)
GetFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*models.Folder, error)
Create(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error) Create(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error)
// GetFolder takes a GetFolderCommand and returns a folder matching the
// request. One of ID, UID, or Title must be included. If multiple values
// are included in the request, Grafana will select one in order of
// specificity (ID, UID, Title).
Get(ctx context.Context, cmd *GetFolderQuery) (*Folder, error)
// Update is used to update a folder's UID, Title and Description. To change // Update is used to update a folder's UID, Title and Description. To change
// a folder's parent folder, use Move. // a folder's parent folder, use Move.
Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*models.Folder, error) Update(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) (*Folder, error)
DeleteFolder(ctx context.Context, cmd *DeleteFolderCommand) error DeleteFolder(ctx context.Context, cmd *DeleteFolderCommand) error
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
// Move changes a folder's parent folder to the requested new parent. // Move changes a folder's parent folder to the requested new parent.
@ -35,12 +40,6 @@ type NestedFolderService interface {
// dashboards in the folder. // dashboards in the folder.
Delete(ctx context.Context, cmd *DeleteFolderCommand) (*Folder, error) Delete(ctx context.Context, cmd *DeleteFolderCommand) (*Folder, error)
// GetFolder takes a GetFolderCommand and returns a folder matching the
// request. One of ID, UID, or Title must be included. If multiple values
// are included in the request, Grafana will select one in order of
// specificity (ID, UID, Title).
Get(ctx context.Context, cmd *GetFolderQuery) (*Folder, error)
// GetParents returns an ordered list of parent folders for the given // GetParents returns an ordered list of parent folders for the given
// folder, starting with the root node and ending with the requested child // folder, starting with the root node and ending with the requested child
// node. // node.

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -47,11 +48,11 @@ func (l *LibraryElementService) createHandler(c *models.ReqContext) response.Res
if *cmd.FolderUID == "" { if *cmd.FolderUID == "" {
cmd.FolderID = 0 cmd.FolderID = 0
} else { } else {
folder, err := l.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgID, *cmd.FolderUID) folder, err := l.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, UID: cmd.FolderUID})
if err != nil || folder == nil { if err != nil || folder == nil {
return response.Error(http.StatusBadRequest, "failed to get folder", err) return response.Error(http.StatusBadRequest, "failed to get folder", err)
} }
cmd.FolderID = folder.Id cmd.FolderID = folder.ID
} }
} }
@ -61,12 +62,12 @@ func (l *LibraryElementService) createHandler(c *models.ReqContext) response.Res
} }
if element.FolderID != 0 { if element.FolderID != 0 {
folder, err := l.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, element.FolderID, c.OrgID) folder, err := l.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, ID: &element.FolderID})
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get folder", err) return response.Error(http.StatusInternalServerError, "failed to get folder", err)
} }
element.FolderUID = folder.Uid element.FolderUID = folder.UID
element.Meta.FolderUID = folder.Uid element.Meta.FolderUID = folder.UID
element.Meta.FolderName = folder.Title element.Meta.FolderName = folder.Title
} }
@ -175,11 +176,11 @@ func (l *LibraryElementService) patchHandler(c *models.ReqContext) response.Resp
if *cmd.FolderUID == "" { if *cmd.FolderUID == "" {
cmd.FolderID = 0 cmd.FolderID = 0
} else { } else {
folder, err := l.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgID, *cmd.FolderUID) folder, err := l.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, UID: cmd.FolderUID})
if err != nil || folder == nil { if err != nil || folder == nil {
return response.Error(http.StatusBadRequest, "failed to get folder", err) return response.Error(http.StatusBadRequest, "failed to get folder", err)
} }
cmd.FolderID = folder.Id cmd.FolderID = folder.ID
} }
} }
@ -189,12 +190,12 @@ func (l *LibraryElementService) patchHandler(c *models.ReqContext) response.Resp
} }
if element.FolderID != 0 { if element.FolderID != 0 {
folder, err := l.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, element.FolderID, c.OrgID) folder, err := l.folderService.Get(c.Req.Context(), &folder.GetFolderQuery{OrgID: c.OrgID, ID: &element.FolderID})
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get folder", err) return response.Error(http.StatusInternalServerError, "failed to get folder", err)
} }
element.FolderUID = folder.Uid element.FolderUID = folder.UID
element.Meta.FolderUID = folder.Uid element.Meta.FolderUID = folder.UID
element.Meta.FolderName = folder.Title element.Meta.FolderName = folder.Title
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -39,12 +40,12 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte
if isGeneralFolder(folderID) && user.HasRole(org.RoleViewer) { if isGeneralFolder(folderID) && user.HasRole(org.RoleViewer) {
return dashboards.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgID) folder, err := l.folderService.Get(ctx, &folder.GetFolderQuery{ID: &folderID, OrgID: user.OrgID})
if err != nil { if err != nil {
return err return err
} }
g := guardian.New(ctx, folder.Id, user.OrgID, user) g := guardian.New(ctx, folder.ID, user.OrgID, user)
canEdit, err := g.CanEdit() canEdit, err := g.CanEdit()
if err != nil { if err != nil {
@ -62,12 +63,12 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte
return nil return nil
} }
folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgID) folder, err := l.folderService.Get(ctx, &folder.GetFolderQuery{ID: &folderID, OrgID: user.OrgID})
if err != nil { if err != nil {
return err return err
} }
g := guardian.New(ctx, folder.Id, user.OrgID, user) g := guardian.New(ctx, folder.ID, user.OrgID, user)
canView, err := g.CanView() canView, err := g.CanView()
if err != nil { if err != nil {

View File

@ -13,7 +13,7 @@ import (
func TestCreateLibraryElement(t *testing.T) { func TestCreateLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail", scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 400, resp.Status()) require.Equal(t, 400, resp.Status())
@ -65,7 +65,7 @@ func TestCreateLibraryElement(t *testing.T) {
testScenario(t, "When an admin tries to create a library panel that does not exists using an nonexistent UID, it should succeed", testScenario(t, "When an admin tries to create a library panel that does not exists using an nonexistent UID, it should succeed",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Nonexistent UID") command := getCreatePanelCommand(sc.folder.ID, "Nonexistent UID")
command.UID = util.GenerateShortUID() command.UID = util.GenerateShortUID()
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -114,7 +114,7 @@ func TestCreateLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an existent UID, it should fail", scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an existent UID, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Existing UID") command := getCreatePanelCommand(sc.folder.ID, "Existing UID")
command.UID = sc.initialResult.Result.UID command.UID = sc.initialResult.Result.UID
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -123,7 +123,7 @@ func TestCreateLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an invalid UID, it should fail", scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an invalid UID, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Invalid UID") command := getCreatePanelCommand(sc.folder.ID, "Invalid UID")
command.UID = "Testing an invalid UID" command.UID = "Testing an invalid UID"
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -132,7 +132,7 @@ func TestCreateLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an UID that is too long, it should fail", scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an UID that is too long, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Invalid UID") command := getCreatePanelCommand(sc.folder.ID, "Invalid UID")
command.UID = "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K" command.UID = "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K"
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)

View File

@ -73,7 +73,7 @@ func TestDeleteLibraryElement(t *testing.T) {
Title: "Testing deleteHandler ", Title: "Testing deleteHandler ",
Data: simplejson.NewFromAny(dashJSON), Data: simplejson.NewFromAny(dashJSON),
} }
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id)
require.NoError(t, err) require.NoError(t, err)

View File

@ -37,7 +37,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all panel elements and both panels and variables exist, it should only return panels", scenarioWithPanel(t, "When an admin tries to get all panel elements and both panels and variables exist, it should only return panels",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateVariableCommand(sc.folder.Id, "query0") command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -77,7 +77,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -103,7 +103,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all variable elements and both panels and variables exist, it should only return panels", scenarioWithPanel(t, "When an admin tries to get all variable elements and both panels and variables exist, it should only return panels",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateVariableCommand(sc.folder.Id, "query0") command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -142,7 +142,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -168,7 +168,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist, it should succeed", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist, it should succeed",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -204,7 +204,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -239,7 +239,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created, Created: result.Result.Elements[1].Meta.Created,
Updated: result.Result.Elements[1].Meta.Updated, Updated: result.Result.Elements[1].Meta.Updated,
@ -265,7 +265,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and sort desc is set, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and sort desc is set, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -304,7 +304,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -339,7 +339,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created, Created: result.Result.Elements[1].Meta.Created,
Updated: result.Result.Elements[1].Meta.Updated, Updated: result.Result.Elements[1].Meta.Updated,
@ -365,7 +365,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to existing types, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to existing types, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", models.PanelElement, []byte(` command := getCreateCommandWithModel(sc.folder.ID, "Gauge - Library Panel", models.PanelElement, []byte(`
{ {
"datasource": "${DS_GDEV-TESTDATA}", "datasource": "${DS_GDEV-TESTDATA}",
"id": 1, "id": 1,
@ -378,7 +378,7 @@ func TestGetAllLibraryElements(t *testing.T) {
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
command = getCreateCommandWithModel(sc.folder.Id, "BarGauge - Library Panel", models.PanelElement, []byte(` command = getCreateCommandWithModel(sc.folder.ID, "BarGauge - Library Panel", models.PanelElement, []byte(`
{ {
"datasource": "${DS_GDEV-TESTDATA}", "datasource": "${DS_GDEV-TESTDATA}",
"id": 1, "id": 1,
@ -425,7 +425,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -460,7 +460,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created, Created: result.Result.Elements[1].Meta.Created,
Updated: result.Result.Elements[1].Meta.Updated, Updated: result.Result.Elements[1].Meta.Updated,
@ -486,7 +486,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to a nonexistent type, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to a nonexistent type, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", models.PanelElement, []byte(` command := getCreateCommandWithModel(sc.folder.ID, "Gauge - Library Panel", models.PanelElement, []byte(`
{ {
"datasource": "${DS_GDEV-TESTDATA}", "datasource": "${DS_GDEV-TESTDATA}",
"id": 1, "id": 1,
@ -621,7 +621,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to General folder, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to General folder, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -661,7 +661,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -696,7 +696,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created, Created: result.Result.Elements[1].Meta.Created,
Updated: result.Result.Elements[1].Meta.Updated, Updated: result.Result.Elements[1].Meta.Updated,
@ -722,7 +722,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and excludeUID is set, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and excludeUID is set, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -761,7 +761,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -787,7 +787,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -826,7 +826,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -852,7 +852,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 2, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 2, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -892,7 +892,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -918,7 +918,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in the description, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in the description, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Text - Library Panel2", models.PanelElement, []byte(` command := getCreateCommandWithModel(sc.folder.ID, "Text - Library Panel2", models.PanelElement, []byte(`
{ {
"datasource": "${DS_GDEV-TESTDATA}", "datasource": "${DS_GDEV-TESTDATA}",
"id": 1, "id": 1,
@ -967,7 +967,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -993,7 +993,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in both name and description, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in both name and description, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Some Other", models.PanelElement, []byte(` command := getCreateCommandWithModel(sc.folder.ID, "Some Other", models.PanelElement, []byte(`
{ {
"datasource": "${DS_GDEV-TESTDATA}", "datasource": "${DS_GDEV-TESTDATA}",
"id": 1, "id": 1,
@ -1040,7 +1040,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -1075,7 +1075,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created, Created: result.Result.Elements[1].Meta.Created,
Updated: result.Result.Elements[1].Meta.Updated, Updated: result.Result.Elements[1].Meta.Updated,
@ -1101,7 +1101,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 1 and searchString is panel2, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 1 and searchString is panel2, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -1142,7 +1142,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created, Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated, Updated: result.Result.Elements[0].Meta.Updated,
@ -1168,7 +1168,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString is panel, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString is panel, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -1199,7 +1199,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString does not exist, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString does not exist, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())

View File

@ -49,7 +49,7 @@ func TestGetLibraryElement(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 0, ConnectedDashboards: 0,
Created: res.Result.Meta.Created, Created: res.Result.Meta.Created,
Updated: res.Result.Meta.Updated, Updated: res.Result.Meta.Updated,
@ -119,7 +119,7 @@ func TestGetLibraryElement(t *testing.T) {
Title: "Testing getHandler", Title: "Testing getHandler",
Data: simplejson.NewFromAny(dashJSON), Data: simplejson.NewFromAny(dashJSON),
} }
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id)
require.NoError(t, err) require.NoError(t, err)
@ -144,7 +144,7 @@ func TestGetLibraryElement(t *testing.T) {
Version: 1, Version: 1,
Meta: LibraryElementDTOMeta{ Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder", FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid, FolderUID: sc.folder.UID,
ConnectedDashboards: 1, ConnectedDashboards: 1,
Created: res.Result.Meta.Created, Created: res.Result.Meta.Created,
Updated: res.Result.Meta.Updated, Updated: res.Result.Meta.Updated,

View File

@ -187,7 +187,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with an existing UID, it should fail", scenarioWithPanel(t, "When an admin tries to patch a library panel with an existing UID, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Existing UID") command := getCreatePanelCommand(sc.folder.ID, "Existing UID")
command.UID = util.GenerateShortUID() command.UID = util.GenerateShortUID()
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -307,7 +307,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail", scenarioWithPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Another Panel") command := getCreatePanelCommand(sc.folder.ID, "Another Panel")
sc.ctx.Req.Body = mockRequestBody(command) sc.ctx.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
var result = validateAndUnMarshalResponse(t, resp) var result = validateAndUnMarshalResponse(t, resp)
@ -343,7 +343,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel in another org, it should fail", scenarioWithPanel(t, "When an admin tries to patch a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{ cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id, FolderID: sc.folder.ID,
Version: 1, Version: 1,
Kind: int64(models.PanelElement), Kind: int64(models.PanelElement),
} }
@ -357,7 +357,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with an old version number, it should fail", scenarioWithPanel(t, "When an admin tries to patch a library panel with an old version number, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{ cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id, FolderID: sc.folder.ID,
Version: 1, Version: 1,
Kind: int64(models.PanelElement), Kind: int64(models.PanelElement),
} }
@ -373,7 +373,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with an other kind, it should succeed but panel should not change", scenarioWithPanel(t, "When an admin tries to patch a library panel with an other kind, it should succeed but panel should not change",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{ cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id, FolderID: sc.folder.ID,
Version: 1, Version: 1,
Kind: int64(models.VariableElement), Kind: int64(models.VariableElement),
} }

View File

@ -73,23 +73,23 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) {
Title: "Testing DeleteLibraryElementsInFolder", Title: "Testing DeleteLibraryElementsInFolder",
Data: simplejson.NewFromAny(dashJSON), Data: simplejson.NewFromAny(dashJSON),
} }
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id)
require.NoError(t, err) require.NoError(t, err)
err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid) err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID)
require.EqualError(t, err, ErrFolderHasConnectedLibraryElements.Error()) require.EqualError(t, err, ErrFolderHasConnectedLibraryElements.Error())
}) })
scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail", scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid+"xxxx") err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID+"xxxx")
require.EqualError(t, err, dashboards.ErrFolderNotFound.Error()) require.EqualError(t, err, dashboards.ErrFolderNotFound.Error())
}) })
scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
command := getCreateVariableCommand(sc.folder.Id, "query0") command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -102,7 +102,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) {
require.NotNil(t, result.Result) require.NotNil(t, result.Result)
require.Equal(t, 2, len(result.Result.Elements)) require.Equal(t, 2, len(result.Result.Elements))
err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid) err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID)
require.NoError(t, err) require.NoError(t, err)
resp = sc.service.getAllHandler(sc.reqContext) resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@ -146,7 +146,7 @@ func TestGetLibraryPanelConnections(t *testing.T) {
Title: "Testing GetLibraryPanelConnections", Title: "Testing GetLibraryPanelConnections",
Data: simplejson.NewFromAny(dashJSON), Data: simplejson.NewFromAny(dashJSON),
} }
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.Id)
require.NoError(t, err) require.NoError(t, err)
@ -256,7 +256,7 @@ type scenarioContext struct {
service *LibraryElementService service *LibraryElementService
reqContext *models.ReqContext reqContext *models.ReqContext
user user.SignedInUser user user.SignedInUser
folder *models.Folder folder *folder.Folder
initialResult libraryElementResult initialResult libraryElementResult
sqlStore db.DB sqlStore db.DB
} }
@ -387,7 +387,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena
guardian.InitLegacyGuardian(store, &dashboards.FakeDashboardService{}, &teamtest.FakeService{}) guardian.InitLegacyGuardian(store, &dashboards.FakeDashboardService{}, &teamtest.FakeService{})
testScenario(t, desc, func(t *testing.T, sc scenarioContext) { testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
sc.initialResult = validateAndUnMarshalResponse(t, resp) sc.initialResult = validateAndUnMarshalResponse(t, resp)
@ -402,13 +402,26 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
t.Helper() t.Helper()
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
ctx := web.Context{Req: &http.Request{ orgID := int64(1)
role := org.RoleAdmin
usr := user.SignedInUser{
UserID: 1,
Name: "Signed In User",
Login: "signed_in_user",
Email: "signed.in.user@test.com",
OrgID: orgID,
OrgRole: role,
LastSeenAt: time.Now(),
}
req := &http.Request{
Header: http.Header{ Header: http.Header{
"Content-Type": []string{"application/json"}, "Content-Type": []string{"application/json"},
}, },
}} }
orgID := int64(1) ctx := appcontext.WithUser(context.Background(), &usr)
role := org.RoleAdmin req = req.WithContext(ctx)
webCtx := web.Context{Req: req}
sqlStore := db.InitTestDB(t) sqlStore := db.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) dashboardStore := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
@ -428,16 +441,6 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil), folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil),
} }
usr := user.SignedInUser{
UserID: 1,
Name: "Signed In User",
Login: "signed_in_user",
Email: "signed.in.user@test.com",
OrgID: orgID,
OrgRole: role,
LastSeenAt: time.Now(),
}
// deliberate difference between signed in user and user in db to make it crystal clear // deliberate difference between signed in user and user in db to make it crystal clear
// what to expect in the tests // what to expect in the tests
// In the real world these are identical // In the real world these are identical
@ -452,16 +455,16 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
sc := scenarioContext{ sc := scenarioContext{
user: usr, user: usr,
ctx: &ctx, ctx: &webCtx,
service: &service, service: &service,
sqlStore: sqlStore, sqlStore: sqlStore,
reqContext: &models.ReqContext{ reqContext: &models.ReqContext{
Context: &ctx, Context: &webCtx,
SignedInUser: &usr, SignedInUser: &usr,
}, },
} }
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}).ToLegacyModel() sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{})
fn(t, sc) fn(t, sc)
}) })

View File

@ -850,12 +850,14 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
Login: userInDbName, Login: userInDbName,
} }
_, err := sqlStore.CreateUser(context.Background(), cmd) ctx := appcontext.WithUser(context.Background(), usr)
_, err := sqlStore.CreateUser(ctx, cmd)
require.NoError(t, err) require.NoError(t, err)
sc := scenarioContext{ sc := scenarioContext{
user: usr, user: usr,
ctx: context.Background(), ctx: ctx,
service: &service, service: &service,
elementService: elementService, elementService: elementService,
sqlStore: sqlStore, sqlStore: sqlStore,

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
@ -181,7 +182,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
return response.JSON(http.StatusOK, ruleResponse) return response.JSON(http.StatusOK, ruleResponse)
} }
func (srv PrometheusSrv) toRuleGroup(groupName string, folder *models.Folder, rules []*ngmodels.AlertRule, labelOptions []ngmodels.LabelOption) *apimodels.RuleGroup { func (srv PrometheusSrv) toRuleGroup(groupName string, folder *folder.Folder, rules []*ngmodels.AlertRule, labelOptions []ngmodels.LabelOption) *apimodels.RuleGroup {
newGroup := &apimodels.RuleGroup{ newGroup := &apimodels.RuleGroup{
Name: groupName, Name: groupName,
File: folder.Title, // file is what Prometheus uses for provisioning, we replace it with namespace. File: folder.Title, // file is what Prometheus uses for provisioning, we replace it with namespace.

View File

@ -83,7 +83,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *models.ReqContext, namespaceTitle s
unauthz, provisioned := false, false unauthz, provisioned := false, false
q := ngmodels.ListAlertRulesQuery{ q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID, OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid}, NamespaceUIDs: []string{namespace.UID},
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
} }
if err = srv.store.ListAlertRules(ctx, &q); err != nil { if err = srv.store.ListAlertRules(ctx, &q); err != nil {
@ -163,7 +163,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext, namespace
q := ngmodels.ListAlertRulesQuery{ q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID, OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid}, NamespaceUIDs: []string{namespace.UID},
} }
if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil { if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to update rule group") return ErrResp(http.StatusInternalServerError, err, "failed to update rule group")
@ -189,7 +189,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext, namespace
if !authorizeAccessToRuleGroup(rules, hasAccess) { if !authorizeAccessToRuleGroup(rules, hasAccess) {
continue continue
} }
result[namespaceTitle] = append(result[namespaceTitle], toGettableRuleGroupConfig(groupName, rules, namespace.Id, provenanceRecords)) result[namespaceTitle] = append(result[namespaceTitle], toGettableRuleGroupConfig(groupName, rules, namespace.ID, provenanceRecords))
} }
return response.JSON(http.StatusAccepted, result) return response.JSON(http.StatusAccepted, result)
@ -205,7 +205,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext, namespaceTitl
q := ngmodels.ListAlertRulesQuery{ q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID, OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid}, NamespaceUIDs: []string{namespace.UID},
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
} }
if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil { if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil {
@ -226,7 +226,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext, namespaceTitl
} }
result := apimodels.RuleGroupConfigResponse{ result := apimodels.RuleGroupConfigResponse{
GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, q.Result, namespace.Id, provenanceRecords), GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, q.Result, namespace.ID, provenanceRecords),
} }
return response.JSON(http.StatusAccepted, result) return response.JSON(http.StatusAccepted, result)
} }
@ -296,7 +296,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
continue continue
} }
namespace := folder.Title namespace := folder.Title
result[namespace] = append(result[namespace], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, folder.Id, provenanceRecords)) result[namespace] = append(result[namespace], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, folder.ID, provenanceRecords))
} }
return response.JSON(http.StatusOK, result) return response.JSON(http.StatusOK, result)
} }
@ -316,7 +316,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
groupKey := ngmodels.AlertRuleGroupKey{ groupKey := ngmodels.AlertRuleGroupKey{
OrgID: c.SignedInUser.OrgID, OrgID: c.SignedInUser.OrgID,
NamespaceUID: namespace.Uid, NamespaceUID: namespace.UID,
RuleGroup: ruleGroupConfig.Name, RuleGroup: ruleGroupConfig.Name,
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/provisioning"
@ -382,7 +383,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t) ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID) groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...) ruleStore.PutRule(context.Background(), expectedRules...)
@ -426,12 +427,12 @@ func TestRouteGetRulesConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t) ruleStore := fakes.NewRuleStore(t)
folder1 := randFolder() folder1 := randFolder()
folder2 := randFolder() folder2 := randFolder()
ruleStore.Folders[orgID] = []*models2.Folder{folder1, folder2} ruleStore.Folders[orgID] = []*folder.Folder{folder1, folder2}
group1Key := models.GenerateGroupKey(orgID) group1Key := models.GenerateGroupKey(orgID)
group1Key.NamespaceUID = folder1.Uid group1Key.NamespaceUID = folder1.UID
group2Key := models.GenerateGroupKey(orgID) group2Key := models.GenerateGroupKey(orgID)
group2Key.NamespaceUID = folder2.Uid group2Key.NamespaceUID = folder2.UID
group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key))) group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key)))
group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key))) group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key)))
@ -464,7 +465,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t) ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID) groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...) ruleStore.PutRule(context.Background(), expectedRules...)
@ -509,7 +510,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t) ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID) groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey))) expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
ruleStore.PutRule(context.Background(), expectedRules...) ruleStore.PutRule(context.Background(), expectedRules...)
@ -544,7 +545,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t) ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID) groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...) ruleStore.PutRule(context.Background(), expectedRules...)
@ -699,9 +700,9 @@ func withGroup(groupName string) func(rule *models.AlertRule) {
} }
} }
func withNamespace(namespace *models2.Folder) func(rule *models.AlertRule) { func withNamespace(namespace *folder.Folder) func(rule *models.AlertRule) {
return func(rule *models.AlertRule) { return func(rule *models.AlertRule) {
rule.NamespaceUID = namespace.Uid rule.NamespaceUID = namespace.UID
} }
} }

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -18,7 +18,7 @@ func validateRuleNode(
groupName string, groupName string,
interval time.Duration, interval time.Duration,
orgId int64, orgId int64,
namespace *models.Folder, namespace *folder.Folder,
conditionValidator func(ngmodels.Condition) error, conditionValidator func(ngmodels.Condition) error,
cfg *setting.UnifiedAlertingSettings) (*ngmodels.AlertRule, error) { cfg *setting.UnifiedAlertingSettings) (*ngmodels.AlertRule, error) {
intervalSeconds, err := validateInterval(cfg, interval) intervalSeconds, err := validateInterval(cfg, interval)
@ -93,7 +93,7 @@ func validateRuleNode(
Data: ruleNode.GrafanaManagedAlert.Data, Data: ruleNode.GrafanaManagedAlert.Data,
UID: ruleNode.GrafanaManagedAlert.UID, UID: ruleNode.GrafanaManagedAlert.UID,
IntervalSeconds: intervalSeconds, IntervalSeconds: intervalSeconds,
NamespaceUID: namespace.Uid, NamespaceUID: namespace.UID,
RuleGroup: groupName, RuleGroup: groupName,
NoDataState: noDataState, NoDataState: noDataState,
ExecErrState: errorState, ExecErrState: errorState,
@ -152,7 +152,7 @@ func validateForInterval(ruleNode *apimodels.PostableExtendedRuleNode) (time.Dur
func validateRuleGroup( func validateRuleGroup(
ruleGroupConfig *apimodels.PostableRuleGroupConfig, ruleGroupConfig *apimodels.PostableRuleGroupConfig,
orgId int64, orgId int64,
namespace *models.Folder, namespace *folder.Folder,
conditionValidator func(ngmodels.Condition) error, conditionValidator func(ngmodels.Condition) error,
cfg *setting.UnifiedAlertingSettings) ([]*ngmodels.AlertRule, error) { cfg *setting.UnifiedAlertingSettings) ([]*ngmodels.AlertRule, error) {
if ruleGroupConfig.Name == "" { if ruleGroupConfig.Name == "" {

View File

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -83,18 +83,18 @@ func validGroup(cfg *setting.UnifiedAlertingSettings, rules ...apimodels.Postabl
} }
} }
func randFolder() *models2.Folder { func randFolder() *folder.Folder {
return &models2.Folder{ return &folder.Folder{
Id: rand.Int63(), ID: rand.Int63(),
Uid: util.GenerateShortUID(), UID: util.GenerateShortUID(),
Title: "TEST-FOLDER-" + util.GenerateShortUID(), Title: "TEST-FOLDER-" + util.GenerateShortUID(),
Url: "", // URL: "",
Version: 0, // Version: 0,
Created: time.Time{}, Created: time.Time{},
Updated: time.Time{}, Updated: time.Time{},
UpdatedBy: 0, // UpdatedBy: 0,
CreatedBy: 0, // CreatedBy: 0,
HasACL: false, // HasACL: false,
} }
} }
@ -235,7 +235,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
require.Equal(t, int64(interval.Seconds()), alert.IntervalSeconds) require.Equal(t, int64(interval.Seconds()), alert.IntervalSeconds)
require.Equal(t, int64(0), alert.Version) require.Equal(t, int64(0), alert.Version)
require.Equal(t, api.GrafanaManagedAlert.UID, alert.UID) require.Equal(t, api.GrafanaManagedAlert.UID, alert.UID)
require.Equal(t, folder.Uid, alert.NamespaceUID) require.Equal(t, folder.UID, alert.NamespaceUID)
require.Nil(t, alert.DashboardUID) require.Nil(t, alert.DashboardUID)
require.Nil(t, alert.PanelID) require.Nil(t, alert.PanelID)
require.Equal(t, name, alert.RuleGroup) require.Equal(t, name, alert.RuleGroup)

View File

@ -3,15 +3,15 @@ package api
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
// RuleStore is the interface for persisting alert rules and instances // RuleStore is the interface for persisting alert rules and instances
type RuleStore interface { type RuleStore interface {
GetUserVisibleNamespaces(context.Context, int64, *user.SignedInUser) (map[string]*models.Folder, error) GetUserVisibleNamespaces(context.Context, int64, *user.SignedInUser) (map[string]*folder.Folder, error)
GetNamespaceByTitle(context.Context, string, int64, *user.SignedInUser, bool) (*models.Folder, error) GetNamespaceByTitle(context.Context, string, int64, *user.SignedInUser, bool) (*folder.Folder, error)
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) error GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) error
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error

View File

@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr"
models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -140,9 +140,9 @@ func WithOrgID(orgId int64) AlertRuleMutator {
} }
} }
func WithNamespace(namespace *models2.Folder) AlertRuleMutator { func WithNamespace(namespace *folder.Folder) AlertRuleMutator {
return func(rule *AlertRule) { return func(rule *AlertRule) {
rule.NamespaceUID = namespace.Uid rule.NamespaceUID = namespace.UID
} }
} }

View File

@ -13,7 +13,7 @@ import (
"github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/services/ngalert/schedule"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
@ -22,9 +22,9 @@ import (
func Test_subscribeToFolderChanges(t *testing.T) { func Test_subscribeToFolderChanges(t *testing.T) {
orgID := rand.Int63() orgID := rand.Int63()
folder := &models2.Folder{ folder := &folder.Folder{
Id: 0, ID: 0,
Uid: util.GenerateShortUID(), UID: util.GenerateShortUID(),
Title: "Folder" + util.GenerateShortUID(), Title: "Folder" + util.GenerateShortUID(),
} }
rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), models.WithNamespace(folder))) rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), models.WithNamespace(folder)))
@ -42,8 +42,8 @@ func Test_subscribeToFolderChanges(t *testing.T) {
err := bus.Publish(context.Background(), &events.FolderTitleUpdated{ err := bus.Publish(context.Background(), &events.FolderTitleUpdated{
Timestamp: time.Now(), Timestamp: time.Now(),
Title: "Folder" + util.GenerateShortUID(), Title: "Folder" + util.GenerateShortUID(),
ID: folder.Id, ID: folder.ID,
UID: folder.Uid, UID: folder.UID,
OrgID: orgID, OrgID: orgID,
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
@ -298,8 +299,8 @@ func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespa
} }
// GetUserVisibleNamespaces returns the folders that are visible to the user and have at least one alert in it // GetUserVisibleNamespaces returns the folders that are visible to the user and have at least one alert in it
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *user.SignedInUser) (map[string]*models.Folder, error) { func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *user.SignedInUser) (map[string]*folder.Folder, error) {
namespaceMap := make(map[string]*models.Folder) namespaceMap := make(map[string]*folder.Folder)
searchQuery := models.FindPersistedDashboardsQuery{ searchQuery := models.FindPersistedDashboardsQuery{
OrgId: orgID, OrgId: orgID,
@ -330,9 +331,9 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use
if !hit.IsFolder { if !hit.IsFolder {
continue continue
} }
namespaceMap[hit.UID] = &models.Folder{ namespaceMap[hit.UID] = &folder.Folder{
Id: hit.ID, ID: hit.ID,
Uid: hit.UID, UID: hit.UID,
Title: hit.Title, Title: hit.Title,
} }
} }
@ -342,15 +343,15 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use
} }
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces. // GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *user.SignedInUser, withCanSave bool) (*models.Folder, error) { func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *user.SignedInUser, withCanSave bool) (*folder.Folder, error) {
folder, err := st.FolderService.GetFolderByTitle(ctx, user, orgID, namespace) folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, Title: &namespace})
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if access control is disabled, check that the user is allowed to save in the folder. // if access control is disabled, check that the user is allowed to save in the folder.
if withCanSave && st.AccessControl.IsDisabled() { if withCanSave && st.AccessControl.IsDisabled() {
g := guardian.New(ctx, folder.Id, orgID, user) g := guardian.New(ctx, folder.ID, orgID, user)
if canSave, err := g.CanSave(); err != nil || !canSave { if canSave, err := g.CanSave(); err != nil || !canSave {
if err != nil { if err != nil {
st.Logger.Error("checking can save permission has failed", "userId", user.UserID, "username", user.Login, "namespace", namespace, "orgId", orgID, "error", err) st.Logger.Error("checking can save permission has failed", "userId", user.UserID, "username", user.Login, "namespace", namespace, "orgId", orgID, "error", err)
@ -363,8 +364,8 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, org
} }
// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces. // GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces.
func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*models.Folder, error) { func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*folder.Folder, error) {
folder, err := st.FolderService.GetFolderByUID(ctx, user, orgID, uid) folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, Title: &uid})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -22,7 +22,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
sqlStore := db.InitTestDB(t) sqlStore := db.InitTestDB(t)
store := DBstore{ store := &DBstore{
SQLStore: sqlStore, SQLStore: sqlStore,
Cfg: setting.UnifiedAlertingSettings{ Cfg: setting.UnifiedAlertingSettings{
BaseInterval: time.Duration(rand.Int63n(100)) * time.Second, BaseInterval: time.Duration(rand.Int63n(100)) * time.Second,
@ -136,7 +136,7 @@ func TestIntegration_CountAlertRules(t *testing.T) {
} }
sqlStore := db.InitTestDB(t) sqlStore := db.InitTestDB(t)
store := DBstore{SQLStore: sqlStore} store := &DBstore{SQLStore: sqlStore}
rule := createRule(t, store) rule := createRule(t, store)
tests := map[string]struct { tests := map[string]struct {
@ -175,7 +175,7 @@ func TestIntegration_CountAlertRules(t *testing.T) {
} }
} }
func createRule(t *testing.T, store DBstore) *models.AlertRule { func createRule(t *testing.T, store *DBstore) *models.AlertRule {
rule := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval))() rule := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval))()
err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error { err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Table(models.AlertRule{}).InsertOne(rule) _, err := sess.Table(models.AlertRule{}).InsertOne(rule)

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
"time" "time"
models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -23,7 +23,7 @@ type RuleStore struct {
Rules map[int64][]*models.AlertRule Rules map[int64][]*models.AlertRule
Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error
RecordedOps []interface{} RecordedOps []interface{}
Folders map[int64][]*models2.Folder Folders map[int64][]*folder.Folder
} }
type GenericRecordedQuery struct { type GenericRecordedQuery struct {
@ -38,7 +38,7 @@ func NewRuleStore(t *testing.T) *RuleStore {
Hook: func(interface{}) error { Hook: func(interface{}) error {
return nil return nil
}, },
Folders: map[int64][]*models2.Folder{}, Folders: map[int64][]*folder.Folder{},
} }
} }
@ -58,18 +58,18 @@ mainloop:
rgs = append(rgs, r) rgs = append(rgs, r)
f.Rules[r.OrgID] = rgs f.Rules[r.OrgID] = rgs
var existing *models2.Folder var existing *folder.Folder
folders := f.Folders[r.OrgID] folders := f.Folders[r.OrgID]
for _, folder := range folders { for _, folder := range folders {
if folder.Uid == r.NamespaceUID { if folder.UID == r.NamespaceUID {
existing = folder existing = folder
break break
} }
} }
if existing == nil { if existing == nil {
folders = append(folders, &models2.Folder{ folders = append(folders, &folder.Folder{
Id: rand.Int63(), ID: rand.Int63(),
Uid: r.NamespaceUID, UID: r.NamespaceUID,
Title: "TEST-FOLDER-" + util.GenerateShortUID(), Title: "TEST-FOLDER-" + util.GenerateShortUID(),
}) })
f.Folders[r.OrgID] = folders f.Folders[r.OrgID] = folders
@ -227,11 +227,11 @@ func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQu
return nil return nil
} }
func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *user.SignedInUser) (map[string]*models2.Folder, error) { func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *user.SignedInUser) (map[string]*folder.Folder, error) {
f.mtx.Lock() f.mtx.Lock()
defer f.mtx.Unlock() defer f.mtx.Unlock()
namespacesMap := map[string]*models2.Folder{} namespacesMap := map[string]*folder.Folder{}
_, ok := f.Rules[orgID] _, ok := f.Rules[orgID]
if !ok { if !ok {
@ -239,12 +239,12 @@ func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *
} }
for _, folder := range f.Folders[orgID] { for _, folder := range f.Folders[orgID] {
namespacesMap[folder.Uid] = folder namespacesMap[folder.UID] = folder
} }
return namespacesMap, nil return namespacesMap, nil
} }
func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *user.SignedInUser, _ bool) (*models2.Folder, error) { func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *user.SignedInUser, _ bool) (*folder.Folder, error) {
folders := f.Folders[orgID] folders := f.Folders[orgID]
for _, folder := range folders { for _, folder := range folders {
if folder.Title == title { if folder.Title == title {
@ -254,7 +254,7 @@ func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID i
return nil, fmt.Errorf("not found") return nil, fmt.Errorf("not found")
} }
func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ *user.SignedInUser) (*models2.Folder, error) { func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ *user.SignedInUser) (*folder.Folder, error) {
f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{
Name: "GetNamespaceByUID", Name: "GetNamespaceByUID",
Params: []interface{}{orgID, uid}, Params: []interface{}{orgID, uid},
@ -262,7 +262,7 @@ func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64
folders := f.Folders[orgID] folders := f.Folders[orgID]
for _, folder := range folders { for _, folder := range folders {
if folder.Uid == uid { if folder.UID == uid {
return folder, nil return folder, nil
} }
} }

View File

@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
gfmodels "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/annotations/annotationstest"
@ -68,6 +67,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
}) })
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{
BaseInterval: setting.SchedulerBaseInterval, BaseInterval: setting.SchedulerBaseInterval,
} }
@ -127,12 +127,10 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
} }
ctx = appcontext.WithUser(ctx, user) ctx = appcontext.WithUser(ctx, user)
f, err := dbstore.FolderService.Create(ctx, &folder.CreateFolderCommand{OrgID: orgID, Title: "FOLDER-" + util.GenerateShortUID(), UID: folderUID}) _, err := dbstore.FolderService.Create(ctx, &folder.CreateFolderCommand{OrgID: orgID, Title: "FOLDER-" + util.GenerateShortUID(), UID: folderUID})
var folder *gfmodels.Folder // var foldr *folder.Folder
if err == nil { if errors.Is(err, dashboards.ErrFolderWithSameUIDExists) || errors.Is(err, dashboards.ErrFolderVersionMismatch) {
folder = f.ToLegacyModel() _, err = dbstore.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, UID: &folderUID})
} else if errors.Is(err, dashboards.ErrFolderWithSameUIDExists) || errors.Is(err, dashboards.ErrFolderVersionMismatch) {
folder, err = dbstore.FolderService.GetFolderByUID(ctx, user, orgID, folderUID)
} }
require.NoError(t, err) require.NoError(t, err)
@ -160,7 +158,7 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
Labels: labels, Labels: labels,
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"}, Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
IntervalSeconds: intervalSeconds, IntervalSeconds: intervalSeconds,
NamespaceUID: folder.Uid, NamespaceUID: folderUID,
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
NoDataState: models.NoData, NoDataState: models.NoData,
ExecErrState: models.AlertingErrState, ExecErrState: models.AlertingErrState,
@ -170,7 +168,7 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: orgID,
NamespaceUIDs: []string{folder.Uid}, NamespaceUIDs: []string{folderUID},
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
} }
err = dbstore.ListAlertRules(ctx, &q) err = dbstore.ListAlertRules(ctx, &q)
@ -178,6 +176,6 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
require.NotEmpty(t, q.Result) require.NotEmpty(t, q.Result)
rule := q.Result[0] rule := q.Result[0]
t.Logf("alert definition: %v with title: %q interval: %d folder: %s created", rule.GetKey(), rule.Title, rule.IntervalSeconds, folder.Uid) t.Logf("alert definition: %v with title: %q interval: %d folder: %s created", rule.GetKey(), rule.Title, rule.IntervalSeconds, folderUID)
return rule return rule
} }