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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 372 additions and 420 deletions

View File

@ -24,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
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.UserId = c.UserID
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 errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(400, "Folder not found", err)
}
return response.Error(500, "Error while checking folder ID", err)
}
cmd.FolderId = folder.Id
cmd.FolderId = folder.ID
}
dash := cmd.GetDashboardModel()

View File

@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboardversion/dashvertest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/libraryelements"
"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")).
Return(&models.Dashboard{Id: dashID, Uid: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil)
mockFolder := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{Id: 1, Uid: "folderUID", Title: "Folder"},
mockFolder := &foldertest.FakeService{
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) {
@ -673,8 +674,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashboardService := dashboards.NewFakeDashboardService(t)
mockFolder := &fakeFolderService{
GetFolderByUIDError: errors.New("Error while searching Folder ID"),
mockFolder := &foldertest.FakeService{
ExpectedError: errors.New("Error while searching Folder ID"),
}
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
// 500: internalServerError
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 {
return apierrors.ToFolderErrorResponse(err)
}
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, folder))
g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, folder))
}
// 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 {
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 {
return apierrors.ToFolderErrorResponse(err)
}
g := guardian.New(c.Req.Context(), folder.Id, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, folder))
g := guardian.New(c.Req.Context(), folder.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, folder))
}
// swagger:route POST /folders folders createFolder
@ -182,8 +183,8 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response {
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
g := guardian.New(c.Req.Context(), result.Id, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.toFolderDto(c, g, result))
g := guardian.New(c.Req.Context(), result.ID, c.OrgID, c.SignedInUser)
return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, result))
}
// 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, "")
}
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 {
canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave()

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@ -26,13 +27,14 @@ import (
// 404: notFoundError
// 500: internalServerError
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 {
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 {
return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied)
@ -49,7 +51,7 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res
continue
}
perm.FolderId = folder.Id
perm.FolderId = folder.ID
perm.DashboardId = 0
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)
}
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 {
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()
if err != nil {
return apierrors.ToFolderErrorResponse(err)
@ -106,7 +109,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
for _, item := range apiCmd.Items {
items = append(items, &models.DashboardACL{
OrgID: c.OrgID,
DashboardID: folder.Id,
DashboardID: folder.ID,
UserID: item.UserID,
TeamID: item.TeamID,
Role: item.Role,
@ -140,13 +143,13 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
if err != nil {
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.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) {
err = models.ErrFolderACLInfoMissing
}
@ -163,7 +166,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
return response.JSON(http.StatusOK, util.DynMap{
"message": "Folder permissions updated",
"id": folder.Id,
"id": folder.ID,
"title": folder.Title,
})
}

View File

@ -1,7 +1,6 @@
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -294,47 +293,3 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
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
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 {
return nil, err
}
req.FolderId = folder.Id
req.FolderId = folder.ID
} 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 {
return nil, err
}
req.FolderUid = folder.Uid
req.FolderUid = folder.UID
}
saveCmd := models.SaveDashboardCommand{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,7 @@ type Service struct {
searchService *search.SearchService
features *featuremgmt.FeatureManager
permissions accesscontrol.FolderPermissionsService
accessControl accesscontrol.AccessControl
// bus is currently used to publish events that cause scheduler to update rules.
bus bus.Bus
@ -62,10 +63,41 @@ func ProvideService(
searchService: searchService,
features: features,
permissions: folderPermissionsService,
accessControl: ac,
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) {
searchQuery := search.Query{
SignedInUser: user,
@ -95,9 +127,9 @@ func (s *Service) GetFolders(ctx context.Context, user *user.SignedInUser, orgID
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 {
return &models.Folder{Id: id, Title: "General"}, nil
return &folder.Folder{ID: id, Title: "General"}, nil
}
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
}
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 err != nil {
return nil, toFolderError(err)
@ -116,13 +148,13 @@ func (s *Service) GetFolderByID(ctx context.Context, user *user.SignedInUser, id
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)
if err != nil {
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 err != nil {
return nil, toFolderError(err)
@ -133,13 +165,13 @@ func (s *Service) GetFolderByUID(ctx context.Context, user *user.SignedInUser, o
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)
if err != nil {
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 err != nil {
return nil, toFolderError(err)
@ -189,7 +221,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
return nil, toFolderError(err)
}
var createdFolder *models.Folder
var createdFolder *folder.Folder
createdFolder, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.Id)
if err != nil {
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()},
}...)
_, 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 {
permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.Id, true)
permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.ID, true)
}
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
// (legacy) folder.
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 {
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
}
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)
if err != nil {
return nil, err
@ -276,7 +308,7 @@ func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int
if err != nil {
return nil, err
}
_, err = s.store.Update(ctx, folder.UpdateFolderCommand{
foldr, err := s.store.Update(ctx, folder.UpdateFolderCommand{
Folder: getFolder,
NewUID: &cmd.Uid,
NewTitle: &cmd.Title,
@ -285,11 +317,12 @@ func (s *Service) Update(ctx context.Context, user *user.SignedInUser, orgID int
if err != nil {
return nil, err
}
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}
_, err := s.dashboardStore.GetDashboard(ctx, &query)
if err != nil {
@ -322,7 +355,7 @@ func (s *Service) legacyUpdate(ctx context.Context, user *user.SignedInUser, org
return nil, toFolderError(err)
}
var foldr *models.Folder
var foldr *folder.Folder
foldr, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
if err != nil {
return nil, err
@ -359,7 +392,7 @@ func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderComm
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 err != nil {
return toFolderError(err)
@ -367,7 +400,7 @@ func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderComm
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 {
return toFolderError(err)
@ -416,12 +449,6 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
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) {
// check the flag, if old - do whatever did before
// 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/tracing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
@ -79,26 +80,26 @@ func TestIntegrationFolderService(t *testing.T) {
folderId := rand.Int63()
folderUID := util.GenerateShortUID()
f := models.NewFolder("Folder")
f.Id = folderId
f.Uid = folderUID
f := folder.NewFolder("Folder", "")
f.ID = folderId
f.UID = folderUID
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).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) {
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
_, err := service.getFolderByID(context.Background(), usr, folderId, orgID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
})
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.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) {
_, err := service.GetFolderByUID(context.Background(), usr, orgID, folderUID)
_, err := service.getFolderByUID(context.Background(), usr, orgID, folderUID)
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) {
dash := models.NewDashboardFolder("Test-Folder")
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("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
@ -170,7 +171,7 @@ func TestIntegrationFolderService(t *testing.T) {
UID: "someuid",
})
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) {
@ -190,7 +191,7 @@ func TestIntegrationFolderService(t *testing.T) {
dashboardFolder := models.NewDashboardFolder("Folder")
dashboardFolder.Id = rand.Int63()
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("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) {
f := models.NewFolder(util.GenerateShortUID())
f.Id = rand.Int63()
f.Uid = util.GenerateShortUID()
dashStore.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
f := folder.NewFolder(util.GenerateShortUID(), "")
f.ID = rand.Int63()
f.UID = util.GenerateShortUID()
dashStore.On("GetFolderByUID", mock.Anything, orgID, f.UID).Return(f, nil)
var actualCmd *models.DeleteDashboardCommand
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 = appcontext.WithUser(ctx, usr)
err := service.DeleteFolder(ctx, &folder.DeleteFolderCommand{
UID: f.Uid,
UID: f.UID,
OrgID: orgID,
ForceDeleteRules: expectedForceDeleteRules,
})
require.NoError(t, err)
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, expectedForceDeleteRules, actualCmd.ForceDeleteFolderRules)
})
@ -242,33 +243,33 @@ func TestIntegrationFolderService(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true})
t.Run("When get folder by id should return folder", func(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID())
expected.Id = rand.Int63()
expected := folder.NewFolder(util.GenerateShortUID(), "")
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.NoError(t, err)
})
t.Run("When get folder by uid should return folder", func(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID())
expected.Uid = util.GenerateShortUID()
expected := folder.NewFolder(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.NoError(t, err)
})
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)
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.NoError(t, err)
})
@ -311,7 +312,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore := dashboards.FakeDashboardStore{}
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.RBACEnabled = false
nestedFoldersEnabled := true
@ -338,18 +339,6 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
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) {
folderStore.ExpectedFolder = &folder.Folder{}
_, err := folderService.GetParents(context.Background(), &folder.GetParentsQuery{})
@ -378,12 +367,6 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
require.NoError(t, err)
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) {
@ -412,7 +395,7 @@ func TestNestedFolderService(t *testing.T) {
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
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("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)
_, 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) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).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
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
@ -463,6 +446,9 @@ func TestNestedFolderService(t *testing.T) {
dashboardStore: dashStore,
store: store,
features: features,
accessControl: actest.FakeAccessControl{
ExpectedEvaluate: true,
},
}
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.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, 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)
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
OrgID: orgID,
@ -493,8 +479,8 @@ func TestNestedFolderService(t *testing.T) {
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
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("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).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(&folder.Folder{}, nil)
// return an error from the folder store
store.ExpectedError = errors.New("FAILED")

View File

@ -14,29 +14,25 @@ type FakeService struct {
ExpectedError error
}
func NewFakeService() *FakeService {
return &FakeService{}
}
var _ folder.Service = (*FakeService)(nil)
func (s *FakeService) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
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) {
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()
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
return s.ExpectedFolder, s.ExpectedError
}
func (s *FakeService) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
return s.ExpectedError

View File

@ -9,13 +9,18 @@ import (
type Service interface {
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)
// 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
// 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
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
// Move changes a folder's parent folder to the requested new parent.
@ -35,12 +40,6 @@ type NestedFolderService interface {
// dashboards in the folder.
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
// folder, starting with the root node and ending with the requested child
// node.

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/web"
)
@ -47,11 +48,11 @@ func (l *LibraryElementService) createHandler(c *models.ReqContext) response.Res
if *cmd.FolderUID == "" {
cmd.FolderID = 0
} 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 {
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 {
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 {
return response.Error(http.StatusInternalServerError, "failed to get folder", err)
}
element.FolderUID = folder.Uid
element.Meta.FolderUID = folder.Uid
element.FolderUID = folder.UID
element.Meta.FolderUID = folder.UID
element.Meta.FolderName = folder.Title
}
@ -175,11 +176,11 @@ func (l *LibraryElementService) patchHandler(c *models.ReqContext) response.Resp
if *cmd.FolderUID == "" {
cmd.FolderID = 0
} 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 {
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 {
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 {
return response.Error(http.StatusInternalServerError, "failed to get folder", err)
}
element.FolderUID = folder.Uid
element.Meta.FolderUID = folder.Uid
element.FolderUID = folder.UID
element.Meta.FolderUID = folder.UID
element.Meta.FolderName = folder.Title
}

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"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/org"
"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) {
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 {
return err
}
g := guardian.New(ctx, folder.Id, user.OrgID, user)
g := guardian.New(ctx, folder.ID, user.OrgID, user)
canEdit, err := g.CanEdit()
if err != nil {
@ -62,12 +63,12 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte
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 {
return err
}
g := guardian.New(ctx, folder.Id, user.OrgID, user)
g := guardian.New(ctx, folder.ID, user.OrgID, user)
canView, err := g.CanView()
if err != nil {

View File

@ -13,7 +13,7 @@ import (
func TestCreateLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail",
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)
resp := sc.service.createHandler(sc.reqContext)
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",
func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Nonexistent UID")
command := getCreatePanelCommand(sc.folder.ID, "Nonexistent UID")
command.UID = util.GenerateShortUID()
sc.reqContext.Req.Body = mockRequestBody(command)
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",
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
sc.reqContext.Req.Body = mockRequestBody(command)
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",
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"
sc.reqContext.Req.Body = mockRequestBody(command)
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",
func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Invalid UID")
command := getCreatePanelCommand(sc.folder.ID, "Invalid UID")
command.UID = "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K"
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)

View File

@ -73,7 +73,7 @@ func TestDeleteLibraryElement(t *testing.T) {
Title: "Testing deleteHandler ",
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)
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",
func(t *testing.T, sc scenarioContext) {
command := getCreateVariableCommand(sc.folder.Id, "query0")
command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -77,7 +77,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
func(t *testing.T, sc scenarioContext) {
command := getCreateVariableCommand(sc.folder.Id, "query0")
command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -142,7 +142,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -204,7 +204,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated,
@ -239,7 +239,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -304,7 +304,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated,
@ -339,7 +339,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created,
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",
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}",
"id": 1,
@ -378,7 +378,7 @@ func TestGetAllLibraryElements(t *testing.T) {
resp := sc.service.createHandler(sc.reqContext)
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}",
"id": 1,
@ -425,7 +425,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated,
@ -460,7 +460,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created,
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",
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}",
"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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -661,7 +661,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated,
@ -696,7 +696,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -761,7 +761,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -826,7 +826,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -892,7 +892,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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}",
"id": 1,
@ -967,7 +967,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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}",
"id": 1,
@ -1040,7 +1040,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
Updated: result.Result.Elements[0].Meta.Updated,
@ -1075,7 +1075,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[1].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -1142,7 +1142,7 @@ func TestGetAllLibraryElements(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
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",
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)
resp := sc.service.createHandler(sc.reqContext)
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",
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)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())

View File

@ -49,7 +49,7 @@ func TestGetLibraryElement(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: res.Result.Meta.Created,
Updated: res.Result.Meta.Updated,
@ -119,7 +119,7 @@ func TestGetLibraryElement(t *testing.T) {
Title: "Testing getHandler",
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)
require.NoError(t, err)
@ -144,7 +144,7 @@ func TestGetLibraryElement(t *testing.T) {
Version: 1,
Meta: LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: sc.folder.Uid,
FolderUID: sc.folder.UID,
ConnectedDashboards: 1,
Created: res.Result.Meta.Created,
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",
func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.Id, "Existing UID")
command := getCreatePanelCommand(sc.folder.ID, "Existing UID")
command.UID = util.GenerateShortUID()
sc.reqContext.Req.Body = mockRequestBody(command)
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",
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)
resp := sc.service.createHandler(sc.reqContext)
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",
func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id,
FolderID: sc.folder.ID,
Version: 1,
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",
func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id,
FolderID: sc.folder.ID,
Version: 1,
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",
func(t *testing.T, sc scenarioContext) {
cmd := PatchLibraryElementCommand{
FolderID: sc.folder.Id,
FolderID: sc.folder.ID,
Version: 1,
Kind: int64(models.VariableElement),
}

View File

@ -73,23 +73,23 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) {
Title: "Testing DeleteLibraryElementsInFolder",
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)
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())
})
scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail",
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())
})
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) {
command := getCreateVariableCommand(sc.folder.Id, "query0")
command := getCreateVariableCommand(sc.folder.ID, "query0")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -102,7 +102,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) {
require.NotNil(t, result.Result)
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)
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -146,7 +146,7 @@ func TestGetLibraryPanelConnections(t *testing.T) {
Title: "Testing GetLibraryPanelConnections",
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)
require.NoError(t, err)
@ -256,7 +256,7 @@ type scenarioContext struct {
service *LibraryElementService
reqContext *models.ReqContext
user user.SignedInUser
folder *models.Folder
folder *folder.Folder
initialResult libraryElementResult
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{})
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)
resp := sc.service.createHandler(sc.reqContext)
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.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{
"Content-Type": []string{"application/json"},
},
}}
orgID := int64(1)
role := org.RoleAdmin
}
ctx := appcontext.WithUser(context.Background(), &usr)
req = req.WithContext(ctx)
webCtx := web.Context{Req: req}
sqlStore := db.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
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),
}
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
// what to expect in the tests
// 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{
user: usr,
ctx: &ctx,
ctx: &webCtx,
service: &service,
sqlStore: sqlStore,
reqContext: &models.ReqContext{
Context: &ctx,
Context: &webCtx,
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)
})

