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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
b45cc443c9
Коммит
137ade29d0
37
api/user.go
37
api/user.go
@@ -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
Обычный файл
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
Обычный файл
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({
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user