PLT-4535/PLT-4503 Fix inactive users in searches and add option functionality to DB user search (#4413)

* Add options to user database search

* Fix inactive users showing up incorrectly in some user searches

* Read JSON for searchUsers API into anonymous struct

* Move anonymous struct to be a normal struct in model directory and upadte client to use it

* Added clarification comment about slightly odd query condition in search
Этот коммит содержится в:
Joram Wilander
2016-11-02 14:38:34 -04:00
коммит произвёл GitHub
родитель b45cc443c9
Коммит 137ade29d0
14 изменённых файлов: 279 добавлений и 83 удалений

Просмотреть файл

@@ -2592,33 +2592,36 @@ func sanitizeProfile(c *Context, user *model.User) *model.User {
} }
func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.UserSearchFromJson(r.Body)
if props == nil {
c.SetInvalidParam("searchUsers", "")
return
}
term := props["term"] if len(props.Term) == 0 {
if len(term) == 0 {
c.SetInvalidParam("searchUsers", "term") c.SetInvalidParam("searchUsers", "term")
return return
} }
teamId := props["team_id"] if props.InChannelId != "" && !HasPermissionToChannelContext(c, props.InChannelId, model.PERMISSION_READ_CHANNEL) {
inChannelId := props["in_channel"]
notInChannelId := props["not_in_channel"]
if inChannelId != "" && !HasPermissionToChannelContext(c, inChannelId, model.PERMISSION_READ_CHANNEL) {
return return
} }
if notInChannelId != "" && !HasPermissionToChannelContext(c, notInChannelId, model.PERMISSION_READ_CHANNEL) { if props.NotInChannelId != "" && !HasPermissionToChannelContext(c, props.NotInChannelId, model.PERMISSION_READ_CHANNEL) {
return return
} }
searchOptions := map[string]bool{}
searchOptions[store.USER_SEARCH_OPTION_USERNAME_ONLY] = true
searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = props.AllowInactive
var uchan store.StoreChannel var uchan store.StoreChannel
if inChannelId != "" { if props.InChannelId != "" {
uchan = Srv.Store.User().SearchInChannel(inChannelId, term, store.USER_SEARCH_TYPE_USERNAME) uchan = Srv.Store.User().SearchInChannel(props.InChannelId, props.Term, searchOptions)
} else if notInChannelId != "" { } else if props.NotInChannelId != "" {
uchan = Srv.Store.User().SearchNotInChannel(teamId, notInChannelId, term, store.USER_SEARCH_TYPE_USERNAME) uchan = Srv.Store.User().SearchNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions)
} else { } else {
uchan = Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_USERNAME) uchan = Srv.Store.User().Search(props.TeamId, props.Term, searchOptions)
} }
if result := <-uchan; result.Err != nil { if result := <-uchan; result.Err != nil {
@@ -2674,8 +2677,8 @@ func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Reque
return return
} }
uchan := Srv.Store.User().SearchInChannel(channelId, term, store.USER_SEARCH_TYPE_ALL) uchan := Srv.Store.User().SearchInChannel(channelId, term, map[string]bool{})
nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, store.USER_SEARCH_TYPE_ALL) nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, map[string]bool{})
autocomplete := &model.UserAutocompleteInChannel{} autocomplete := &model.UserAutocompleteInChannel{}
@@ -2720,7 +2723,7 @@ func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request)
} }
} }
uchan := Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_ALL) uchan := Srv.Store.User().Search(teamId, term, map[string]bool{})
autocomplete := &model.UserAutocompleteInTeam{} autocomplete := &model.UserAutocompleteInTeam{}

Просмотреть файл