View File

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

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
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)
}
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{
Name: groupName,
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
q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid},
NamespaceUIDs: []string{namespace.UID},
RuleGroup: ruleGroup,
}
if err = srv.store.ListAlertRules(ctx, &q); err != nil {
@ -163,7 +163,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext, namespace
q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid},
NamespaceUIDs: []string{namespace.UID},
}
if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil {
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) {
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)
@ -205,7 +205,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext, namespaceTitl
q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgID,
NamespaceUIDs: []string{namespace.Uid},
NamespaceUIDs: []string{namespace.UID},
RuleGroup: ruleGroup,
}
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{
GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, q.Result, namespace.Id, provenanceRecords),
GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, q.Result, namespace.ID, provenanceRecords),
}
return response.JSON(http.StatusAccepted, result)
}
@ -296,7 +296,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
continue
}
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)
}
@ -316,7 +316,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
groupKey := ngmodels.AlertRuleGroupKey{
OrgID: c.SignedInUser.OrgID,
NamespaceUID: namespace.Uid,
NamespaceUID: namespace.UID,
RuleGroup: ruleGroupConfig.Name,
}

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"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"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
@ -382,7 +383,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
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()))
ruleStore.PutRule(context.Background(), expectedRules...)
@ -426,12 +427,12 @@ func TestRouteGetRulesConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
folder1 := randFolder()
folder2 := randFolder()
ruleStore.Folders[orgID] = []*models2.Folder{folder1, folder2}
ruleStore.Folders[orgID] = []*folder.Folder{folder1, folder2}
group1Key := models.GenerateGroupKey(orgID)
group1Key.NamespaceUID = folder1.Uid
group1Key.NamespaceUID = folder1.UID
group2Key := models.GenerateGroupKey(orgID)
group2Key.NamespaceUID = folder2.Uid
group2Key.NamespaceUID = folder2.UID
group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key)))
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.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
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()))
ruleStore.PutRule(context.Background(), expectedRules...)
@ -509,7 +510,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid
groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
ruleStore.PutRule(context.Background(), expectedRules...)
@ -544,7 +545,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
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()))
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) {
rule.NamespaceUID = namespace.Uid
rule.NamespaceUID = namespace.UID
}
}

