summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-06-30 12:07:23 -0400
committerHarrison Healey <harrisonmhealey@gmail.com>2017-06-30 12:07:23 -0400
commit5507154992eedd323385c37e59b2008586b9aaa0 (patch)
treeb017d2a40207cb437b9aa84703990577539df9f8
parent6b77a054c25acb0437a58107c4592ad66c830993 (diff)
downloadchat-5507154992eedd323385c37e59b2008586b9aaa0.tar.gz
chat-5507154992eedd323385c37e59b2008586b9aaa0.tar.bz2
chat-5507154992eedd323385c37e59b2008586b9aaa0.zip
Add some basic sorting support for GET /users endpoint (#6801)
-rw-r--r--api4/user.go32
-rw-r--r--api4/user_test.go58
-rw-r--r--app/analytics.go33
-rw-r--r--i18n/en.json4
-rw-r--r--model/client4.go22
-rw-r--r--store/sql_user_store.go48
-rw-r--r--store/sql_user_store_test.go17
-rw-r--r--store/store.go3
8 files changed, 199 insertions, 18 deletions
diff --git a/api4/user.go b/api4/user.go
index 24c1c917b..04faf13c4 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -277,9 +277,21 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
inChannelId := r.URL.Query().Get("in_channel")
notInChannelId := r.URL.Query().Get("not_in_channel")
withoutTeam := r.URL.Query().Get("without_team")
+ sort := r.URL.Query().Get("sort")
if len(notInChannelId) > 0 && len(inTeamId) == 0 {
- c.SetInvalidParam("team_id")
+ c.SetInvalidUrlParam("team_id")
+ return
+ }
+
+ if sort != "" && sort != "last_activity_at" && sort != "create_at" {
+ c.SetInvalidUrlParam("sort")
+ return
+ }
+
+ // Currently only supports sorting on a team
+ if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "") {
+ c.SetInvalidUrlParam("sort")
return
}
@@ -287,7 +299,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
var err *model.AppError
etag := ""
- if withoutTeamBool, err := strconv.ParseBool(withoutTeam); err == nil && withoutTeamBool {
+ if withoutTeamBool, _ := strconv.ParseBool(withoutTeam); withoutTeamBool {
// Use a special permission for now
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_LIST_USERS_WITHOUT_TEAM) {
c.SetPermissionError(model.PERMISSION_LIST_USERS_WITHOUT_TEAM)
@@ -320,12 +332,18 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- etag = app.GetUsersInTeamEtag(inTeamId)
- if HandleEtag(etag, "Get Users in Team", w, r) {
- return
- }
+ if sort == "last_activity_at" {
+ profiles, err = app.GetRecentlyActiveUsersForTeamPage(inTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ } else if sort == "create_at" {
+ profiles, err = app.GetNewUsersForTeamPage(inTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ } else {
+ etag = app.GetUsersInTeamEtag(inTeamId)
+ if HandleEtag(etag, "Get Users in Team", w, r) {
+ return
+ }
- profiles, err = app.GetUsersInTeamPage(inTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ profiles, err = app.GetUsersInTeamPage(inTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ }
} else if len(inChannelId) > 0 {
if !app.SessionHasPermissionToChannel(c.Session, inChannelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
diff --git a/api4/user_test.go b/api4/user_test.go
index 1067ebaf6..77157e250 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -1224,6 +1224,64 @@ func TestGetUsers(t *testing.T) {
CheckUnauthorizedStatus(t, resp)
}
+func TestGetNewUsersInTeam(t *testing.T) {
+ th := Setup().InitBasic()
+ defer TearDown()
+ Client := th.Client
+ teamId := th.BasicTeam.Id
+
+ rusers, resp := Client.GetNewUsersInTeam(teamId, 0, 60, "")
+ CheckNoError(t, resp)
+
+ lastCreateAt := model.GetMillis()
+ for _, u := range rusers {
+ if u.CreateAt > lastCreateAt {
+ t.Fatal("bad sorting")
+ }
+ lastCreateAt = u.CreateAt
+ CheckUserSanitization(t, u)
+ }
+
+ rusers, resp = Client.GetNewUsersInTeam(teamId, 1, 1, "")
+ CheckNoError(t, resp)
+ if len(rusers) != 1 {
+ t.Fatal("should be 1 per page")
+ }
+
+ Client.Logout()
+ _, resp = Client.GetNewUsersInTeam(teamId, 1, 1, "")
+ CheckUnauthorizedStatus(t, resp)
+}
+
+func TestGetRecentlyActiveUsersInTeam(t *testing.T) {
+ th := Setup().InitBasic()
+ defer TearDown()
+ Client := th.Client
+ teamId := th.BasicTeam.Id
+
+ app.SetStatusOnline(th.BasicUser.Id, "", true)
+
+ rusers, resp := Client.GetRecentlyActiveUsersInTeam(teamId, 0, 60, "")
+ CheckNoError(t, resp)
+
+ for _, u := range rusers {
+ if u.LastActivityAt == 0 {
+ t.Fatal("did not return last activity at")
+ }
+ CheckUserSanitization(t, u)
+ }
+
+ rusers, resp = Client.GetRecentlyActiveUsersInTeam(teamId, 0, 1, "")
+ CheckNoError(t, resp)
+ if len(rusers) != 1 {
+ t.Fatal("should be 1 per page")
+ }
+
+ Client.Logout()
+ _, resp = Client.GetRecentlyActiveUsersInTeam(teamId, 0, 1, "")
+ CheckUnauthorizedStatus(t, resp)
+}
+
func TestGetUsersWithoutTeam(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
diff --git a/app/analytics.go b/app/analytics.go
index 78e1fe7b4..35fbddd89 100644
--- a/app/analytics.go
+++ b/app/analytics.go
@@ -231,9 +231,38 @@ func GetAnalytics(name string, teamId string) (model.AnalyticsRows, *model.AppEr
}
func GetRecentlyActiveUsersForTeam(teamId string) (map[string]*model.User, *model.AppError) {
- if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(teamId); result.Err != nil {
+ if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(teamId, 0, 100); result.Err != nil {
return nil, result.Err
} else {
- return result.Data.(map[string]*model.User), nil
+ users := result.Data.([]*model.User)
+ userMap := make(map[string]*model.User)
+
+ for _, user := range users {
+ userMap[user.Id] = user
+ }
+
+ return userMap, nil
+ }
+}
+
+func GetRecentlyActiveUsersForTeamPage(teamId string, page, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
+ var users []*model.User
+ if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(teamId, page*perPage, perPage); result.Err != nil {
+ return nil, result.Err
+ } else {
+ users = result.Data.([]*model.User)
}
+
+ return sanitizeProfiles(users, asAdmin), nil
+}
+
+func GetNewUsersForTeamPage(teamId string, page, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
+ var users []*model.User
+ if result := <-Srv.Store.User().GetNewUsersForTeam(teamId, page*perPage, perPage); result.Err != nil {
+ return nil, result.Err
+ } else {
+ users = result.Data.([]*model.User)
+ }
+
+ return sanitizeProfiles(users, asAdmin), nil
}
diff --git a/i18n/en.json b/i18n/en.json
index d6de2a8d9..308374ad4 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -5876,6 +5876,10 @@
"translation": "We encountered an error while finding the recently active users"
},
{
+ "id": "store.sql_user.get_new_users.app_error",
+ "translation": "We encountered an error while finding the new users"
+ },
+ {
"id": "store.sql_user.get_sysadmin_profiles.app_error",
"translation": "We encountered an error while finding user profiles"
},
diff --git a/model/client4.go b/model/client4.go
index 33a906429..8df1f8e32 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -611,6 +611,28 @@ func (c *Client4) GetUsersInTeam(teamId string, page int, perPage int, etag stri
}
}
+// GetNewUsersInTeam returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetNewUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?sort=create_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetRecentlyActiveUsersInTeam returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetRecentlyActiveUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?sort=last_activity_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
// GetUsersNotInTeam returns a page of users who are not in a team. Page counting starts at 0.
func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 4aa6f6cfe..ab031ea19 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -756,7 +756,7 @@ type UserWithLastActivityAt struct {
LastActivityAt int64
}
-func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string) StoreChannel {
+func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string, offset, limit int) StoreChannel {
storeChannel := make(StoreChannel)
@@ -774,21 +774,55 @@ func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string) StoreChannel
INNER JOIN Status AS s ON s.UserId = t.UserId
WHERE t.TeamId = :TeamId
ORDER BY s.LastActivityAt DESC
- LIMIT 100
- `, map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewLocAppError("SqlUserStore.GetRecentlyActiveUsers", "store.sql_user.get_recently_active_users.app_error", nil, err.Error())
+ LIMIT :Limit OFFSET :Offset
+ `, map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlUserStore.GetRecentlyActiveUsers", "store.sql_user.get_recently_active_users.app_error", nil, err.Error(), http.StatusInternalServerError)
} else {
- userMap := make(map[string]*model.User)
+ userList := []*model.User{}
for _, userWithLastActivityAt := range users {
u := userWithLastActivityAt.User
u.Sanitize(map[string]bool{})
u.LastActivityAt = userWithLastActivityAt.LastActivityAt
- userMap[u.Id] = &u
+ userList = append(userList, &u)
}
- result.Data = userMap
+ result.Data = userList
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
+func (us SqlUserStore) GetNewUsersForTeam(teamId string, offset, limit int) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var users []*model.User
+
+ if _, err := us.GetReplica().Select(&users, `
+ SELECT
+ u.*
+ FROM Users AS u
+ INNER JOIN TeamMembers AS t ON u.Id = t.UserId
+ WHERE t.TeamId = :TeamId
+ ORDER BY u.CreateAt DESC
+ LIMIT :Limit OFFSET :Offset
+ `, map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlUserStore.GetNewUsersForTeam", "store.sql_user.get_new_users.app_error", nil, err.Error(), http.StatusInternalServerError)
+ } else {
+ for _, u := range users {
+ u.Sanitize(map[string]bool{})
+ }
+
+ result.Data = users
}
storeChannel <- result
diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go
index dc4cd684a..18c4c022c 100644
--- a/store/sql_user_store_test.go
+++ b/store/sql_user_store_test.go
@@ -1291,7 +1291,22 @@ func TestUserStoreGetRecentlyActiveUsersForTeam(t *testing.T) {
tid := model.NewId()
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
- if r1 := <-store.User().GetRecentlyActiveUsersForTeam(tid); r1.Err != nil {
+ if r1 := <-store.User().GetRecentlyActiveUsersForTeam(tid, 0, 100); r1.Err != nil {
+ t.Fatal(r1.Err)
+ }
+}
+
+func TestUserStoreGetNewUsersForTeam(t *testing.T) {
+ Setup()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ Must(store.User().Save(u1))
+ Must(store.Status().SaveOrUpdate(&model.Status{UserId: u1.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}))
+ tid := model.NewId()
+ Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
+
+ if r1 := <-store.User().GetNewUsersForTeam(tid, 0, 100); r1.Err != nil {
t.Fatal(r1.Err)
}
}
diff --git a/store/store.go b/store/store.go
index 23c6acd37..9ae5f4b81 100644
--- a/store/store.go
+++ b/store/store.go
@@ -208,7 +208,8 @@ type UserStore interface {
AnalyticsActiveCount(time int64) StoreChannel
GetUnreadCount(userId string) StoreChannel
GetUnreadCountForChannel(userId string, channelId string) StoreChannel
- GetRecentlyActiveUsersForTeam(teamId string) StoreChannel
+ GetRecentlyActiveUsersForTeam(teamId string, offset, limit int) StoreChannel
+ GetNewUsersForTeam(teamId string, offset, limit int) StoreChannel
Search(teamId string, term string, options map[string]bool) StoreChannel
SearchNotInTeam(notInTeamId string, term string, options map[string]bool) StoreChannel
SearchInChannel(channelId string, term string, options map[string]bool) StoreChannel