Merge branch 'master' into 10630_folder_api

This commit is contained in:
Marcus Efraimsson
2018-02-12 15:14:41 +01:00
123 changed files with 1770 additions and 787 deletions
+1 -1
View File
@@ -105,7 +105,7 @@ func transformToDTOs(alerts []*models.Alert, c *middleware.Context) ([]*dtos.Ale
for _, alert := range alertDTOs {
for _, dash := range dashboardsQuery.Result {
if alert.DashboardId == dash.Id {
alert.DashbboardUri = dash.GenerateUrl()
alert.Url = dash.GenerateUrl()
break
}
}
+7 -7
View File
@@ -150,13 +150,13 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
teamsRoute.Get("/:teamId", wrap(GetTeamById))
teamsRoute.Get("/search", wrap(SearchTeams))
teamsRoute.Post("/", quota("teams"), reqOrgAdmin, bind(m.CreateTeamCommand{}), wrap(CreateTeam))
teamsRoute.Put("/:teamId", reqOrgAdmin, bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
teamsRoute.Delete("/:teamId", reqOrgAdmin, wrap(DeleteTeamById))
teamsRoute.Get("/:teamId/members", reqOrgAdmin, wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", reqOrgAdmin, quota("teams"), bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", reqOrgAdmin, wrap(RemoveTeamMember))
})
teamsRoute.Post("/", quota("teams"), bind(m.CreateTeamCommand{}), wrap(CreateTeam))
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
teamsRoute.Delete("/:teamId", wrap(DeleteTeamById))
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", quota("teams"), bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
}, reqOrgAdmin)
// org information available to all users.
apiRoute.Group("/org", func(orgRoute RouteRegister) {
+15 -6
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -99,7 +100,7 @@ func GetDashboard(c *middleware.Context) Response {
return ApiError(500, "Dashboard folder could not be read", err)
}
meta.FolderTitle = query.Result.Title
meta.FolderSlug = query.Result.Slug
meta.FolderUrl = query.Result.GetUrl()
}
// make sure db version is in sync with json model version
@@ -218,6 +219,10 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
}
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(m.RootFolderName) {
return ApiError(400, "A folder already exists with that name", nil)
}
if dash.Id == 0 {
limitReached, err := middleware.QuotaReached(c, "dashboard")
if err != nil {
@@ -228,7 +233,7 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
}
}
dashItem := &dashboards.SaveDashboardItem{
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgId: c.OrgId,
@@ -238,8 +243,11 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
if err == m.ErrDashboardTitleEmpty {
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
if err == m.ErrDashboardTitleEmpty ||
err == m.ErrDashboardWithSameNameAsFolder ||
err == m.ErrDashboardFolderWithSameNameAsDashboard ||
err == m.ErrDashboardTypeMismatch {
return ApiError(400, err.Error(), nil)
}
if err == m.ErrDashboardContainsInvalidAlertData {
@@ -294,10 +302,11 @@ func GetHomeDashboard(c *middleware.Context) Response {
}
if prefsQuery.Result.HomeDashboardId != 0 {
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
slugQuery := m.GetDashboardRefByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
err := bus.Dispatch(&slugQuery)
if err == nil {
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
return Json(200, &dashRedirect)
} else {
log.Warn("Failed to get slug from database, %s", err.Error())
+21
View File
@@ -13,6 +13,11 @@ import (
func GetDashboardAclList(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
@@ -24,12 +29,23 @@ func GetDashboardAclList(c *middleware.Context) Response {
return ApiError(500, "Failed to get dashboard acl", err)
}
for _, perm := range acl {
if perm.Slug != "" {
perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug)
}
}
return Json(200, acl)
}
func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCommand) Response {
dashId := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
@@ -73,6 +89,11 @@ func DeleteDashboardAcl(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")
aclId := c.ParamsInt64(":aclId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
+42
View File
@@ -23,6 +23,14 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
}
dtoRes := transformDashboardAclsToDTOs(mockResult)
getDashboardQueryResult := m.NewDashboard("Dash")
var getDashboardNotFoundError error
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return getDashboardNotFoundError
})
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = dtoRes
return nil
@@ -60,6 +68,40 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
sc.handlerFunc = GetDashboardAclList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
Convey("Should not be able to access ACL", func() {
So(sc.resp.Code, ShouldEqual, 404)
})
})
Convey("Should not be able to update permissions for non-existing dashboard", func() {
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}
postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, cmd, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
CallPostAcl(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/2/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_ADMIN, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
sc.handlerFunc = DeleteDashboardAcl
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
Convey("Should not be able to delete non-existing dashboard", func() {
So(sc.resp.Code, ShouldEqual, 404)
})
})
})
Convey("When user is org editor and has admin permission in the ACL", func() {
+12 -2
View File
@@ -17,15 +17,25 @@ import (
)
type fakeDashboardRepo struct {
inserted []*dashboards.SaveDashboardItem
inserted []*dashboards.SaveDashboardDTO
provisioned []*m.DashboardProvisioning
getDashboard []*m.Dashboard
}
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardItem) (*m.Dashboard, error) {
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
repo.inserted = append(repo.inserted, json)
return json.Dashboard, nil
}
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning) (*m.Dashboard, error) {
repo.inserted = append(repo.inserted, dto)
return dto.Dashboard, nil
}
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
return repo.provisioned, nil
}
var fakeRepo *fakeDashboardRepo
// This tests two main scenarios. If a user has access to execute an action on a dashboard:
+1 -1
View File
@@ -19,7 +19,7 @@ type AlertRule struct {
EvalDate time.Time `json:"evalDate"`
EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"`
DashbboardUri string `json:"dashboardUri"`
Url string `json:"url"`
CanEdit bool `json:"canEdit"`
}
+1 -1
View File
@@ -27,7 +27,7 @@ type DashboardMeta struct {
IsFolder bool `json:"isFolder"`
FolderId int64 `json:"folderId"`
FolderTitle string `json:"folderTitle"`
FolderSlug string `json:"folderSlug"`
FolderUrl string `json:"folderUrl"`
}
type DashboardFullWithMeta struct {
+4 -4
View File
@@ -91,13 +91,13 @@ func CreateFolder(c *middleware.Context, cmd m.CreateFolderCommand) Response {
return ApiError(403, "Quota reached", nil)
}
dashItem := &dashboards.SaveDashboardItem{
saveDashboardDto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: c.OrgId,
UserId: c.UserId,
}
folder, err := dashboards.GetRepository().SaveDashboard(dashItem)
folder, err := dashboards.GetRepository().SaveDashboard(saveDashboardDto)
if err != nil {
return toFolderError(err)
@@ -127,14 +127,14 @@ func UpdateFolder(c *middleware.Context, cmd m.UpdateFolderCommand) Response {
return ApiError(400, m.ErrFolderTitleEmpty.Error(), nil)
}
dashItem := &dashboards.SaveDashboardItem{
saveDashboardDto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: c.OrgId,
UserId: c.UserId,
Overwrite: cmd.Overwrite,
}
folder, err := dashboards.GetRepository().SaveDashboard(dashItem)
folder, err := dashboards.GetRepository().SaveDashboard(saveDashboardDto)
if err != nil {
return toFolderError(err)
+2 -2
View File
@@ -40,14 +40,14 @@ func GenStateString() string {
func OAuthLogin(ctx *middleware.Context) {
if setting.OAuthService == nil {
ctx.Handle(404, "login.OAuthLogin(oauth service not enabled)", nil)
ctx.Handle(404, "OAuth not enabled", nil)
return
}
name := ctx.Params(":name")
connect, ok := social.SocialMap[name]
if !ok {
ctx.Handle(404, "login.OAuthLogin(social login not enabled)", errors.New(name))
ctx.Handle(404, fmt.Sprintf("No OAuth with name %s configured", name), nil)
return
}
+11 -7
View File
@@ -46,26 +46,30 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
// GET /api/org/users
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
return getOrgUsersHelper(c.OrgId)
return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
}
// GET /api/orgs/:orgId/users
func GetOrgUsers(c *middleware.Context) Response {
return getOrgUsersHelper(c.ParamsInt64(":orgId"))
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
}
func getOrgUsersHelper(orgId int64) Response {
query := m.GetOrgUsersQuery{OrgId: orgId}
func getOrgUsersHelper(orgId int64, query string, limit int) Response {
q := m.GetOrgUsersQuery{
OrgId: orgId,
Query: query,
Limit: limit,
}
if err := bus.Dispatch(&query); err != nil {
if err := bus.Dispatch(&q); err != nil {
return ApiError(500, "Failed to get account user", err)
}
for _, user := range query.Result {
for _, user := range q.Result {
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
}
return Json(200, query.Result)
return Json(200, q.Result)
}
// PATCH /api/org/users/:userId
+4 -3
View File
@@ -26,6 +26,7 @@ func CreateTeam(c *middleware.Context, cmd m.CreateTeamCommand) Response {
// PUT /api/teams/:teamId
func UpdateTeam(c *middleware.Context, cmd m.UpdateTeamCommand) Response {
cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":teamId")
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
@@ -39,7 +40,7 @@ func UpdateTeam(c *middleware.Context, cmd m.UpdateTeamCommand) Response {
// DELETE /api/teams/:teamId
func DeleteTeamById(c *middleware.Context) Response {
if err := bus.Dispatch(&m.DeleteTeamCommand{Id: c.ParamsInt64(":teamId")}); err != nil {
if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Failed to delete Team. ID not found", nil)
}
@@ -60,11 +61,11 @@ func SearchTeams(c *middleware.Context) Response {
}
query := m.SearchTeamsQuery{
OrgId: c.OrgId,
Query: c.Query("query"),
Name: c.Query("name"),
Page: page,
Limit: perPage,
OrgId: c.OrgId,
}
if err := bus.Dispatch(&query); err != nil {
@@ -83,7 +84,7 @@ func SearchTeams(c *middleware.Context) Response {
// GET /api/teams/:teamId
func GetTeamById(c *middleware.Context) Response {
query := m.GetTeamByIdQuery{Id: c.ParamsInt64(":teamId")}
query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTeamNotFound {
+2 -2
View File
@@ -10,7 +10,7 @@ import (
// GET /api/teams/:teamId/members
func GetTeamMembers(c *middleware.Context) Response {
query := m.GetTeamMembersQuery{TeamId: c.ParamsInt64(":teamId")}
query := m.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get Team Members", err)
@@ -42,7 +42,7 @@ func AddTeamMember(c *middleware.Context, cmd m.AddTeamMemberCommand) Response {
// DELETE /api/teams/:teamId/members/:userId
func RemoveTeamMember(c *middleware.Context) Response {
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
return ApiError(500, "Failed to remove Member from Team", err)
}
return ApiSuccess("Team Member removed")