summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Kiers <stephen@stephenkiers.com>2018-03-09 05:48:30 -0700
committerJoram Wilander <jwawilander@gmail.com>2018-03-09 07:48:30 -0500
commit302dae5bb982aad14324a4df61a018557f3dd24e (patch)
tree8e1b8b52b3c717dd90ade42ad6dd1e3a265d9903
parent31532f7feb4055f42538e23d48d4f0c941609db1 (diff)
downloadchat-302dae5bb982aad14324a4df61a018557f3dd24e.tar.gz
chat-302dae5bb982aad14324a4df61a018557f3dd24e.tar.bz2
chat-302dae5bb982aad14324a4df61a018557f3dd24e.zip
MM-9274- Sort Users in Channel by status (#8181)
* sort by lastActivity * added status ordering to Users * sort offline before dnd * remove data not needed * added seperate call for when order=‘status’ is on GetUser request * remove PrintLn * styling fix * remove mistake * mistake 2 * better comment * explicit if statemnt * writing tests * removed manually added mocks * generated mock * ICU-668 Added unit tests * style fix * sort by lastActivity * added status ordering to Users * sort offline before dnd * remove data not needed * added seperate call for when order=‘status’ is on GetUser request * remove PrintLn * styling fix * remove mistake * mistake 2 * better comment * explicit if statemnt * writing tests * removed manually added mocks * generated mock * ICU-668 Added unit tests * style fix * reverse dnd and offline * Fixed app.SaveStatusAndBroadcast * Fixed incorrect merge * Fixing incorrect merge again
-rw-r--r--api4/apitestlib.go16
-rw-r--r--api4/user.go14
-rw-r--r--api4/user_test.go143
-rw-r--r--app/apptestlib.go16
-rw-r--r--app/server_test.go2
-rw-r--r--app/status.go32
-rw-r--r--app/status_test.go40
-rw-r--r--app/user.go17
-rw-r--r--app/user_test.go129
-rw-r--r--model/client4.go15
-rw-r--r--store/sqlstore/user_store.go49
-rw-r--r--store/store.go1
-rw-r--r--store/storetest/mocks/UserStore.go16
-rw-r--r--store/storetest/user_store.go77
14 files changed, 538 insertions, 29 deletions
diff --git a/api4/apitestlib.go b/api4/apitestlib.go
index 2bb80ddfb..6edd37812 100644
--- a/api4/apitestlib.go
+++ b/api4/apitestlib.go
@@ -467,6 +467,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.EnableDebugLogForTest()
}
+func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
+ utils.DisableDebugLogForTest()
+
+ member, err := me.App.AddUserToChannel(user, channel)
+ if err != nil {
+ l4g.Error(err.Error())
+ l4g.Close()
+ time.Sleep(time.Second)
+ panic(err)
+ }
+
+ utils.EnableDebugLogForTest()
+
+ return member
+}
+
func (me *TestHelper) GenerateTestEmail() string {
if me.App.Config().EmailSettings.SMTPServer != "dockerhost" && os.Getenv("CI_INBUCKET_PORT") == "" {
return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com")
diff --git a/api4/user.go b/api4/user.go
index f82a6e3d5..8f8f08c75 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -290,16 +290,21 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if sort != "" && sort != "last_activity_at" && sort != "create_at" {
+ if sort != "" && sort != "last_activity_at" && sort != "create_at" && sort != "status" {
c.SetInvalidUrlParam("sort")
return
}
// Currently only supports sorting on a team
+ // or sort="status" on inChannelId
if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "") {
c.SetInvalidUrlParam("sort")
return
}
+ if sort == "status" && inChannelId == "" {
+ c.SetInvalidUrlParam("sort")
+ return
+ }
var profiles []*model.User
var err *model.AppError
@@ -355,8 +360,11 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
-
- profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ if sort == "status" {
+ profiles, err = c.App.GetUsersInChannelPageByStatus(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ } else {
+ profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
+ }
} else {
// No permission check required
diff --git a/api4/user_test.go b/api4/user_test.go
index 4613a8ea9..f04cd6ab2 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -2650,3 +2650,146 @@ func TestUserAccessTokenDisableConfig(t *testing.T) {
_, resp = Client.GetMe("")
CheckNoError(t, resp)
}
+
+func TestGetUsersByStatus(t *testing.T) {
+ th := Setup()
+ defer th.TearDown()
+
+ team, err := th.App.CreateTeam(&model.Team{
+ DisplayName: "dn_" + model.NewId(),
+ Name: GenerateTestTeamName(),
+ Email: th.GenerateTestEmail(),
+ Type: model.TEAM_OPEN,
+ })
+ if err != nil {
+ t.Fatalf("failed to create team: %v", err)
+ }
+
+ channel, err := th.App.CreateChannel(&model.Channel{
+ DisplayName: "dn_" + model.NewId(),
+ Name: "name_" + model.NewId(),
+ Type: model.CHANNEL_OPEN,
+ TeamId: team.Id,
+ CreatorId: model.NewId(),
+ }, false)
+ if err != nil {
+ t.Fatalf("failed to create channel: %v", err)
+ }
+
+ createUserWithStatus := func(username string, status string) *model.User {
+ id := model.NewId()
+
+ user, err := th.App.CreateUser(&model.User{
+ Email: "success+" + id + "@simulator.amazonses.com",
+ Username: "un_" + username + "_" + id,
+ Nickname: "nn_" + id,
+ Password: "Password1",
+ })
+ if err != nil {
+ t.Fatalf("failed to create user: %v", err)
+ }
+
+ th.LinkUserToTeam(user, team)
+ th.AddUserToChannel(user, channel)
+
+ th.App.SaveAndBroadcastStatus(&model.Status{
+ UserId: user.Id,
+ Status: status,
+ Manual: true,
+ })
+
+ return user
+ }
+
+ // Creating these out of order in case that affects results
+ offlineUser1 := createUserWithStatus("offline1", model.STATUS_OFFLINE)
+ offlineUser2 := createUserWithStatus("offline2", model.STATUS_OFFLINE)
+ awayUser1 := createUserWithStatus("away1", model.STATUS_AWAY)
+ awayUser2 := createUserWithStatus("away2", model.STATUS_AWAY)
+ onlineUser1 := createUserWithStatus("online1", model.STATUS_ONLINE)
+ onlineUser2 := createUserWithStatus("online2", model.STATUS_ONLINE)
+ dndUser1 := createUserWithStatus("dnd1", model.STATUS_DND)
+ dndUser2 := createUserWithStatus("dnd2", model.STATUS_DND)
+
+ client := th.CreateClient()
+ if _, resp := client.Login(onlineUser2.Username, "Password1"); resp.Error != nil {
+ t.Fatal(resp.Error)
+ }
+
+ t.Run("sorting by status then alphabetical", func(t *testing.T) {
+ usersByStatus, resp := client.GetUsersInChannelByStatus(channel.Id, 0, 8, "")
+ if resp.Error != nil {
+ t.Fatal(resp.Error)
+ }
+
+ expectedUsersByStatus := []*model.User{
+ onlineUser1,
+ onlineUser2,
+ awayUser1,
+ awayUser2,
+ dndUser1,
+ dndUser2,
+ offlineUser1,
+ offlineUser2,
+ }
+
+ if len(usersByStatus) != len(expectedUsersByStatus) {
+ t.Fatalf("received only %v users, expected %v", len(usersByStatus), len(expectedUsersByStatus))
+ }
+
+ for i := range usersByStatus {
+ if usersByStatus[i].Id != expectedUsersByStatus[i].Id {
+ t.Fatalf("received user %v at index %v, expected %v", usersByStatus[i].Username, i, expectedUsersByStatus[i].Username)
+ }
+ }
+ })
+
+ t.Run("paging", func(t *testing.T) {
+ usersByStatus, resp := client.GetUsersInChannelByStatus(channel.Id, 0, 3, "")
+ if resp.Error != nil {
+ t.Fatal(resp.Error)
+ }
+
+ if len(usersByStatus) != 3 {
+ t.Fatal("received too many users")
+ }
+
+ if usersByStatus[0].Id != onlineUser1.Id && usersByStatus[1].Id != onlineUser2.Id {
+ t.Fatal("expected to receive online users first")
+ }
+
+ if usersByStatus[2].Id != awayUser1.Id {
+ t.Fatal("expected to receive away users second")
+ }
+
+ usersByStatus, resp = client.GetUsersInChannelByStatus(channel.Id, 1, 3, "")
+ if resp.Error != nil {
+ t.Fatal(resp.Error)
+ }
+
+ if usersByStatus[0].Id != awayUser2.Id {
+ t.Fatal("expected to receive away users second")
+ }
+
+ if usersByStatus[1].Id != dndUser1.Id && usersByStatus[2].Id != dndUser2.Id {
+ t.Fatal("expected to receive dnd users third")
+ }
+
+ usersByStatus, resp = client.GetUsersInChannelByStatus(channel.Id, 1, 4, "")
+ if resp.Error != nil {
+ t.Fatal(resp.Error)
+ }
+
+ if len(usersByStatus) != 4 {
+ t.Fatal("received too many users")
+ }
+
+ if usersByStatus[0].Id != dndUser1.Id && usersByStatus[1].Id != dndUser2.Id {
+ t.Fatal("expected to receive dnd users third")
+ }
+
+ if usersByStatus[2].Id != offlineUser1.Id && usersByStatus[3].Id != offlineUser2.Id {
+ t.Fatal("expected to receive offline users last")
+ }
+ })
+}
diff --git a/app/apptestlib.go b/app/apptestlib.go
index 9e5bfc637..01f5b0102 100644
--- a/app/apptestlib.go
+++ b/app/apptestlib.go
@@ -245,6 +245,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.EnableDebugLogForTest()
}
+func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
+ utils.DisableDebugLogForTest()
+
+ member, err := me.App.AddUserToChannel(user, channel)
+ if err != nil {
+ l4g.Error(err.Error())
+ l4g.Close()
+ time.Sleep(time.Second)
+ panic(err)
+ }
+
+ utils.EnableDebugLogForTest()
+
+ return member
+}
+
func (me *TestHelper) TearDown() {
me.App.Shutdown()
os.Remove(me.tempConfigPath)
diff --git a/app/server_test.go b/app/server_test.go
index de358b976..94771a44e 100644
--- a/app/server_test.go
+++ b/app/server_test.go
@@ -26,7 +26,7 @@ func TestStartServerRateLimiterCriticalError(t *testing.T) {
// Attempt to use Rate Limiter with an invalid config
a.UpdateConfig(func(cfg *model.Config) {
- *cfg.RateLimitSettings.Enable = true
+ *cfg.RateLimitSettings.Enable = true
*cfg.RateLimitSettings.MaxBurst = -100
})
diff --git a/app/status.go b/app/status.go
index d677f9a23..c8bff0d1a 100644
--- a/app/status.go
+++ b/app/status.go
@@ -236,16 +236,7 @@ func (a *App) SetStatusOffline(userId string, manual bool) {
status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
- a.AddStatusCache(status)
-
- if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
- l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
- }
-
- event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
- event.Add("status", model.STATUS_OFFLINE)
- event.Add("user_id", status.UserId)
- a.Publish(event)
+ a.SaveAndBroadcastStatus(status)
}
func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
@@ -277,16 +268,7 @@ func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
status.Manual = manual
status.ActiveChannel = ""
- a.AddStatusCache(status)
-
- if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
- l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
- }
-
- event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
- event.Add("status", model.STATUS_AWAY)
- event.Add("user_id", status.UserId)
- a.Publish(event)
+ a.SaveAndBroadcastStatus(status)
}
func (a *App) SetStatusDoNotDisturb(userId string) {
@@ -303,16 +285,22 @@ func (a *App) SetStatusDoNotDisturb(userId string) {
status.Status = model.STATUS_DND
status.Manual = true
+ a.SaveAndBroadcastStatus(status)
+}
+
+func (a *App) SaveAndBroadcastStatus(status *model.Status) *model.AppError {
a.AddStatusCache(status)
if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
- l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
+ l4g.Error(utils.T("api.status.save_status.error"), status.UserId, result.Err)
}
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
- event.Add("status", model.STATUS_DND)
+ event.Add("status", status.Status)
event.Add("user_id", status.UserId)
a.Publish(event)
+
+ return nil
}
func GetStatusFromCache(userId string) *model.Status {
diff --git a/app/status_test.go b/app/status_test.go
new file mode 100644
index 000000000..bf5736a48
--- /dev/null
+++ b/app/status_test.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "testing"
+
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func TestSaveStatus(t *testing.T) {
+ th := Setup().InitBasic()
+ defer th.TearDown()
+
+ user := th.BasicUser
+
+ for _, statusString := range []string{
+ model.STATUS_ONLINE,
+ model.STATUS_AWAY,
+ model.STATUS_DND,
+ model.STATUS_OFFLINE,
+ } {
+ t.Run(statusString, func(t *testing.T) {
+ status := &model.Status{
+ UserId: user.Id,
+ Status: statusString,
+ }
+
+ th.App.SaveAndBroadcastStatus(status)
+
+ after, err := th.App.GetStatus(user.Id)
+ if err != nil {
+ t.Fatalf("failed to get status after save: %v", err)
+ } else if after.Status != statusString {
+ t.Fatalf("failed to save status, got %v, expected %v", after.Status, statusString)
+ }
+ })
+ }
+}
diff --git a/app/user.go b/app/user.go
index c303cbc68..dbce296d2 100644
--- a/app/user.go
+++ b/app/user.go
@@ -505,6 +505,14 @@ func (a *App) GetUsersInChannel(channelId string, offset int, limit int) ([]*mod
}
}
+func (a *App) GetUsersInChannelByStatus(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().GetProfilesInChannelByStatus(channelId, offset, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.User), nil
+ }
+}
+
func (a *App) GetUsersInChannelMap(channelId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) {
users, err := a.GetUsersInChannel(channelId, offset, limit)
if err != nil {
@@ -530,6 +538,15 @@ func (a *App) GetUsersInChannelPage(channelId string, page int, perPage int, asA
return a.sanitizeProfiles(users, asAdmin), nil
}
+func (a *App) GetUsersInChannelPageByStatus(channelId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
+ users, err := a.GetUsersInChannelByStatus(channelId, page*perPage, perPage)
+ if err != nil {
+ return nil, err
+ }
+
+ return a.sanitizeProfiles(users, asAdmin), nil
+}
+
func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit); result.Err != nil {
return nil, result.Err
diff --git a/app/user_test.go b/app/user_test.go
index 38ff286b3..94052da61 100644
--- a/app/user_test.go
+++ b/app/user_test.go
@@ -299,3 +299,132 @@ func createGitlabUser(t *testing.T, a *App, email string, username string) (*mod
return user, gitlabUserObj
}
+
+func TestGetUsersByStatus(t *testing.T) {
+ th := Setup()
+ defer th.TearDown()
+
+ team := th.CreateTeam()
+ channel, err := th.App.CreateChannel(&model.Channel{
+ DisplayName: "dn_" + model.NewId(),
+ Name: "name_" + model.NewId(),
+ Type: model.CHANNEL_OPEN,
+ TeamId: team.Id,
+ CreatorId: model.NewId(),
+ }, false)
+ if err != nil {
+ t.Fatalf("failed to create channel: %v", err)
+ }
+
+ createUserWithStatus := func(username string, status string) *model.User {
+ id := model.NewId()
+
+ user, err := th.App.CreateUser(&model.User{
+ Email: "success+" + id + "@simulator.amazonses.com",
+ Username: "un_" + username + "_" + id,
+ Nickname: "nn_" + id,
+ Password: "Password1",
+ })
+ if err != nil {
+ t.Fatalf("failed to create user: %v", err)
+ }
+
+ th.LinkUserToTeam(user, team)
+ th.AddUserToChannel(user, channel)
+
+ th.App.SaveAndBroadcastStatus(&model.Status{
+ UserId: user.Id,
+ Status: status,
+ Manual: true,
+ })
+
+ return user
+ }
+
+ // Creating these out of order in case that affects results
+ awayUser1 := createUserWithStatus("away1", model.STATUS_AWAY)
+ awayUser2 := createUserWithStatus("away2", model.STATUS_AWAY)
+ dndUser1 := createUserWithStatus("dnd1", model.STATUS_DND)
+ dndUser2 := createUserWithStatus("dnd2", model.STATUS_DND)
+ offlineUser1 := createUserWithStatus("offline1", model.STATUS_OFFLINE)
+ offlineUser2 := createUserWithStatus("offline2", model.STATUS_OFFLINE)
+ onlineUser1 := createUserWithStatus("online1", model.STATUS_ONLINE)
+ onlineUser2 := createUserWithStatus("online2", model.STATUS_ONLINE)
+
+ t.Run("sorting by status then alphabetical", func(t *testing.T) {
+ usersByStatus, err := th.App.GetUsersInChannelPageByStatus(channel.Id, 0, 8, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expectedUsersByStatus := []*model.User{
+ onlineUser1,
+ onlineUser2,
+ awayUser1,
+ awayUser2,
+ dndUser1,
+ dndUser2,
+ offlineUser1,
+ offlineUser2,
+ }
+
+ if len(usersByStatus) != len(expectedUsersByStatus) {
+ t.Fatalf("received only %v users, expected %v", len(usersByStatus), len(expectedUsersByStatus))
+ }
+
+ for i := range usersByStatus {
+ if usersByStatus[i].Id != expectedUsersByStatus[i].Id {
+ t.Fatalf("received user %v at index %v, expected %v", usersByStatus[i].Username, i, expectedUsersByStatus[i].Username)
+ }
+ }
+ })
+
+ t.Run("paging", func(t *testing.T) {
+ usersByStatus, err := th.App.GetUsersInChannelPageByStatus(channel.Id, 0, 3, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(usersByStatus) != 3 {
+ t.Fatal("received too many users")
+ }
+
+ if usersByStatus[0].Id != onlineUser1.Id && usersByStatus[1].Id != onlineUser2.Id {
+ t.Fatal("expected to receive online users first")
+ }
+
+ if usersByStatus[2].Id != awayUser1.Id {
+ t.Fatal("expected to receive away users second")
+ }
+
+ usersByStatus, err = th.App.GetUsersInChannelPageByStatus(channel.Id, 1, 3, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if usersByStatus[0].Id != awayUser2.Id {
+ t.Fatal("expected to receive away users second")
+ }
+
+ if usersByStatus[1].Id != dndUser1.Id && usersByStatus[2].Id != dndUser2.Id {
+ t.Fatal("expected to receive dnd users third")
+ }
+
+ usersByStatus, err = th.App.GetUsersInChannelPageByStatus(channel.Id, 1, 4, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(usersByStatus) != 4 {
+ t.Fatal("received too many users")
+ }
+
+ if usersByStatus[0].Id != dndUser1.Id && usersByStatus[1].Id != dndUser2.Id {
+ t.Fatal("expected to receive dnd users third")
+ }
+
+ if usersByStatus[2].Id != offlineUser1.Id && usersByStatus[3].Id != offlineUser2.Id {
+ t.Fatal("expected to receive offline users last")
+ }
+ })
+}
diff --git a/model/client4.go b/model/client4.go
index 1d71d7b3e..9e552d046 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -695,7 +695,7 @@ func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag s
}
}
-// GetUsersInChannel returns a page of users on a team. Page counting starts at 0.
+// GetUsersInChannel returns a page of users in a channel. Page counting starts at 0.
func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
@@ -706,7 +706,18 @@ func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, eta
}
}
-// GetUsersNotInChannel returns a page of users on a team. Page counting starts at 0.
+// GetUsersInChannelStatus returns a page of users in a channel. Page counting starts at 0. Sorted by Status
+func (c *Client4) GetUsersInChannelByStatus(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v&sort=status", channelId, 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)
+ }
+}
+
+// GetUsersNotInChannel returns a page of users not in a channel. Page counting starts at 0.
func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_team=%v&not_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go
index 04e0c994c..5e84af930 100644
--- a/store/sqlstore/user_store.go
+++ b/store/sqlstore/user_store.go
@@ -412,7 +412,18 @@ func (us SqlUserStore) GetProfilesInChannel(channelId string, offset int, limit
return store.Do(func(result *store.StoreResult) {
var users []*model.User
- query := "SELECT Users.* FROM Users, ChannelMembers WHERE ChannelMembers.ChannelId = :ChannelId AND Users.Id = ChannelMembers.UserId ORDER BY Users.Username ASC LIMIT :Limit OFFSET :Offset"
+ query := `
+ SELECT
+ Users.*
+ FROM
+ Users, ChannelMembers
+ WHERE
+ ChannelMembers.ChannelId = :ChannelId
+ AND Users.Id = ChannelMembers.UserId
+ ORDER BY
+ Users.Username ASC
+ LIMIT :Limit OFFSET :Offset
+ `
if _, err := us.GetReplica().Select(&users, query, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetProfilesInChannel", "store.sql_user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
@@ -427,6 +438,42 @@ func (us SqlUserStore) GetProfilesInChannel(channelId string, offset int, limit
})
}
+func (us SqlUserStore) GetProfilesInChannelByStatus(channelId string, offset int, limit int) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ var users []*model.User
+
+ query := `
+ SELECT
+ Users.*
+ FROM Users
+ INNER JOIN ChannelMembers ON Users.Id = ChannelMembers.UserId
+ LEFT JOIN Status ON Users.Id = Status.UserId
+ WHERE
+ ChannelMembers.ChannelId = :ChannelId
+ ORDER BY
+ CASE Status
+ WHEN 'online' THEN 1
+ WHEN 'away' THEN 2
+ WHEN 'dnd' THEN 3
+ ELSE 4
+ END,
+ Users.Username ASC
+ LIMIT :Limit OFFSET :Offset
+ `
+
+ if _, err := us.GetReplica().Select(&users, query, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlUserStore.GetProfilesInChannelByStatus", "store.sql_user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
+ } else {
+
+ for _, u := range users {
+ u.Sanitize(map[string]bool{})
+ }
+
+ result.Data = users
+ }
+ })
+}
+
func (us SqlUserStore) GetAllProfilesInChannel(channelId string, allowFromCache bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
if allowFromCache {
diff --git a/store/store.go b/store/store.go
index 0394277b7..671da02bd 100644
--- a/store/store.go
+++ b/store/store.go
@@ -216,6 +216,7 @@ type UserStore interface {
InvalidateProfilesInChannelCacheByUser(userId string)
InvalidateProfilesInChannelCache(channelId string)
GetProfilesInChannel(channelId string, offset int, limit int) StoreChannel
+ GetProfilesInChannelByStatus(channelId string, offset int, limit int) StoreChannel
GetAllProfilesInChannel(channelId string, allowFromCache bool) StoreChannel
GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel
GetProfilesWithoutTeam(offset int, limit int) StoreChannel
diff --git a/store/storetest/mocks/UserStore.go b/store/storetest/mocks/UserStore.go
index 2f921ae6e..369a29e7a 100644
--- a/store/storetest/mocks/UserStore.go
+++ b/store/storetest/mocks/UserStore.go
@@ -354,6 +354,22 @@ func (_m *UserStore) GetProfilesInChannel(channelId string, offset int, limit in
return r0
}
+// GetProfilesInChannelByStatus provides a mock function with given fields: channelId, offset, limit
+func (_m *UserStore) GetProfilesInChannelByStatus(channelId string, offset int, limit int) store.StoreChannel {
+ ret := _m.Called(channelId, offset, limit)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok {
+ r0 = rf(channelId, offset, limit)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
// GetProfilesNotInChannel provides a mock function with given fields: teamId, channelId, offset, limit
func (_m *UserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) store.StoreChannel {
ret := _m.Called(teamId, channelId, offset, limit)
diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go
index 47f04d1bb..2fd7d4190 100644
--- a/store/storetest/user_store.go
+++ b/store/storetest/user_store.go
@@ -25,6 +25,7 @@ func TestUserStore(t *testing.T, ss store.Store) {
t.Run("GetAllProfiles", func(t *testing.T) { testUserStoreGetAllProfiles(t, ss) })
t.Run("GetProfiles", func(t *testing.T) { testUserStoreGetProfiles(t, ss) })
t.Run("GetProfilesInChannel", func(t *testing.T) { testUserStoreGetProfilesInChannel(t, ss) })
+ t.Run("GetProfilesInChannelByStatus", func(t *testing.T) { testUserStoreGetProfilesInChannelByStatus(t, ss) })
t.Run("GetProfilesWithoutTeam", func(t *testing.T) { testUserStoreGetProfilesWithoutTeam(t, ss) })
t.Run("GetAllProfilesInChannel", func(t *testing.T) { testUserStoreGetAllProfilesInChannel(t, ss) })
t.Run("GetProfilesNotInChannel", func(t *testing.T) { testUserStoreGetProfilesNotInChannel(t, ss) })
@@ -464,6 +465,82 @@ func testUserStoreGetProfilesInChannel(t *testing.T, ss store.Store) {
}
}
+func testUserStoreGetProfilesInChannelByStatus(t *testing.T, ss store.Store) {
+ teamId := model.NewId()
+
+ u1 := &model.User{}
+ u1.Email = model.NewId()
+ store.Must(ss.User().Save(u1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
+
+ u2 := &model.User{}
+ u2.Email = model.NewId()
+ store.Must(ss.User().Save(u2))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
+
+ c1 := model.Channel{}
+ c1.TeamId = teamId
+ c1.DisplayName = "Profiles in channel"
+ c1.Name = "profiles-" + model.NewId()
+ c1.Type = model.CHANNEL_OPEN
+
+ c2 := model.Channel{}
+ c2.TeamId = teamId
+ c2.DisplayName = "Profiles in private"
+ c2.Name = "profiles-" + model.NewId()
+ c2.Type = model.CHANNEL_PRIVATE
+
+ store.Must(ss.Channel().Save(&c1, -1))
+ store.Must(ss.Channel().Save(&c2, -1))
+
+ m1 := model.ChannelMember{}
+ m1.ChannelId = c1.Id
+ m1.UserId = u1.Id
+ m1.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m2 := model.ChannelMember{}
+ m2.ChannelId = c1.Id
+ m2.UserId = u2.Id
+ m2.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ m3 := model.ChannelMember{}
+ m3.ChannelId = c2.Id
+ m3.UserId = u1.Id
+ m3.NotifyProps = model.GetDefaultChannelNotifyProps()
+
+ store.Must(ss.Channel().SaveMember(&m1))
+ store.Must(ss.Channel().SaveMember(&m2))
+ store.Must(ss.Channel().SaveMember(&m3))
+
+ if r1 := <-ss.User().GetProfilesInChannelByStatus(c1.Id, 0, 100); r1.Err != nil {
+ t.Fatal(r1.Err)
+ } else {
+ users := r1.Data.([]*model.User)
+ if len(users) != 2 {
+ t.Fatal("invalid returned users")
+ }
+
+ found := false
+ for _, u := range users {
+ if u.Id == u1.Id {
+ found = true
+ }
+ }
+
+ if !found {
+ t.Fatal("missing user")
+ }
+ }
+
+ if r2 := <-ss.User().GetProfilesInChannelByStatus(c2.Id, 0, 1); r2.Err != nil {
+ t.Fatal(r2.Err)
+ } else {
+ if len(r2.Data.([]*model.User)) != 1 {
+ t.Fatal("should have returned only 1 user")
+ }
+ }
+}
+
func testUserStoreGetProfilesWithoutTeam(t *testing.T, ss store.Store) {
teamId := model.NewId()