mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
32540: Add org users with pagination (#33788)
* Add model for search org user and add handler for dispatch * 32540_org_users_with_pagination: Add endpoint for search org users * 32540_org_users_with_pagination: Add test for org user search handler * 32540_org_users_with_pagination: fix indentation * 32540_org_users_with_pagination: Remove newline * 32540_org_users_with_pagination: Remove empty line * 32540_org_users_with_pagination: Fix indentation * Update pkg/api/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/models/org_user.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * 32540_org_users_with_pagination: Use hs.SQLStore.SearchOrgUsers instead of bus * Add model for search org user and add handler for dispatch * 32540_org_users_with_pagination: Add endpoint for search org users * 32540_org_users_with_pagination: Add test for org user search handler * 32540_org_users_with_pagination: fix indentation * 32540_org_users_with_pagination: Remove newline * 32540_org_users_with_pagination: Remove empty line * 32540_org_users_with_pagination: Fix indentation * Update pkg/api/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/models/org_user.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * 32540_org_users_with_pagination: Use hs.SQLStore.SearchOrgUsers instead of bus * 32540_org_users_with_pagination: Add test for the sqlstore * 32540_org_users_with_pagination: Fix sqlstore test * Update pkg/api/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/api/org_users_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/sqlstore/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/sqlstore/org_users.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/sqlstore/org_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/sqlstore/org_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * 32540: Fix search org users method * 32540: Fix sqlstore test * 32540: Fix go-lint Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
parent
553d3b9dbc
commit
f2fcf721eb
@ -203,6 +203,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Put("/", reqOrgAdmin, bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", reqOrgAdmin, bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Get("/users", authorize(reqOrgAdmin, accesscontrol.ActionOrgUsersRead, accesscontrol.ScopeUsersAll), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users/search", authorize(reqOrgAdmin, accesscontrol.ActionOrgUsersRead, accesscontrol.ScopeUsersAll), routing.Wrap(hs.SearchOrgUsersWithPaging))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, accesscontrol.ActionOrgUsersAdd, accesscontrol.ScopeUsersAll), quota("user"), bind(models.AddOrgUserCommand{}), routing.Wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, accesscontrol.ActionOrgUsersRoleUpdate, usersScope), bind(models.UpdateOrgUserCommand{}), routing.Wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, accesscontrol.ActionOrgUsersRemove, usersScope), routing.Wrap(RemoveOrgUserForCurrentOrg))
|
||||
|
@ -157,6 +157,47 @@ func (hs *HTTPServer) getOrgUsersHelper(query *models.GetOrgUsersQuery, signedIn
|
||||
return filteredUsers, nil
|
||||
}
|
||||
|
||||
// SearchOrgUsersWithPaging is an HTTP handler to search for org users with paging.
|
||||
// GET /api/org/users/search
|
||||
func (hs *HTTPServer) SearchOrgUsersWithPaging(c *models.ReqContext) response.Response {
|
||||
perPage := c.QueryInt("perpage")
|
||||
if perPage <= 0 {
|
||||
perPage = 1000
|
||||
}
|
||||
page := c.QueryInt("page")
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
query := &models.SearchOrgUsersQuery{
|
||||
OrgID: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: perPage,
|
||||
Page: page,
|
||||
}
|
||||
|
||||
if err := hs.SQLStore.SearchOrgUsers(query); err != nil {
|
||||
return response.Error(500, "Failed to get users for current organization", err)
|
||||
}
|
||||
|
||||
filteredUsers := make([]*models.OrgUserDTO, 0, len(query.Result.OrgUsers))
|
||||
for _, user := range query.Result.OrgUsers {
|
||||
if dtos.IsHiddenUser(user.Login, c.SignedInUser, hs.Cfg) {
|
||||
continue
|
||||
}
|
||||
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
||||
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
}
|
||||
|
||||
query.Result.OrgUsers = filteredUsers
|
||||
query.Result.Page = page
|
||||
query.Result.PerPage = perPage
|
||||
|
||||
return response.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// PATCH /api/org/users/:userId
|
||||
func UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
@ -175,7 +216,6 @@ func updateOrgUserHelper(cmd models.UpdateOrgUserCommand) response.Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return response.Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if errors.Is(err, models.ErrLastOrgAdmin) {
|
||||
return response.Error(400, "Cannot change role so that there is no organization admin left", nil)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
@ -8,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -24,10 +26,24 @@ func setUpGetOrgUsersHandler() {
|
||||
})
|
||||
}
|
||||
|
||||
func setUpGetOrgUsersDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
|
||||
setting.AutoAssignOrg = true
|
||||
setting.AutoAssignOrgId = 1
|
||||
|
||||
_, err := sqlStore.CreateUser(context.Background(), models.CreateUserCommand{Email: "testUser@grafana.com", Login: "testUserLogin"})
|
||||
require.NoError(t, err)
|
||||
_, err = sqlStore.CreateUser(context.Background(), models.CreateUserCommand{Email: "user1@grafana.com", Login: "user1"})
|
||||
require.NoError(t, err)
|
||||
_, err = sqlStore.CreateUser(context.Background(), models.CreateUserCommand{Email: "user2@grafana.com", Login: "user2"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
settings := setting.NewCfg()
|
||||
hs := &HTTPServer{Cfg: settings}
|
||||
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
|
||||
loggedInUserScenario(t, "When calling GET on", "api/org/users", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
||||
@ -42,6 +58,42 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
assert.Len(t, resp, 3)
|
||||
})
|
||||
|
||||
loggedInUserScenario(t, "When calling GET on", "api/org/users/search", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersDB(t, sqlStore)
|
||||
|
||||
sc.handlerFunc = hs.SearchOrgUsersWithPaging
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp models.SearchOrgUsersQueryResult
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.OrgUsers, 3)
|
||||
assert.Equal(t, int64(3), resp.TotalCount)
|
||||
assert.Equal(t, 1000, resp.PerPage)
|
||||
assert.Equal(t, 1, resp.Page)
|
||||
})
|
||||
|
||||
loggedInUserScenario(t, "When calling GET with page and limit query parameters on", "api/org/users/search", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersDB(t, sqlStore)
|
||||
|
||||
sc.handlerFunc = hs.SearchOrgUsersWithPaging
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "2", "page": "2"}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp models.SearchOrgUsersQueryResult
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.OrgUsers, 1)
|
||||
assert.Equal(t, int64(3), resp.TotalCount)
|
||||
assert.Equal(t, 2, resp.PerPage)
|
||||
assert.Equal(t, 2, resp.Page)
|
||||
})
|
||||
|
||||
loggedInUserScenario(t, "When calling GET as an editor with no team / folder permissions on",
|
||||
"api/org/users/lookup", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
@ -114,6 +114,22 @@ type GetOrgUsersQuery struct {
|
||||
Result []*OrgUserDTO
|
||||
}
|
||||
|
||||
type SearchOrgUsersQuery struct {
|
||||
OrgID int64
|
||||
Query string
|
||||
Page int
|
||||
Limit int
|
||||
|
||||
Result SearchOrgUsersQueryResult
|
||||
}
|
||||
|
||||
type SearchOrgUsersQueryResult struct {
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
OrgUsers []*OrgUserDTO `json:"OrgUsers"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// Projections and DTOs
|
||||
|
||||
|
@ -95,6 +95,43 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given single org and 2 users inserted", func() {
|
||||
setting.AutoAssignOrg = true
|
||||
setting.AutoAssignOrgId = 1
|
||||
setting.AutoAssignOrgRole = "Viewer"
|
||||
|
||||
ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
|
||||
ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
|
||||
|
||||
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
|
||||
So(err, ShouldBeNil)
|
||||
_, err = sqlStore.CreateUser(context.Background(), ac2cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Can get organization users paginated with query", func() {
|
||||
query := models.SearchOrgUsersQuery{
|
||||
OrgID: ac1.OrgId,
|
||||
Page: 1,
|
||||
}
|
||||
err = sqlStore.SearchOrgUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.OrgUsers), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Can get organization users paginated and limited", func() {
|
||||
query := models.SearchOrgUsersQuery{
|
||||
OrgID: ac1.OrgId,
|
||||
Limit: 1,
|
||||
Page: 1,
|
||||
}
|
||||
err = sqlStore.SearchOrgUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.OrgUsers), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given two saved users", func() {
|
||||
setting.AutoAssignOrg = false
|
||||
|
||||
|
@ -142,6 +142,71 @@ func GetOrgUsers(query *models.GetOrgUsersQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *SQLStore) SearchOrgUsers(query *models.SearchOrgUsersQuery) error {
|
||||
query.Result = models.SearchOrgUsersQueryResult{
|
||||
OrgUsers: make([]*models.OrgUserDTO, 0),
|
||||
}
|
||||
|
||||
sess := x.Table("org_user")
|
||||
sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
|
||||
|
||||
whereConditions := make([]string, 0)
|
||||
whereParams := make([]interface{}, 0)
|
||||
|
||||
whereConditions = append(whereConditions, "org_user.org_id = ?")
|
||||
whereParams = append(whereParams, query.OrgID)
|
||||
|
||||
if query.Query != "" {
|
||||
queryWithWildcards := "%" + query.Query + "%"
|
||||
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
||||
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
||||
}
|
||||
|
||||
if len(whereConditions) > 0 {
|
||||
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
||||
}
|
||||
|
||||
if query.Limit > 0 {
|
||||
offset := query.Limit * (query.Page - 1)
|
||||
sess.Limit(query.Limit, offset)
|
||||
}
|
||||
|
||||
sess.Cols(
|
||||
"org_user.org_id",
|
||||
"org_user.user_id",
|
||||
"user.email",
|
||||
"user.name",
|
||||
"user.login",
|
||||
"org_user.role",
|
||||
"user.last_seen_at",
|
||||
)
|
||||
sess.Asc("user.email", "user.login")
|
||||
|
||||
if err := sess.Find(&query.Result.OrgUsers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get total count
|
||||
orgUser := models.OrgUser{}
|
||||
countSess := x.Table("org_user")
|
||||
|
||||
if len(whereConditions) > 0 {
|
||||
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
||||
}
|
||||
|
||||
count, err := countSess.Count(&orgUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query.Result.TotalCount = count
|
||||
|
||||
for _, user := range query.Result.OrgUsers {
|
||||
user.LastSeenAtAge = util.GetAgeString(user.LastSeenAt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveOrgUser(cmd *models.RemoveOrgUserCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
// check if user exists
|
||||
|
Loading…
Reference in New Issue
Block a user