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
This commit is contained in:
Joram Wilander
2016-11-02 14:38:34 -04:00
committed by GitHub
parent b45cc443c9
commit 137ade29d0
14 changed files with 279 additions and 83 deletions

View File

@@ -2592,33 +2592,36 @@ func sanitizeProfile(c *Context, user *model.User) *model.User {
}
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(term) == 0 {
if len(props.Term) == 0 {
c.SetInvalidParam("searchUsers", "term")
return
}
teamId := props["team_id"]
inChannelId := props["in_channel"]
notInChannelId := props["not_in_channel"]
if inChannelId != "" && !HasPermissionToChannelContext(c, inChannelId, model.PERMISSION_READ_CHANNEL) {
if props.InChannelId != "" && !HasPermissionToChannelContext(c, props.InChannelId, model.PERMISSION_READ_CHANNEL) {
return
}
if notInChannelId != "" && !HasPermissionToChannelContext(c, notInChannelId, model.PERMISSION_READ_CHANNEL) {
if props.NotInChannelId != "" && !HasPermissionToChannelContext(c, props.NotInChannelId, model.PERMISSION_READ_CHANNEL) {
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
if inChannelId != "" {
uchan = Srv.Store.User().SearchInChannel(inChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
} else if notInChannelId != "" {
uchan = Srv.Store.User().SearchNotInChannel(teamId, notInChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
if props.InChannelId != "" {
uchan = Srv.Store.User().SearchInChannel(props.InChannelId, props.Term, searchOptions)
} else if props.NotInChannelId != "" {
uchan = Srv.Store.User().SearchNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions)
} 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 {
@@ -2674,8 +2677,8 @@ func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Reque
return
}
uchan := Srv.Store.User().SearchInChannel(channelId, term, store.USER_SEARCH_TYPE_ALL)
nuchan := Srv.Store.User().SearchNotInChannel(teamId, 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, map[string]bool{})
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{}

View File

@@ -2034,10 +2034,14 @@ func TestGetProfilesNotInChannel(t *testing.T) {
}
func TestSearchUsers(t *testing.T) {
th := Setup().InitBasic()
th := Setup().InitBasic().InitSystemAdmin()
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)
} else {
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)
} else {
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)
} else {
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)
} else {
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)
} else {
users := result.Data.([]*model.User)
@@ -2141,7 +2179,7 @@ func TestSearchUsers(t *testing.T) {
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)
} else {
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")
}
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")
}
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")
}
}

View File

@@ -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
// be authenticated.
func (c *Client) SearchUsers(term string, teamId string, options map[string]string) (*Result, *AppError) {
options["term"] = term
options["team_id"] = teamId
if r, err := c.DoApiPost("/users/search", MapToJson(options)); err != nil {
func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) {
if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil {
return nil, err
} else {
defer closeBody(r)

39
model/user_search.go Normal file
View File

@@ -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 Normal file
View File

@@ -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")
}
}

View File

@@ -15,12 +15,14 @@ import (
)
const (
MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
PROFILES_IN_CHANNEL_CACHE_SIZE = 5000
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname"
USER_SEARCH_TYPE_USERNAME = "Username"
MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
PROFILES_IN_CHANNEL_CACHE_SIZE = 5000
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
USER_SEARCH_OPTION_USERNAME_ONLY = "username_only"
USER_SEARCH_OPTION_ALLOW_INACTIVE = "allow_inactive"
USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname"
USER_SEARCH_TYPE_USERNAME = "Username"
)
type SqlUserStore struct {
@@ -1085,20 +1087,24 @@ func (us SqlUserStore) GetUnreadCountForChannel(userId string, channelId string)
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)
go func() {
searchQuery := ""
if teamId == "" {
// Id != '' is added because both SEARCH_CLAUSE and INACTIVE_CLAUSE start with an AND
searchQuery = `
SELECT
*
FROM
Users
WHERE
DeleteAt = 0
Id != ''
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Username ASC
LIMIT 50`
} else {
@@ -1110,14 +1116,14 @@ func (us SqlUserStore) Search(teamId string, term string, searchType string) Sto
WHERE
TeamMembers.TeamId = :TeamId
AND Users.Id = TeamMembers.UserId
AND Users.DeleteAt = 0
AND TeamMembers.DeleteAt = 0
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
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)
}()
@@ -1125,7 +1131,7 @@ func (us SqlUserStore) Search(teamId string, term string, searchType string) Sto
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)
go func() {
@@ -1133,34 +1139,38 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
if teamId == "" {
searchQuery = `
SELECT
u.*
FROM Users u
Users.*
FROM Users
LEFT JOIN ChannelMembers cm
ON cm.UserId = u.Id
ON cm.UserId = Users.Id
AND cm.ChannelId = :ChannelId
WHERE cm.UserId IS NULL
SEARCH_CLAUSE
ORDER BY u.Username ASC
WHERE
cm.UserId IS NULL
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
LIMIT 100`
} else {
searchQuery = `
SELECT
u.*
FROM Users u
Users.*
FROM Users
INNER JOIN TeamMembers tm
ON tm.UserId = u.Id
ON tm.UserId = Users.Id
AND tm.TeamId = :TeamId
AND tm.DeleteAt = 0
LEFT JOIN ChannelMembers cm
ON cm.UserId = u.Id
ON cm.UserId = Users.Id
AND cm.ChannelId = :ChannelId
WHERE cm.UserId IS NULL
SEARCH_CLAUSE
ORDER BY u.Username ASC
WHERE
cm.UserId IS NULL
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
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)
}()
@@ -1168,7 +1178,7 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
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)
go func() {
@@ -1180,12 +1190,12 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType
WHERE
ChannelMembers.ChannelId = :ChannelId
AND ChannelMembers.UserId = Users.Id
AND Users.DeleteAt = 0
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
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)
}()
@@ -1193,7 +1203,7 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType
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{}
// 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)
}
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 == "" {
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {

View File

@@ -942,11 +942,75 @@ func TestUserStoreSearch(t *testing.T) {
u2.Email = model.NewId()
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()
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: 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)
} else {
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)
} else {
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)
} else {
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)
}
@@ -1009,7 +1073,7 @@ func TestUserStoreSearch(t *testing.T) {
c1.Type = model.CHANNEL_OPEN
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)
} else {
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)
} else {
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)
} else {
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)
} else {
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()}))
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)
} else {
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)
} else {
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)
} else {
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)
} else {
profiles := r1.Data.([]*model.User)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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