@@ -2034,10 +2034,14 @@ func TestGetProfilesNotInChannel(t *testing.T) {
} }
func TestSearchUsers(t *testing.T) { func TestSearchUsers(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic().InitSystemAdmin()
Client := th.BasicClient Client := th.BasicClient
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil { inactiveUser := th.CreateUser(Client)
LinkUserToTeam(inactiveUser, th.BasicTeam)
th.SystemAdminClient.Must(th.SystemAdminClient.UpdateActive(inactiveUser.Id, false))
if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2054,7 +2058,41 @@ func TestSearchUsers(t *testing.T) {
} }
} }
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err != nil { if result, err := Client.SearchUsers(model.UserSearch{Term: inactiveUser.Username, TeamId: th.BasicTeam.Id}); err != nil {
t.Fatal(err)
} else {
users := result.Data.([]*model.User)
found := false
for _, user := range users {
if user.Id == inactiveUser.Id {
found = true
}
}
if found {
t.Fatal("should not have found inactive user")
}
}
if result, err := Client.SearchUsers(model.UserSearch{Term: inactiveUser.Username, TeamId: th.BasicTeam.Id, AllowInactive: true}); err != nil {
t.Fatal(err)
} else {
users := result.Data.([]*model.User)
found := false
for _, user := range users {
if user.Id == inactiveUser.Id {
found = true
}
}
if !found {
t.Fatal("should have found inactive user")
}
}
if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, InChannelId: th.BasicChannel.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2075,7 +2113,7 @@ func TestSearchUsers(t *testing.T) {
} }
} }
if result, err := Client.SearchUsers(th.BasicUser2.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil { if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser2.Username, NotInChannelId: th.BasicChannel.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2102,7 +2140,7 @@ func TestSearchUsers(t *testing.T) {
} }
} }
if result, err := Client.SearchUsers(th.BasicUser2.Username, th.BasicTeam.Id, map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil { if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser2.Username, TeamId: th.BasicTeam.Id, NotInChannelId: th.BasicChannel.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2129,7 +2167,7 @@ func TestSearchUsers(t *testing.T) {
} }
} }
if result, err := Client.SearchUsers(th.BasicUser.Username, "junk", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil { if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, TeamId: "junk", NotInChannelId: th.BasicChannel.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2141,7 +2179,7 @@ func TestSearchUsers(t *testing.T) {
th.LoginBasic2() th.LoginBasic2()
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil { if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username}); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
users := result.Data.([]*model.User) users := result.Data.([]*model.User)
@@ -2158,15 +2196,15 @@ func TestSearchUsers(t *testing.T) {
} }
} }
if _, err := Client.SearchUsers("", "", map[string]string{}); err == nil { if _, err := Client.SearchUsers(model.UserSearch{}); err == nil {
t.Fatal("should have errored - blank term") t.Fatal("should have errored - blank term")
} }
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err == nil { if _, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, InChannelId: th.BasicChannel.Id}); err == nil {
t.Fatal("should not have access") t.Fatal("should not have access")
} }
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err == nil { if _, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, NotInChannelId: th.BasicChannel.Id}); err == nil {
t.Fatal("should not have access") t.Fatal("should not have access")
} }
} }

Просмотреть файл