View File

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

View File

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

View File

@ -3,15 +3,15 @@ package api
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/folder"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/user"
)
// RuleStore is the interface for persisting alert rules and instances
type RuleStore interface {
GetUserVisibleNamespaces(context.Context, int64, *user.SignedInUser) (map[string]*models.Folder, error)
GetNamespaceByTitle(context.Context, string, int64, *user.SignedInUser, bool) (*models.Folder, error)
GetUserVisibleNamespaces(context.Context, int64, *user.SignedInUser) (map[string]*folder.Folder, error)
GetNamespaceByTitle(context.Context, string, int64, *user.SignedInUser, bool) (*folder.Folder, error)
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) 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/pkg/expr"
models2 "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/folder"
"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) {
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/infra/log"
"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/schedule"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
@ -22,9 +22,9 @@ import (
func Test_subscribeToFolderChanges(t *testing.T) {
orgID := rand.Int63()
folder := &models2.Folder{
Id: 0,
Uid: util.GenerateShortUID(),
folder := &folder.Folder{
ID: 0,
UID: util.GenerateShortUID(),
Title: "Folder" + util.GenerateShortUID(),
}
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{
Timestamp: time.Now(),
Title: "Folder" + util.GenerateShortUID(),
ID: folder.Id,
UID: folder.Uid,
ID: folder.ID,
UID: folder.UID,
OrgID: orgID,
})
require.NoError(t, err)

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"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
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *user.SignedInUser) (map[string]*models.Folder, error) {
namespaceMap := make(map[string]*models.Folder)
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *user.SignedInUser) (map[string]*folder.Folder, error) {
namespaceMap := make(map[string]*folder.Folder)
searchQuery := models.FindPersistedDashboardsQuery{
OrgId: orgID,
@ -330,9 +331,9 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use
if !hit.IsFolder {
continue
}
namespaceMap[hit.UID] = &models.Folder{
Id: hit.ID,
Uid: hit.UID,
namespaceMap[hit.UID] = &folder.Folder{
ID: hit.ID,
UID: hit.UID,
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.
func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *user.SignedInUser, withCanSave bool) (*models.Folder, error) {
folder, err := st.FolderService.GetFolderByTitle(ctx, user, orgID, namespace)
func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *user.SignedInUser, withCanSave bool) (*folder.Folder, error) {
folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, Title: &namespace})
if err != nil {
return nil, err
}
// if access control is disabled, check that the user is allowed to save in the folder.
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 err != nil {
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.
func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*models.Folder, error) {
folder, err := st.FolderService.GetFolderByUID(ctx, user, orgID, uid)
func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*folder.Folder, error) {
folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, Title: &uid})
if err != nil {
return nil, err
}

View File

@ -22,7 +22,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
t.Skip("skipping integration test")
}
sqlStore := db.InitTestDB(t)
store := DBstore{
store := &DBstore{
SQLStore: sqlStore,
Cfg: setting.UnifiedAlertingSettings{
BaseInterval: time.Duration(rand.Int63n(100)) * time.Second,
@ -136,7 +136,7 @@ func TestIntegration_CountAlertRules(t *testing.T) {
}
sqlStore := db.InitTestDB(t)
store := DBstore{SQLStore: sqlStore}
store := &DBstore{SQLStore: sqlStore}
rule := createRule(t, store)
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))()
err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Table(models.AlertRule{}).InsertOne(rule)

View File

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

View File

@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
gfmodels "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"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.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{
BaseInterval: setting.SchedulerBaseInterval,
}
@ -127,12 +127,10 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
}
ctx = appcontext.WithUser(ctx, user)
f, err := dbstore.FolderService.Create(ctx, &folder.CreateFolderCommand{OrgID: orgID, Title: "FOLDER-" + util.GenerateShortUID(), UID: folderUID})
var folder *gfmodels.Folder
if err == nil {
folder = f.ToLegacyModel()
} else if errors.Is(err, dashboards.ErrFolderWithSameUIDExists) || errors.Is(err, dashboards.ErrFolderVersionMismatch) {
folder, err = dbstore.FolderService.GetFolderByUID(ctx, user, orgID, folderUID)
_, err := dbstore.FolderService.Create(ctx, &folder.CreateFolderCommand{OrgID: orgID, Title: "FOLDER-" + util.GenerateShortUID(), UID: folderUID})
// var foldr *folder.Folder
if errors.Is(err, dashboards.ErrFolderWithSameUIDExists) || errors.Is(err, dashboards.ErrFolderVersionMismatch) {
_, err = dbstore.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, UID: &folderUID})
}
require.NoError(t, err)
@ -160,7 +158,7 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
Labels: labels,
Annotations: map[string]string{"testAnnoKey": "testAnnoValue"},
IntervalSeconds: intervalSeconds,
NamespaceUID: folder.Uid,
NamespaceUID: folderUID,
RuleGroup: ruleGroup,
NoDataState: models.NoData,
ExecErrState: models.AlertingErrState,
@ -170,7 +168,7 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
q := models.ListAlertRulesQuery{
OrgID: orgID,
NamespaceUIDs: []string{folder.Uid},
NamespaceUIDs: []string{folderUID},
RuleGroup: ruleGroup,
}
err = dbstore.ListAlertRules(ctx, &q)
@ -178,6 +176,6 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
require.NotEmpty(t, q.Result)
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
}