mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
API: Support creating a nested folder (#58508)
* API: Support nested folder creation * Update swagger * fixup * Update pkg/api/dtos/folder.go Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com> * Fix some tests * create legacy folder url from title and uid Co-authored-by: idafurjes <36131195+idafurjes@users.noreply.github.com> Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com> Co-authored-by: Ida Furjesova <ida.furjesova@grafana.com>
This commit is contained in:
parent
b5388bb080
commit
bf5a08e039
@ -22,6 +22,8 @@ type Folder struct {
|
||||
Updated time.Time `json:"updated"`
|
||||
Version int `json:"version"`
|
||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||
// only used if nested folders are enabled
|
||||
ParentUID string `json:"parentUid"`
|
||||
}
|
||||
|
||||
type FolderSearchHit struct {
|
||||
|
@ -105,6 +105,8 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
|
||||
//
|
||||
// Create folder.
|
||||
//
|
||||
// If nested folders are enabled then it additionally expects the parent folder UID.
|
||||
//
|
||||
// Responses:
|
||||
// 200: folderResponse
|
||||
// 400: badRequestError
|
||||
@ -113,23 +115,29 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
|
||||
// 409: conflictError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response {
|
||||
cmd := models.CreateFolderCommand{}
|
||||
cmd := folder.CreateFolderCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
folder, err := hs.folderService.CreateFolder(c.Req.Context(), c.SignedInUser, c.OrgID, cmd.Title, cmd.Uid)
|
||||
cmd.OrgID = c.OrgID
|
||||
|
||||
folder, err := hs.folderService.Create(c.Req.Context(), &cmd)
|
||||
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)
|
||||
// TODO set ParentUID if nested folders are enabled
|
||||
return response.JSON(http.StatusOK, hs.newToFolderDto(c, g, folder))
|
||||
}
|
||||
|
||||
// swagger:route PUT /folders/{folder_uid} folders updateFolder
|
||||
//
|
||||
// Update folder.
|
||||
//
|
||||
// If nested folders are enabled then it optionally expects a new parent folder UID that moves the folder and
|
||||
// includes it into the response.
|
||||
//
|
||||
// Responses:
|
||||
// 200: folderResponse
|
||||
// 400: badRequestError
|
||||
@ -156,6 +164,7 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response {
|
||||
// Delete folder.
|
||||
//
|
||||
// Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.
|
||||
// If nested folders are enabled then it also deletes all the subfolders.
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteFolderResponse
|
||||
@ -216,6 +225,42 @@ func (hs *HTTPServer) toFolderDto(c *models.ReqContext, g guardian.DashboardGuar
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) newToFolderDto(c *models.ReqContext, g guardian.DashboardGuardian, folder *folder.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),
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters getFolders
|
||||
type GetFoldersParams struct {
|
||||
// Limit the maximum number of folders to return
|
||||
@ -262,7 +307,7 @@ type GetFolderByIDParams struct {
|
||||
type CreateFolderParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body models.CreateFolderCommand `json:"body"`
|
||||
Body folder.CreateFolderCommand `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:parameters deleteFolder
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
service "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"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/org"
|
||||
@ -130,7 +131,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder"}
|
||||
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
mockSQLStore := mockstore.NewSQLStoreMock()
|
||||
|
||||
@ -187,7 +188,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
|
||||
})
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder"}
|
||||
|
||||
cmd := dtos.UpdateDashboardACLCommand{
|
||||
Items: []dtos.DashboardACLUpdateItem{
|
||||
@ -251,7 +252,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
|
||||
)
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder"}
|
||||
|
||||
cmd := dtos.UpdateDashboardACLCommand{
|
||||
Items: []dtos.DashboardACLUpdateItem{
|
||||
@ -297,7 +298,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
|
||||
var gotItems []*models.DashboardACL
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder"}
|
||||
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
gotItems = args.Get(2).([]*models.DashboardACL)
|
||||
}).Return(nil).Once()
|
||||
|
@ -39,7 +39,7 @@ func TestFoldersAPIEndpoint(t *testing.T) {
|
||||
Title: "Folder",
|
||||
}
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder"}
|
||||
|
||||
createFolderScenario(t, "When calling POST on", "/api/folders", "/api/folders", folderService, cmd,
|
||||
func(sc *scenarioContext) {
|
||||
@ -94,7 +94,7 @@ func TestFoldersAPIEndpoint(t *testing.T) {
|
||||
Title: "Folder upd",
|
||||
}
|
||||
|
||||
folderService.ExpectedFolder = &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"}
|
||||
folderService.ExpectedFolder = &folder.Folder{ID: 1, UID: "uid", Title: "Folder upd"}
|
||||
|
||||
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", folderService, cmd,
|
||||
func(sc *scenarioContext) {
|
||||
@ -179,7 +179,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should attach access control metadata to folder response", func(t *testing.T) {
|
||||
folderService.ExpectedFolder = &models.Folder{Uid: "folderUid"}
|
||||
folderService.ExpectedFolder = &folder.Folder{UID: "folderUid"}
|
||||
|
||||
req := server.NewGetRequest("/api/folders/folderUid?accesscontrol=true")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
@ -202,7 +202,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should attach access control metadata to folder response", func(t *testing.T) {
|
||||
folderService.ExpectedFolder = &models.Folder{Uid: "folderUid"}
|
||||
folderService.ExpectedFolder = &folder.Folder{UID: "folderUid"}
|
||||
|
||||
req := server.NewGetRequest("/api/folders/folderUid")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -55,9 +56,9 @@ func TestImportDashboardService(t *testing.T) {
|
||||
},
|
||||
}
|
||||
folderService := &foldertest.FakeService{
|
||||
ExpectedFolder: &models.Folder{
|
||||
Id: 5,
|
||||
Uid: "123",
|
||||
ExpectedFolder: &folder.Folder{
|
||||
ID: 5,
|
||||
UID: "123",
|
||||
},
|
||||
}
|
||||
|
||||
@ -115,9 +116,9 @@ func TestImportDashboardService(t *testing.T) {
|
||||
}
|
||||
libraryPanelService := &libraryPanelServiceMock{}
|
||||
folderService := &foldertest.FakeService{
|
||||
ExpectedFolder: &models.Folder{
|
||||
Id: 5,
|
||||
Uid: "123",
|
||||
ExpectedFolder: &folder.Folder{
|
||||
ID: 5,
|
||||
UID: "123",
|
||||
},
|
||||
}
|
||||
s := &ImportDashboardService{
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
@ -150,16 +149,21 @@ func (s *Service) GetFolderByTitle(ctx context.Context, user *user.SignedInUser,
|
||||
return dashFolder, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
|
||||
dashFolder := models.NewDashboardFolder(title)
|
||||
dashFolder.OrgId = orgID
|
||||
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
dashFolder := models.NewDashboardFolder(cmd.Title)
|
||||
dashFolder.OrgId = cmd.OrgID
|
||||
|
||||
trimmedUID := strings.TrimSpace(uid)
|
||||
trimmedUID := strings.TrimSpace(cmd.UID)
|
||||
if trimmedUID == accesscontrol.GeneralFolderUID {
|
||||
return nil, dashboards.ErrFolderInvalidUID
|
||||
}
|
||||
|
||||
dashFolder.SetUid(trimmedUID)
|
||||
|
||||
user, err := appcontext.User(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userID := user.UserID
|
||||
if userID == 0 {
|
||||
userID = -1
|
||||
@ -170,7 +174,7 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
||||
|
||||
dto := &dashboards.SaveDashboardDTO{
|
||||
Dashboard: dashFolder,
|
||||
OrgId: orgID,
|
||||
OrgId: cmd.OrgID,
|
||||
User: user,
|
||||
}
|
||||
|
||||
@ -185,7 +189,7 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
||||
}
|
||||
|
||||
var createdFolder *models.Folder
|
||||
createdFolder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
|
||||
createdFolder, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -204,9 +208,9 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
|
||||
}...)
|
||||
|
||||
_, permissionErr = s.permissions.SetPermissions(ctx, 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, orgID, userID, createdFolder.Id, true)
|
||||
permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.Id, true)
|
||||
}
|
||||
|
||||
if permissionErr != nil {
|
||||
@ -219,31 +223,34 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
|
||||
description = dash.Data.Get("description").MustString()
|
||||
}
|
||||
|
||||
parentUID := folder.RootFolderUID
|
||||
if cmd.ParentUID != "" {
|
||||
parentUID = cmd.ParentUID
|
||||
}
|
||||
_, err := s.store.Create(ctx, folder.CreateFolderCommand{
|
||||
// TODO: Today, if a UID isn't specified, the dashboard store
|
||||
// generates a new UID. The new folder store will need to do this as
|
||||
// well, but for now we take the UID from the newly created folder.
|
||||
UID: dash.Uid,
|
||||
OrgID: orgID,
|
||||
Title: title,
|
||||
OrgID: cmd.OrgID,
|
||||
Title: cmd.Title,
|
||||
Description: description,
|
||||
ParentUID: folder.RootFolderUID,
|
||||
ParentUID: parentUID,
|
||||
})
|
||||
if err != nil {
|
||||
// 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: 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)
|
||||
}
|
||||
return createdFolder, err
|
||||
return folder.FromDashboard(dash), err
|
||||
}
|
||||
// The folder UID is specified (or generated) during creation, so we'll
|
||||
// stop here and return the created model.Folder.
|
||||
}
|
||||
return createdFolder, nil
|
||||
return folder.FromDashboard(dash), nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
||||
@ -333,15 +340,6 @@ func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderComm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
// check the flag, if old - do whatever did before
|
||||
// for new only the store
|
||||
if cmd.UID == "" {
|
||||
cmd.UID = util.GenerateShortUID()
|
||||
}
|
||||
return s.store.Create(ctx, *cmd)
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
|
||||
// check the flag, if old - do whatever did before
|
||||
// for new only the store
|
||||
|
@ -79,12 +79,12 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
folderId := rand.Int63()
|
||||
folderUID := util.GenerateShortUID()
|
||||
|
||||
newFolder := models.NewFolder("Folder")
|
||||
newFolder.Id = folderId
|
||||
newFolder.Uid = folderUID
|
||||
f := models.NewFolder("Folder")
|
||||
f.Id = folderId
|
||||
f.Uid = folderUID
|
||||
|
||||
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil)
|
||||
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil)
|
||||
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)
|
||||
@ -104,7 +104,12 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
|
||||
t.Run("When creating folder should return access denied error", func(t *testing.T) {
|
||||
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
|
||||
_, err := service.CreateFolder(context.Background(), usr, orgID, newFolder.Title, folderUID)
|
||||
ctx := appcontext.WithUser(context.Background(), usr)
|
||||
_, err := service.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: f.Title,
|
||||
UID: folderUID,
|
||||
})
|
||||
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
|
||||
})
|
||||
|
||||
@ -158,16 +163,26 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
|
||||
dashStore.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
|
||||
|
||||
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "someuid")
|
||||
ctx := appcontext.WithUser(context.Background(), usr)
|
||||
actualFolder, err := service.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: dash.Title,
|
||||
UID: "someuid",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, f, actualFolder)
|
||||
require.Equal(t, f, actualFolder.ToLegacyModel())
|
||||
})
|
||||
|
||||
t.Run("When creating folder should return error if uid is general", func(t *testing.T) {
|
||||
dash := models.NewDashboardFolder("Test-Folder")
|
||||
dash.Id = rand.Int63()
|
||||
|
||||
_, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "general")
|
||||
ctx := appcontext.WithUser(context.Background(), usr)
|
||||
_, err := service.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: dash.Title,
|
||||
UID: "general",
|
||||
})
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID)
|
||||
})
|
||||
|
||||
@ -287,14 +302,38 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestFolderService(t *testing.T) {
|
||||
func TestNestedFolderServiceFeatureToggle(t *testing.T) {
|
||||
folderStore := NewFakeStore()
|
||||
|
||||
dashboardsvc := dashboards.FakeDashboardService{}
|
||||
dashboardsvc.On("BuildSaveDashboardCommand",
|
||||
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
|
||||
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)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
nestedFoldersEnabled := true
|
||||
features := featuremgmt.WithFeatures()
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool {
|
||||
if key == featuremgmt.FlagNestedFolders {
|
||||
return nestedFoldersEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||
folderService := &Service{
|
||||
store: folderStore,
|
||||
cfg: cfg,
|
||||
store: folderStore,
|
||||
dashboardStore: &dashStore,
|
||||
dashboardService: &dashboardsvc,
|
||||
features: features,
|
||||
}
|
||||
t.Run("create folder", func(t *testing.T) {
|
||||
folderStore.ExpectedFolder = &folder.Folder{}
|
||||
res, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{})
|
||||
ctx := appcontext.WithUser(context.Background(), usr)
|
||||
res, err := folderService.Create(ctx, &folder.CreateFolderCommand{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res.UID)
|
||||
})
|
||||
@ -381,7 +420,12 @@ func TestNestedFolderService(t *testing.T) {
|
||||
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)
|
||||
|
||||
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||
ctx = appcontext.WithUser(ctx, usr)
|
||||
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: "myFolder",
|
||||
UID: "myFolder",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
|
||||
require.False(t, store.CreateCalled)
|
||||
@ -434,7 +478,12 @@ func TestNestedFolderService(t *testing.T) {
|
||||
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)
|
||||
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||
ctx = appcontext.WithUser(ctx, usr)
|
||||
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: "myFolder",
|
||||
UID: "myFolder",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// CreateFolder should also call the folder store's create method.
|
||||
require.True(t, store.CreateCalled)
|
||||
@ -457,7 +506,12 @@ func TestNestedFolderService(t *testing.T) {
|
||||
store.ExpectedError = errors.New("FAILED")
|
||||
|
||||
// the service return success as long as the legacy create succeeds
|
||||
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
|
||||
ctx = appcontext.WithUser(ctx, usr)
|
||||
_, err := foldersvc.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
Title: "myFolder",
|
||||
UID: "myFolder",
|
||||
})
|
||||
require.Error(t, err, "FAILED")
|
||||
|
||||
// CreateFolder should also call the folder store's create method.
|
||||
|
@ -36,6 +36,15 @@ func (ss *sqlStore) Create(ctx context.Context, cmd folder.CreateFolderCommand)
|
||||
}
|
||||
|
||||
var foldr *folder.Folder
|
||||
/*
|
||||
user, err := appcontext.User(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version := 1
|
||||
updatedBy := user.UserID
|
||||
createdBy := user.UserID
|
||||
*/
|
||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var sqlOrArgs []interface{}
|
||||
if cmd.ParentUID == "" {
|
||||
@ -47,7 +56,7 @@ func (ss *sqlStore) Create(ctx context.Context, cmd folder.CreateFolderCommand)
|
||||
UID: &cmd.ParentUID,
|
||||
OrgID: cmd.OrgID,
|
||||
}); err != nil {
|
||||
return err
|
||||
return folder.ErrFolderNotFound.Errorf("parent folder does not exist")
|
||||
}
|
||||
}
|
||||
sql := "INSERT INTO folder(org_id, uid, parent_uid, title, description, created, updated) VALUES(?, ?, ?, ?, ?, ?, ?)"
|
||||
|
BIN
pkg/services/folder/folderimpl/test.db
Normal file
BIN
pkg/services/folder/folderimpl/test.db
Normal file
Binary file not shown.
@ -10,7 +10,7 @@ import (
|
||||
|
||||
type FakeService struct {
|
||||
ExpectedFolders []*models.Folder
|
||||
ExpectedFolder *models.Folder
|
||||
ExpectedFolder *folder.Folder
|
||||
ExpectedError error
|
||||
}
|
||||
|
||||
@ -20,19 +20,22 @@ func (s *FakeService) GetFolders(ctx context.Context, user *user.SignedInUser, o
|
||||
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, s.ExpectedError
|
||||
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
|
||||
return s.ExpectedFolder, s.ExpectedError
|
||||
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, s.ExpectedError
|
||||
return s.ExpectedFolder.ToLegacyModel(), s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) CreateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
|
||||
func (s *FakeService) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
return s.ExpectedFolder, s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
||||
cmd.Result = s.ExpectedFolder
|
||||
cmd.Result = s.ExpectedFolder.ToLegacyModel()
|
||||
return s.ExpectedError
|
||||
}
|
||||
func (s *FakeService) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
|
||||
|
@ -3,6 +3,7 @@ package folder
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@ -33,7 +34,17 @@ type Folder struct {
|
||||
|
||||
// TODO: validate if this field is required/relevant to folders.
|
||||
// currently there is no such column
|
||||
// Version int
|
||||
// Url string
|
||||
// UpdatedBy int64
|
||||
// CreatedBy int64
|
||||
// HasACL bool
|
||||
}
|
||||
|
||||
type FolderDTO struct {
|
||||
Folder
|
||||
|
||||
Children []FolderDTO
|
||||
}
|
||||
|
||||
// NewFolder tales a title and returns a Folder with the Created and Updated
|
||||
@ -51,7 +62,7 @@ func NewFolder(title string, description string) *Folder {
|
||||
// to create a folder.
|
||||
type CreateFolderCommand struct {
|
||||
UID string `json:"uid"`
|
||||
OrgID int64 `json:"orgId"`
|
||||
OrgID int64 `json:"-"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
ParentUID string `json:"parent_uid"`
|
||||
@ -72,7 +83,7 @@ type UpdateFolderCommand struct {
|
||||
type MoveFolderCommand struct {
|
||||
UID string `json:"uid"`
|
||||
NewParentUID string `json:"new_parent_uid"`
|
||||
OrgID int64 `json:"orgId"`
|
||||
OrgID int64 `json:"-"`
|
||||
}
|
||||
|
||||
// DeleteFolderCommand captures the information required by the folder service
|
||||
@ -113,3 +124,34 @@ type GetTreeQuery struct {
|
||||
Limit int64
|
||||
Page int64
|
||||
}
|
||||
|
||||
// ToLegacyModel is temporary until the two folder services are merged
|
||||
func (f *Folder) ToLegacyModel() *models.Folder {
|
||||
return &models.Folder{
|
||||
Id: f.ID,
|
||||
Uid: f.UID,
|
||||
Title: f.Title,
|
||||
Url: models.GetFolderUrl(f.UID, models.SlugifyTitle(f.Title)),
|
||||
Version: 0,
|
||||
Created: f.Created,
|
||||
Updated: f.Updated,
|
||||
UpdatedBy: 0,
|
||||
CreatedBy: 0,
|
||||
HasACL: false,
|
||||
}
|
||||
}
|
||||
|
||||
func FromDashboard(dash *models.Dashboard) *Folder {
|
||||
return &Folder{
|
||||
ID: dash.Id,
|
||||
UID: dash.Uid,
|
||||
Title: dash.Title,
|
||||
//HasACL: dash.HasACL,
|
||||
//Url: dash.GetUrl(),
|
||||
//Version: dash.Version,
|
||||
Created: dash.Created,
|
||||
//CreatedBy: dash.CreatedBy,
|
||||
Updated: dash.Updated,
|
||||
//UpdatedBy: dash.UpdatedBy,
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ type Service interface {
|
||||
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)
|
||||
CreateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, title, uid string) (*models.Folder, error)
|
||||
Create(ctx context.Context, cmd *CreateFolderCommand) (*Folder, error)
|
||||
UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error
|
||||
DeleteFolder(ctx context.Context, cmd *DeleteFolderCommand) error
|
||||
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
|
||||
|
@ -524,11 +524,11 @@ func TestGetAllLibraryElements(t *testing.T) {
|
||||
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to existing folders, it should succeed and the result should be correct",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
|
||||
command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel2")
|
||||
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
folderFilter := strconv.FormatInt(newFolder.Id, 10)
|
||||
folderFilter := strconv.FormatInt(newFolder.ID, 10)
|
||||
|
||||
err := sc.reqContext.Req.ParseForm()
|
||||
require.NoError(t, err)
|
||||
@ -548,7 +548,7 @@ func TestGetAllLibraryElements(t *testing.T) {
|
||||
{
|
||||
ID: 2,
|
||||
OrgID: 1,
|
||||
FolderID: newFolder.Id,
|
||||
FolderID: newFolder.ID,
|
||||
UID: result.Result.Elements[0].UID,
|
||||
Name: "Text - Library Panel2",
|
||||
Kind: int64(models.PanelElement),
|
||||
@ -564,7 +564,7 @@ func TestGetAllLibraryElements(t *testing.T) {
|
||||
Version: 1,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: "NewFolder",
|
||||
FolderUID: newFolder.Uid,
|
||||
FolderUID: newFolder.UID,
|
||||
ConnectedDashboards: 0,
|
||||
Created: result.Result.Elements[0].Meta.Created,
|
||||
Updated: result.Result.Elements[0].Meta.Updated,
|
||||
@ -591,7 +591,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 a nonexistent folders, it should succeed and the result should be correct",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
|
||||
command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel2")
|
||||
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
|
@ -25,7 +25,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
|
||||
cmd := PatchLibraryElementCommand{
|
||||
FolderID: newFolder.Id,
|
||||
FolderID: newFolder.ID,
|
||||
Name: "Panel - New name",
|
||||
Model: []byte(`
|
||||
{
|
||||
@ -48,7 +48,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
Result: libraryElement{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
FolderID: newFolder.Id,
|
||||
FolderID: newFolder.ID,
|
||||
UID: sc.initialResult.Result.UID,
|
||||
Name: "Panel - New name",
|
||||
Kind: int64(models.PanelElement),
|
||||
@ -90,7 +90,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
|
||||
cmd := PatchLibraryElementCommand{
|
||||
FolderID: newFolder.Id,
|
||||
FolderID: newFolder.ID,
|
||||
Kind: int64(models.PanelElement),
|
||||
Version: 1,
|
||||
}
|
||||
@ -99,7 +99,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
resp := sc.service.patchHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
sc.initialResult.Result.FolderID = newFolder.Id
|
||||
sc.initialResult.Result.FolderID = newFolder.ID
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
@ -325,7 +325,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
scenarioWithPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
|
||||
command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel")
|
||||
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel")
|
||||
sc.ctx.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
|
@ -73,7 +73,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
command := getCreatePanelCommand(folder.Id, "Library Panel Name")
|
||||
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
@ -82,14 +82,14 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder with %s, it should return correct status", testCase.role, testCase.desc),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, everyonePermissions)
|
||||
command := getCreatePanelCommand(fromFolder.Id, "Library Panel Name")
|
||||
command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := PatchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)}
|
||||
cmd := PatchLibraryElementCommand{FolderID: toFolder.ID, Version: 1, Kind: int64(models.PanelElement)}
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID})
|
||||
sc.reqContext.Req.Body = mockRequestBody(cmd)
|
||||
resp = sc.service.patchHandler(sc.reqContext)
|
||||
@ -99,14 +99,14 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from a folder with %s, it should return correct status", testCase.role, testCase.desc),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, testCase.items)
|
||||
command := getCreatePanelCommand(fromFolder.Id, "Library Panel Name")
|
||||
command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := PatchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)}
|
||||
cmd := PatchLibraryElementCommand{FolderID: toFolder.ID, Version: 1, Kind: int64(models.PanelElement)}
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID})
|
||||
sc.reqContext.Req.Body = mockRequestBody(cmd)
|
||||
resp = sc.service.patchHandler(sc.reqContext)
|
||||
@ -116,7 +116,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
|
||||
cmd := getCreatePanelCommand(folder.Id, "Library Panel Name")
|
||||
cmd := getCreatePanelCommand(folder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(cmd)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
@ -151,7 +151,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
|
||||
command := getCreatePanelCommand(folder.Id, "Library Panel Name")
|
||||
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
@ -173,7 +173,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := PatchLibraryElementCommand{FolderID: folder.Id, Version: 1, Kind: int64(models.PanelElement)}
|
||||
cmd := PatchLibraryElementCommand{FolderID: folder.ID, Version: 1, Kind: int64(models.PanelElement)}
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID})
|
||||
sc.ctx.Req.Body = mockRequestBody(cmd)
|
||||
resp = sc.service.patchHandler(sc.reqContext)
|
||||
@ -216,7 +216,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", testCase.role),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
|
||||
command := getCreatePanelCommand(folder.Id, "Library Panel Name")
|
||||
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
|
||||
sc.reqContext.Req.Body = mockRequestBody(command)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
@ -245,7 +245,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
var results []libraryElement
|
||||
for i, folderCase := range folderCases {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
|
||||
cmd := getCreatePanelCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
|
||||
cmd := getCreatePanelCommand(folder.ID, fmt.Sprintf("Library Panel in Folder%v", i))
|
||||
sc.reqContext.Req.Body = mockRequestBody(cmd)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
@ -254,7 +254,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
result.Result.Meta.UpdatedBy.Name = userInDbName
|
||||
result.Result.Meta.UpdatedBy.AvatarURL = userInDbAvatar
|
||||
result.Result.Meta.FolderName = folder.Title
|
||||
result.Result.Meta.FolderUID = folder.Uid
|
||||
result.Result.Meta.FolderUID = folder.UID
|
||||
results = append(results, result.Result)
|
||||
}
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
@ -308,7 +308,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
var results []libraryElement
|
||||
for i, folderCase := range folderCases {
|
||||
folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
|
||||
cmd := getCreatePanelCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
|
||||
cmd := getCreatePanelCommand(folder.ID, fmt.Sprintf("Library Panel in Folder%v", i))
|
||||
sc.reqContext.Req.Body = mockRequestBody(cmd)
|
||||
resp := sc.service.createHandler(sc.reqContext)
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
@ -317,7 +317,7 @@ func TestLibraryElementPermissions(t *testing.T) {
|
||||
result.Result.Meta.UpdatedBy.Name = userInDbName
|
||||
result.Result.Meta.UpdatedBy.AvatarURL = userInDbAvatar
|
||||
result.Result.Meta.FolderName = folder.Title
|
||||
result.Result.Meta.FolderUID = folder.Uid
|
||||
result.Result.Meta.FolderUID = folder.UID
|
||||
results = append(results, result.Result)
|
||||
}
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@ -25,6 +26,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -294,7 +296,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
|
||||
}
|
||||
|
||||
func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.SignedInUser,
|
||||
items []folderACLItem) *models.Folder {
|
||||
items []folderACLItem) *folder.Folder {
|
||||
t.Helper()
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
@ -312,10 +314,13 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S
|
||||
)
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
|
||||
t.Logf("Creating folder with title and UID %q", title)
|
||||
folder, err := s.CreateFolder(context.Background(), &user, user.OrgID, title, title)
|
||||
ctx := appcontext.WithUser(context.Background(), &user)
|
||||
folder, err := s.Create(ctx, &folder.CreateFolderCommand{
|
||||
OrgID: user.OrgID, Title: title, UID: title,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
updateFolderACL(t, dashboardStore, folder.Id, items)
|
||||
updateFolderACL(t, dashboardStore, folder.ID, items)
|
||||
|
||||
return folder
|
||||
}
|
||||
@ -456,7 +461,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
},
|
||||
}
|
||||
|
||||
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{})
|
||||
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}).ToLegacyModel()
|
||||
|
||||
fn(t, sc)
|
||||
})
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@ -22,6 +23,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
@ -705,7 +707,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
|
||||
}
|
||||
|
||||
func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.SignedInUser,
|
||||
items []folderACLItem) *models.Folder {
|
||||
items []folderACLItem) *folder.Folder {
|
||||
t.Helper()
|
||||
|
||||
ac := acmock.New()
|
||||
@ -720,10 +722,11 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
|
||||
|
||||
t.Logf("Creating folder with title and UID %q", title)
|
||||
folder, err := s.CreateFolder(context.Background(), user, user.OrgID, title, title)
|
||||
ctx := appcontext.WithUser(context.Background(), user)
|
||||
folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: user.OrgID, Title: title, UID: title})
|
||||
require.NoError(t, err)
|
||||
|
||||
updateFolderACL(t, dashboardStore, folder.Id, items)
|
||||
updateFolderACL(t, dashboardStore, folder.ID, items)
|
||||
|
||||
return folder
|
||||
}
|
||||
@ -858,7 +861,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
sqlStore: sqlStore,
|
||||
}
|
||||
|
||||
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{})
|
||||
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}).ToLegacyModel()
|
||||
|
||||
fn(t, sc)
|
||||
})
|
||||
|
@ -14,9 +14,11 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"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"
|
||||
@ -24,6 +26,7 @@ import (
|
||||
databasestore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
@ -122,8 +125,13 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s
|
||||
OrgRole: org.RoleAdmin,
|
||||
IsGrafanaAdmin: true,
|
||||
}
|
||||
folder, err := dbstore.FolderService.CreateFolder(ctx, user, orgID, "FOLDER-"+util.GenerateShortUID(), folderUID)
|
||||
if errors.Is(err, dashboards.ErrFolderWithSameUIDExists) || errors.Is(err, dashboards.ErrFolderVersionMismatch) {
|
||||
|
||||
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)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
@ -5255,6 +5255,7 @@
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "If nested folders are enabled then it additionally expects the parent folder UID.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -5362,6 +5363,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "If nested folders are enabled then it optionally expects a new parent folder UID that moves the folder and\nincludes it into the response.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -5409,7 +5411,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.",
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.\nIf nested folders are enabled then it also deletes all the subfolders.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -11399,6 +11401,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/definitions/ScheduleDTO"
|
||||
},
|
||||
@ -11618,8 +11624,15 @@
|
||||
}
|
||||
},
|
||||
"CreateFolderCommand": {
|
||||
"description": "CreateFolderCommand captures the information required by the folder service\nto create a folder.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -11706,6 +11719,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/definitions/ScheduleDTO"
|
||||
},
|
||||
@ -12051,6 +12068,9 @@
|
||||
"hasAcl": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasPublicDashboard": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFolder": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -12334,11 +12354,14 @@
|
||||
"title": "DataResponse contains the results from a DataQuery.",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
|
||||
"description": "Error is a property to be set if the corresponding DataQuery has an error.",
|
||||
"type": "string"
|
||||
},
|
||||
"Frames": {
|
||||
"$ref": "#/definitions/Frames"
|
||||
},
|
||||
"Status": {
|
||||
"$ref": "#/definitions/Status"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -12587,7 +12610,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"DsPermissionType": {
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\nEnum: 0,1",
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\n`2` - Edit\nEnum: 0,1,2",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
@ -12606,6 +12629,9 @@
|
||||
"auth_password": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
},
|
||||
"auth_password_file": {
|
||||
"type": "string"
|
||||
},
|
||||
"auth_secret": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
},
|
||||
@ -12963,6 +12989,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"parent_uid": {
|
||||
"description": "only used if nested folders are enabled",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -13519,6 +13549,9 @@
|
||||
"smtp_auth_password": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
},
|
||||
"smtp_auth_password_file": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtp_auth_secret": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
},
|
||||
@ -13734,6 +13767,9 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"imported": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -14081,6 +14117,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -16844,12 +16884,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"Success": {
|
||||
"$ref": "#/definitions/ResponseDetails"
|
||||
@ -17273,6 +17309,9 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -18323,7 +18362,6 @@
|
||||
}
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@ -18515,7 +18553,6 @@
|
||||
}
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -18564,13 +18601,13 @@
|
||||
}
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
@ -18667,6 +18704,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"postSilencesOKBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"silenceID": {
|
||||
"description": "silence ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"postableAlert": {
|
||||
"description": "PostableAlert postable alert",
|
||||
"type": "object",
|
||||
@ -18705,7 +18751,6 @@
|
||||
}
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -18743,6 +18788,7 @@
|
||||
}
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"active",
|
||||
@ -19935,6 +19981,15 @@
|
||||
"$ref": "#/definitions/QueryDataResponse"
|
||||
}
|
||||
},
|
||||
"receiversResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/receiver"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordingRuleResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
|
@ -4608,6 +4608,7 @@
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "If nested folders are enabled then it additionally expects the parent folder UID.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -4715,6 +4716,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "If nested folders are enabled then it optionally expects a new parent folder UID that moves the folder and\nincludes it into the response.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -4762,7 +4764,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.",
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.\nIf nested folders are enabled then it also deletes all the subfolders.",
|
||||
"tags": [
|
||||
"folders"
|
||||
],
|
||||
@ -10425,6 +10427,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/definitions/ScheduleDTO"
|
||||
},
|
||||
@ -10638,8 +10644,15 @@
|
||||
}
|
||||
},
|
||||
"CreateFolderCommand": {
|
||||
"description": "CreateFolderCommand captures the information required by the folder service\nto create a folder.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -10726,6 +10739,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/definitions/ScheduleDTO"
|
||||
},
|
||||
@ -11071,6 +11088,9 @@
|
||||
"hasAcl": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasPublicDashboard": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFolder": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -11354,11 +11374,14 @@
|
||||
"title": "DataResponse contains the results from a DataQuery.",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
|
||||
"description": "Error is a property to be set if the corresponding DataQuery has an error.",
|
||||
"type": "string"
|
||||
},
|
||||
"Frames": {
|
||||
"$ref": "#/definitions/Frames"
|
||||
},
|
||||
"Status": {
|
||||
"$ref": "#/definitions/Status"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -11590,7 +11613,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"DsPermissionType": {
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\nEnum: 0,1",
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\n`2` - Edit\nEnum: 0,1,2",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
@ -11785,6 +11808,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"parent_uid": {
|
||||
"description": "only used if nested folders are enabled",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -12080,6 +12107,9 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"imported": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -12354,6 +12384,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -13809,12 +13843,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"SuccessResponseBody": {
|
||||
"type": "object",
|
||||
|
@ -1511,6 +1511,19 @@
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"receiversResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/receiver"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"recordingRuleResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@ -2790,6 +2803,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/components/schemas/ScheduleDTO"
|
||||
},
|
||||
@ -3010,7 +3027,14 @@
|
||||
"type": "object"
|
||||
},
|
||||
"CreateFolderCommand": {
|
||||
"description": "CreateFolderCommand captures the information required by the folder service\nto create a folder.",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3097,6 +3121,10 @@
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
},
|
||||
"scaleFactor": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"schedule": {
|
||||
"$ref": "#/components/schemas/ScheduleDTO"
|
||||
},
|
||||
@ -3442,6 +3470,9 @@
|
||||
"hasAcl": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasPublicDashboard": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFolder": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -3724,11 +3755,14 @@
|
||||
"description": "A map of RefIDs (unique query identifiers) to this type makes up the Responses property of a QueryDataResponse.\nThe Error property is used to allow for partial success responses from the containing QueryDataResponse.",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
|
||||
"description": "Error is a property to be set if the corresponding DataQuery has an error.",
|
||||
"type": "string"
|
||||
},
|
||||
"Frames": {
|
||||
"$ref": "#/components/schemas/Frames"
|
||||
},
|
||||
"Status": {
|
||||
"$ref": "#/components/schemas/Status"
|
||||
}
|
||||
},
|
||||
"title": "DataResponse contains the results from a DataQuery.",
|
||||
@ -3979,7 +4013,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"DsPermissionType": {
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\nEnum: 0,1",
|
||||
"description": "Datasource permission\nDescription:\n`0` - No Access\n`1` - Query\n`2` - Edit\nEnum: 0,1,2",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
@ -3996,6 +4030,9 @@
|
||||
"auth_password": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"auth_password_file": {
|
||||
"type": "string"
|
||||
},
|
||||
"auth_secret": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
@ -4354,6 +4391,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"parent_uid": {
|
||||
"description": "only used if nested folders are enabled",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -4910,6 +4951,9 @@
|
||||
"smtp_auth_password": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"smtp_auth_password_file": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtp_auth_secret": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
@ -5124,6 +5168,9 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"imported": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -5471,6 +5518,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -8234,12 +8285,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"Status": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Success": {
|
||||
"$ref": "#/components/schemas/ResponseDetails"
|
||||
@ -8662,6 +8709,9 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@ -9713,7 +9763,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@ -9905,7 +9954,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -9954,13 +10002,13 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/gettableSilence"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"properties": {
|
||||
"lastNotifyAttempt": {
|
||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||
@ -10057,6 +10105,15 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"postSilencesOKBody": {
|
||||
"properties": {
|
||||
"silenceID": {
|
||||
"description": "silence ID",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"postableAlert": {
|
||||
"description": "PostableAlert postable alert",
|
||||
"properties": {
|
||||
@ -10095,7 +10152,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -10133,6 +10189,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "active",
|
||||
@ -15963,6 +16020,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"description": "If nested folders are enabled then it additionally expects the parent folder UID.",
|
||||
"operationId": "createFolder",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@ -16041,7 +16099,7 @@
|
||||
},
|
||||
"/folders/{folder_uid}": {
|
||||
"delete": {
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.",
|
||||
"description": "Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.\nIf nested folders are enabled then it also deletes all the subfolders.",
|
||||
"operationId": "deleteFolder",
|
||||
"parameters": [
|
||||
{
|
||||
@ -16122,6 +16180,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"description": "If nested folders are enabled then it optionally expects a new parent folder UID that moves the folder and\nincludes it into the response.",
|
||||
"operationId": "updateFolder",
|
||||
"parameters": [
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user