@@ -572,10 +572,8 @@ func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) {
// SearchUsers returns a list of users that have a username matching or similar to the search term. Must // SearchUsers returns a list of users that have a username matching or similar to the search term. Must
// be authenticated. // be authenticated.
func (c *Client) SearchUsers(term string, teamId string, options map[string]string) (*Result, *AppError) { func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) {
options["term"] = term if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil {
options["team_id"] = teamId
if r, err := c.DoApiPost("/users/search", MapToJson(options)); err != nil {
return nil, err return nil, err
} else { } else {
defer closeBody(r) defer closeBody(r)

39
model/user_search.go Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type UserSearch struct {
Term string `json:"term"`
TeamId string `json:"team_id"`
InChannelId string `json:"in_channel_id"`
NotInChannelId string `json:"not_in_channel_id"`
AllowInactive bool `json:"allow_inactive"`
}
// ToJson convert a User to a json string
func (u *UserSearch) ToJson() string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
// UserSearchFromJson will decode the input and return a User
func UserSearchFromJson(data io.Reader) *UserSearch {
decoder := json.NewDecoder(data)
var us UserSearch
err := decoder.Decode(&us)
if err == nil {
return &us
} else {
return nil
}
}

19
model/user_search_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestUserSearchJson(t *testing.T) {
userSearch := UserSearch{Term: NewId(), TeamId: NewId()}
json := userSearch.ToJson()
ruserSearch := UserSearchFromJson(strings.NewReader(json))
if userSearch.Term != ruserSearch.Term {
t.Fatal("Terms do not match")
}
}

Просмотреть файл

@@ -15,12 +15,14 @@ import (
) )
const ( const (
MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const" MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error" MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
PROFILES_IN_CHANNEL_CACHE_SIZE = 5000 PROFILES_IN_CHANNEL_CACHE_SIZE = 5000
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname" USER_SEARCH_OPTION_USERNAME_ONLY = "username_only"
USER_SEARCH_TYPE_USERNAME = "Username" USER_SEARCH_OPTION_ALLOW_INACTIVE = "allow_inactive"
USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname"
USER_SEARCH_TYPE_USERNAME = "Username"
) )
type SqlUserStore struct { type SqlUserStore struct {
@@ -1085,20 +1087,24 @@ func (us SqlUserStore) GetUnreadCountForChannel(userId string, channelId string)
return storeChannel return storeChannel
} }
func (us SqlUserStore) Search(teamId string, term string, searchType string) StoreChannel { func (us SqlUserStore) Search(teamId string, term string, options map[string]bool) StoreChannel {
storeChannel := make(StoreChannel, 1) storeChannel := make(StoreChannel, 1)
go func() { go func() {
searchQuery := "" searchQuery := ""
if teamId == "" { if teamId == "" {
// Id != '' is added because both SEARCH_CLAUSE and INACTIVE_CLAUSE start with an AND
searchQuery = ` searchQuery = `
SELECT SELECT
* *
FROM FROM
Users Users
WHERE WHERE
DeleteAt = 0 Id != ''
SEARCH_CLAUSE SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Username ASC ORDER BY Username ASC
LIMIT 50` LIMIT 50`
} else { } else {
@@ -1110,14 +1116,14 @@ func (us SqlUserStore) Search(teamId string, term string, searchType string) Sto
WHERE WHERE
TeamMembers.TeamId = :TeamId TeamMembers.TeamId = :TeamId
AND Users.Id = TeamMembers.UserId AND Users.Id = TeamMembers.UserId
AND Users.DeleteAt = 0
AND TeamMembers.DeleteAt = 0 AND TeamMembers.DeleteAt = 0
SEARCH_CLAUSE SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC ORDER BY Users.Username ASC
LIMIT 100` LIMIT 100`
} }
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId}) storeChannel <- us.performSearch(searchQuery, term, options, map[string]interface{}{"TeamId": teamId})
close(storeChannel) close(storeChannel)
}() }()
@@ -1125,7 +1131,7 @@ func (us SqlUserStore) Search(teamId string, term string, searchType string) Sto
return storeChannel return storeChannel
} }
func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel { func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, options map[string]bool) StoreChannel {
storeChannel := make(StoreChannel, 1) storeChannel := make(StoreChannel, 1)
go func() { go func() {
@@ -1133,34 +1139,38 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
if teamId == "" { if teamId == "" {
searchQuery = ` searchQuery = `
SELECT SELECT
u.* Users.*
FROM Users u FROM Users
LEFT JOIN ChannelMembers cm LEFT JOIN ChannelMembers cm
ON cm.UserId = u.Id ON cm.UserId = Users.Id
AND cm.ChannelId = :ChannelId AND cm.ChannelId = :ChannelId
WHERE cm.UserId IS NULL WHERE
SEARCH_CLAUSE cm.UserId IS NULL
ORDER BY u.Username ASC SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
LIMIT 100` LIMIT 100`
} else { } else {
searchQuery = ` searchQuery = `
SELECT SELECT
u.* Users.*
FROM Users u FROM Users
INNER JOIN TeamMembers tm INNER JOIN TeamMembers tm
ON tm.UserId = u.Id ON tm.UserId = Users.Id
AND tm.TeamId = :TeamId AND tm.TeamId = :TeamId
AND tm.DeleteAt = 0 AND tm.DeleteAt = 0
LEFT JOIN ChannelMembers cm LEFT JOIN ChannelMembers cm
ON cm.UserId = u.Id ON cm.UserId = Users.Id
AND cm.ChannelId = :ChannelId AND cm.ChannelId = :ChannelId
WHERE cm.UserId IS NULL WHERE
SEARCH_CLAUSE cm.UserId IS NULL
ORDER BY u.Username ASC SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
LIMIT 100` LIMIT 100`
} }
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId}) storeChannel <- us.performSearch(searchQuery, term, options, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId})
close(storeChannel) close(storeChannel)
}() }()
@@ -1168,7 +1178,7 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
return storeChannel return storeChannel
} }
func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType string) StoreChannel { func (us SqlUserStore) SearchInChannel(channelId string, term string, options map[string]bool) StoreChannel {
storeChannel := make(StoreChannel, 1) storeChannel := make(StoreChannel, 1)
go func() { go func() {
@@ -1180,12 +1190,12 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType
WHERE WHERE
ChannelMembers.ChannelId = :ChannelId ChannelMembers.ChannelId = :ChannelId
AND ChannelMembers.UserId = Users.Id AND ChannelMembers.UserId = Users.Id
AND Users.DeleteAt = 0
SEARCH_CLAUSE SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC ORDER BY Users.Username ASC
LIMIT 100` LIMIT 100`
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"ChannelId": channelId}) storeChannel <- us.performSearch(searchQuery, term, options, map[string]interface{}{"ChannelId": channelId})
close(storeChannel) close(storeChannel)
}() }()
@@ -1193,7 +1203,7 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType
return storeChannel return storeChannel
} }
func (us SqlUserStore) performSearch(searchQuery string, term string, searchType string, parameters map[string]interface{}) StoreResult { func (us SqlUserStore) performSearch(searchQuery string, term string, options map[string]bool, parameters map[string]interface{}) StoreResult {
result := StoreResult{} result := StoreResult{}
// these chars have special meaning and can be treated as spaces // these chars have special meaning and can be treated as spaces
@@ -1201,6 +1211,17 @@ func (us SqlUserStore) performSearch(searchQuery string, term string, searchType
term = strings.Replace(term, c, " ", -1) term = strings.Replace(term, c, " ", -1)
} }
searchType := USER_SEARCH_TYPE_ALL
if ok := options[USER_SEARCH_OPTION_USERNAME_ONLY]; ok {
searchType = USER_SEARCH_TYPE_USERNAME
}
if ok := options[USER_SEARCH_OPTION_ALLOW_INACTIVE]; ok {
searchQuery = strings.Replace(searchQuery, "INACTIVE_CLAUSE", "", 1)
} else {
searchQuery = strings.Replace(searchQuery, "INACTIVE_CLAUSE", "AND Users.DeleteAt = 0", 1)
}
if term == "" { if term == "" {
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {

Просмотреть файл

@@ -942,11 +942,75 @@ func TestUserStoreSearch(t *testing.T) {
u2.Email = model.NewId() u2.Email = model.NewId()
Must(store.User().Save(u2)) Must(store.User().Save(u2))
u3 := &model.User{}
u3.Username = "jimbo" + model.NewId()
u3.Email = model.NewId()
u3.DeleteAt = 1
Must(store.User().Save(u3))
tid := model.NewId() tid := model.NewId()
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id})) Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u2.Id})) Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u2.Id}))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}))
if r1 := <-store.User().Search(tid, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { searchOptions := map[string]bool{}
searchOptions[USER_SEARCH_OPTION_USERNAME_ONLY] = true
if r1 := <-store.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err)
} else {
profiles := r1.Data.([]*model.User)
found1 := false
found2 := false
for _, profile := range profiles {
if profile.Id == u1.Id {
found1 = true
}
if profile.Id == u3.Id {
found2 = true
}
}
if !found1 {
t.Fatal("should have found user")
}
if found2 {
t.Fatal("should not have found inactive user")
}
}
searchOptions[USER_SEARCH_OPTION_ALLOW_INACTIVE] = true
if r1 := <-store.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err)
} else {
profiles := r1.Data.([]*model.User)
found1 := false
found2 := false
for _, profile := range profiles {
if profile.Id == u1.Id {
found1 = true
}
if profile.Id == u3.Id {
found2 = true
}
}
if !found1 {
t.Fatal("should have found user")
}
if !found2 {
t.Fatal("should have found inactive user")
}
}
searchOptions[USER_SEARCH_OPTION_ALLOW_INACTIVE] = false
if r1 := <-store.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -963,7 +1027,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search("", "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().Search("", "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -980,7 +1044,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search("", "jim-bobb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().Search("", "jim-bobb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -998,7 +1062,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search(tid, "", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().Search(tid, "", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} }
@@ -1009,7 +1073,7 @@ func TestUserStoreSearch(t *testing.T) {
c1.Type = model.CHANNEL_OPEN c1.Type = model.CHANNEL_OPEN
c1 = *Must(store.Channel().Save(&c1)).(*model.Channel) c1 = *Must(store.Channel().Save(&c1)).(*model.Channel)
if r1 := <-store.User().SearchNotInChannel(tid, c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().SearchNotInChannel(tid, c1.Id, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1026,7 +1090,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().SearchNotInChannel("", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().SearchNotInChannel("", c1.Id, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1043,7 +1107,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().SearchNotInChannel("junk", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().SearchNotInChannel("junk", c1.Id, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1060,7 +1124,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1079,7 +1143,7 @@ func TestUserStoreSearch(t *testing.T) {
Must(store.Channel().SaveMember(&model.ChannelMember{ChannelId: c1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})) Must(store.Channel().SaveMember(&model.ChannelMember{ChannelId: c1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}))
if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil { if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1096,7 +1160,9 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search(tid, "Tim", USER_SEARCH_TYPE_ALL); r1.Err != nil { searchOptions = map[string]bool{}
if r1 := <-store.User().Search(tid, "Tim", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1113,7 +1179,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search(tid, "Bill", USER_SEARCH_TYPE_ALL); r1.Err != nil { if r1 := <-store.User().Search(tid, "Bill", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)
@@ -1130,7 +1196,7 @@ func TestUserStoreSearch(t *testing.T) {
} }
} }
if r1 := <-store.User().Search(tid, "Rob", USER_SEARCH_TYPE_ALL); r1.Err != nil { if r1 := <-store.User().Search(tid, "Rob", searchOptions); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
profiles := r1.Data.([]*model.User) profiles := r1.Data.([]*model.User)

Просмотреть файл

@@ -164,9 +164,9 @@ type UserStore interface {
GetUnreadCount(userId string) StoreChannel GetUnreadCount(userId string) StoreChannel
GetUnreadCountForChannel(userId string, channelId string) StoreChannel GetUnreadCountForChannel(userId string, channelId string) StoreChannel
GetRecentlyActiveUsersForTeam(teamId string) StoreChannel GetRecentlyActiveUsersForTeam(teamId string) StoreChannel
Search(teamId string, term string, searchType string) StoreChannel Search(teamId string, term string, options map[string]bool) StoreChannel
SearchInChannel(channelId string, term string, searchType string) StoreChannel SearchInChannel(channelId string, term string, options map[string]bool) StoreChannel
SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel SearchNotInChannel(teamId string, channelId string, term string, options map[string]bool) StoreChannel
} }
type SessionStore interface { type SessionStore interface {

Просмотреть файл

@@ -13,7 +13,7 @@ import UserStore from 'stores/user_store.jsx';
import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx'; import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
import {getTeamStats, getUser} from 'utils/async_client.jsx'; import {getTeamStats, getUser} from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx'; import {Constants, UserSearchOptions} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import React from 'react'; import React from 'react';
@@ -145,10 +145,13 @@ export default class UserList extends React.Component {
return; return;
} }
const options = {};
options[UserSearchOptions.ALLOW_INACTIVE] = true;
searchUsers( searchUsers(
term, term,
this.props.params.team, this.props.params.team,
{}, options,
(users) => { (users) => {
this.setState({loading: true, search: true, users}); this.setState({loading: true, search: true, users});
loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete); loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);

Просмотреть файл

@@ -105,7 +105,7 @@ export default class ChannelInviteModal extends React.Component {
searchUsers( searchUsers(
term, term,
TeamStore.getCurrentId(), TeamStore.getCurrentId(),
{not_in_channel: this.props.channel.id}, {not_in_channel_id: this.props.channel.id},
(users) => { (users) => {
this.setState({search: true, users}); this.setState({search: true, users});
} }

Просмотреть файл

@@ -117,7 +117,7 @@ export default class ChannelMembersModal extends React.Component {
searchUsers( searchUsers(
term, term,
TeamStore.getCurrentId(), TeamStore.getCurrentId(),
{in_channel: this.props.channel.id}, {in_channel_id: this.props.channel.id},
(users) => { (users) => {
this.setState({search: true, users}); this.setState({search: true, users});
} }

Просмотреть файл

@@ -37,7 +37,7 @@ export default class MoreDirectChannels extends React.Component {
this.loadComplete = this.loadComplete.bind(this); this.loadComplete = this.loadComplete.bind(this);
this.state = { this.state = {
users: UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true), users: UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true),
loadingDMChannel: -1, loadingDMChannel: -1,
listType: 'team', listType: 'team',
loading: false, loading: false,
@@ -111,7 +111,7 @@ export default class MoreDirectChannels extends React.Component {
if (this.state.listType === 'any') { if (this.state.listType === 'any') {
users = UserStore.getProfileList(); users = UserStore.getProfileList();
} else { } else {
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true); users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true);
} }
this.setState({ this.setState({
@@ -125,7 +125,7 @@ export default class MoreDirectChannels extends React.Component {
if (listType === 'any') { if (listType === 'any') {
users = UserStore.getProfileList(); users = UserStore.getProfileList();
} else { } else {
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true); users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true);
} }
this.setState({ this.setState({

Просмотреть файл

@@ -312,7 +312,7 @@ class UserStoreClass extends EventEmitter {
this.saveProfiles(profiles); this.saveProfiles(profiles);
} }
getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent) { getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
const userIds = this.profiles_in_team[teamId] || []; const userIds = this.profiles_in_team[teamId] || [];
const profiles = []; const profiles = [];
const currentId = this.getCurrentId(); const currentId = this.getCurrentId();
@@ -324,6 +324,10 @@ class UserStoreClass extends EventEmitter {
continue; continue;
} }
if (skipInactive && profile.delete_at > 0) {
continue;
}
if (profile) { if (profile) {
profiles.push(profile); profiles.push(profile);
} }

Просмотреть файл

@@ -181,6 +181,10 @@ export const UserStatuses = {
ONLINE: 'online' ONLINE: 'online'
}; };
export const UserSearchOptions = {
ALLOW_INACTIVE: 'allow_inactive'
};
export const SocketEvents = { export const SocketEvents = {
POSTED: 'posted', POSTED: 'posted',
POST_EDITED: 'post_edited', POST_EDITED: 'post_edited',
@@ -214,6 +218,7 @@ export const Constants = {
ActionTypes, ActionTypes,
WebrtcActionTypes, WebrtcActionTypes,
UserStatuses, UserStatuses,
UserSearchOptions,
TutorialSteps, TutorialSteps,
PayloadSources: keyMirror({ PayloadSources: keyMirror({