admin: adds paging to global user list
Currently there is a limit of 1000 users in the global user list. This change introduces paging so that an admin can see all users and not just the first 1000. Adds a new route to the api - /api/users/search that returns a list of users and a total count. It takes two parameters perpage and page that enable paging. Fixes #7469
This commit is contained in:
+2
-1
@@ -123,6 +123,7 @@ func (hs *HttpServer) registerRoutes() {
|
||||
// users (admin permission required)
|
||||
r.Group("/users", func() {
|
||||
r.Get("/", wrap(SearchUsers))
|
||||
r.Get("/search", wrap(SearchUsersWithPaging))
|
||||
r.Get("/:id", wrap(GetUserById))
|
||||
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||
// query parameters /users/lookup?loginOrEmail=admin@example.com
|
||||
@@ -195,7 +196,7 @@ func (hs *HttpServer) registerRoutes() {
|
||||
|
||||
// Data sources
|
||||
r.Group("/datasources", func() {
|
||||
r.Get("/", GetDataSources)
|
||||
r.Get("/", wrap(GetDataSources))
|
||||
r.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), AddDataSource)
|
||||
r.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
|
||||
r.Delete("/:id", DeleteDataSourceById)
|
||||
|
||||
@@ -11,12 +11,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetDataSources(c *middleware.Context) {
|
||||
func GetDataSources(c *middleware.Context) Response {
|
||||
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to query datasources", err)
|
||||
return
|
||||
return ApiError(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
result := make(dtos.DataSourceList, 0)
|
||||
@@ -46,7 +45,8 @@ func GetDataSources(c *middleware.Context) {
|
||||
}
|
||||
|
||||
sort.Sort(result)
|
||||
c.JSON(200, result)
|
||||
|
||||
return Json(200, &result)
|
||||
}
|
||||
|
||||
func GetDataSourceById(c *middleware.Context) Response {
|
||||
|
||||
@@ -59,7 +59,9 @@ func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := &scenarioContext{}
|
||||
sc := &scenarioContext{
|
||||
url: url,
|
||||
}
|
||||
viewsPath, _ := filepath.Abs("../../public/views")
|
||||
|
||||
sc.m = macaron.New()
|
||||
@@ -71,16 +73,18 @@ func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
|
||||
sc.m.Use(middleware.GetContextHandler())
|
||||
sc.m.Use(middleware.Sessioner(&session.Options{}))
|
||||
|
||||
sc.defaultHandler = func(c *middleware.Context) {
|
||||
sc.defaultHandler = wrap(func(c *middleware.Context) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.OrgRole = models.ROLE_EDITOR
|
||||
if sc.handlerFunc != nil {
|
||||
sc.handlerFunc(sc.context)
|
||||
return sc.handlerFunc(sc.context)
|
||||
}
|
||||
}
|
||||
sc.m.SetAutoHead(true)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.m.Get(url, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
@@ -96,6 +100,20 @@ func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map[string]string) *scenarioContext {
|
||||
sc.resp = httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
q := req.URL.Query()
|
||||
for k, v := range queryParams {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
So(err, ShouldBeNil)
|
||||
sc.req = req
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
type scenarioContext struct {
|
||||
m *macaron.Macaron
|
||||
context *middleware.Context
|
||||
@@ -103,6 +121,7 @@ type scenarioContext struct {
|
||||
handlerFunc handlerFunc
|
||||
defaultHandler macaron.Handler
|
||||
req *http.Request
|
||||
url string
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) exec() {
|
||||
@@ -110,4 +129,4 @@ func (sc *scenarioContext) exec() {
|
||||
}
|
||||
|
||||
type scenarioFunc func(c *scenarioContext)
|
||||
type handlerFunc func(c *middleware.Context)
|
||||
type handlerFunc func(c *middleware.Context) Response
|
||||
|
||||
+34
-2
@@ -210,14 +210,46 @@ func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand)
|
||||
|
||||
// GET /api/users
|
||||
func SearchUsers(c *middleware.Context) Response {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
query, err := searchUser(c)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to fetch users", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result.Users)
|
||||
}
|
||||
|
||||
// GET /api/paged-users
|
||||
func SearchUsersWithPaging(c *middleware.Context) Response {
|
||||
query, err := searchUser(c)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to fetch users", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
func searchUser(c *middleware.Context) (*m.SearchUsersQuery, error) {
|
||||
perPage := c.QueryInt("perpage")
|
||||
if perPage <= 0 {
|
||||
perPage = 1000
|
||||
}
|
||||
page := c.QueryInt("page")
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
query := &m.SearchUsersQuery{Query: "", Page: page, Limit: perPage}
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Result.Page = page
|
||||
query.Result.PerPage = perPage
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func SetHelpFlag(c *middleware.Context) Response {
|
||||
flag := c.ParamsInt64(":id")
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestUserApiEndpoint(t *testing.T) {
|
||||
Convey("Given a user is logged in", t, func() {
|
||||
mockResult := models.SearchUserQueryResult{
|
||||
Users: []*models.UserSearchHitDTO{
|
||||
{Name: "user1"},
|
||||
{Name: "user2"},
|
||||
},
|
||||
TotalCount: 2,
|
||||
}
|
||||
|
||||
loggedInUserScenario("When calling GET on", "/api/users", func(sc *scenarioContext) {
|
||||
var sentLimit int
|
||||
var sendPage int
|
||||
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
|
||||
query.Result = mockResult
|
||||
|
||||
sentLimit = query.Limit
|
||||
sendPage = query.Page
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = SearchUsers
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sentLimit, ShouldEqual, 1000)
|
||||
So(sendPage, ShouldEqual, 1)
|
||||
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
So(err, ShouldBeNil)
|
||||
So(len(respJSON.MustArray()), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
loggedInUserScenario("When calling GET with page and limit querystring parameters on", "/api/users", func(sc *scenarioContext) {
|
||||
var sentLimit int
|
||||
var sendPage int
|
||||
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
|
||||
query.Result = mockResult
|
||||
|
||||
sentLimit = query.Limit
|
||||
sendPage = query.Page
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = SearchUsers
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
|
||||
|
||||
So(sentLimit, ShouldEqual, 10)
|
||||
So(sendPage, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
loggedInUserScenario("When calling GET on", "/api/users/search", func(sc *scenarioContext) {
|
||||
var sentLimit int
|
||||
var sendPage int
|
||||
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
|
||||
query.Result = mockResult
|
||||
|
||||
sentLimit = query.Limit
|
||||
sendPage = query.Page
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = SearchUsersWithPaging
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sentLimit, ShouldEqual, 1000)
|
||||
So(sendPage, ShouldEqual, 1)
|
||||
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(respJSON.Get("totalCount").MustInt(), ShouldEqual, 2)
|
||||
So(len(respJSON.Get("users").MustArray()), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
loggedInUserScenario("When calling GET with page and perpage querystring parameters on", "/api/users/search", func(sc *scenarioContext) {
|
||||
var sentLimit int
|
||||
var sendPage int
|
||||
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
|
||||
query.Result = mockResult
|
||||
|
||||
sentLimit = query.Limit
|
||||
sendPage = query.Page
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = SearchUsersWithPaging
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
|
||||
|
||||
So(sentLimit, ShouldEqual, 10)
|
||||
So(sendPage, ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user