From 715097cc76510a3d78ba83e8544ee7c956ed26e9 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Wed, 17 Oct 2018 11:24:12 -0400 Subject: MM-12234: configurable limit to user autocomplete and search matches (#9499) * unit test cleanup * allow limiting user search results * clean up test users before starting * model UserSearchOptions to simplify parameters --- model/client4.go | 28 ++++++++++++++++++++-------- model/user_search.go | 29 +++++++++++++++++++++++++++-- model/user_search_test.go | 4 ++-- 3 files changed, 49 insertions(+), 12 deletions(-) (limited to 'model') diff --git a/model/client4.go b/model/client4.go index c35dab904..c5001c048 100644 --- a/model/client4.go +++ b/model/client4.go @@ -417,6 +417,10 @@ func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "") } +func (c *Client4) doApiPostBytes(url string, data []byte) (*http.Response, *AppError) { + return c.doApiRequestBytes(http.MethodPost, c.ApiUrl+url, data, "") +} + func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) { return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "") } @@ -426,7 +430,15 @@ func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) { } func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) { - rq, _ := http.NewRequest(method, url, strings.NewReader(data)) + return c.doApiRequestReader(method, url, strings.NewReader(data), etag) +} + +func (c *Client4) doApiRequestBytes(method, url string, data []byte, etag string) (*http.Response, *AppError) { + return c.doApiRequestReader(method, url, bytes.NewReader(data), etag) +} + +func (c *Client4) doApiRequestReader(method, url string, data io.Reader, etag string) (*http.Response, *AppError) { + rq, _ := http.NewRequest(method, url, data) if len(etag) > 0 { rq.Header.Set(HEADER_ETAG_CLIENT, etag) @@ -691,8 +703,8 @@ func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) { } // AutocompleteUsersInTeam returns the users on a team based on search term. -func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?in_team=%v&name=%v", teamId, username) +func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, limit int, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?in_team=%v&name=%v&limit=%d", teamId, username, limit) if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { return nil, BuildErrorResponse(r, err) } else { @@ -702,8 +714,8 @@ func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag s } // AutocompleteUsersInChannel returns the users in a channel based on search term. -func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v", teamId, channelId, username) +func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, limit int, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v&limit=%d", teamId, channelId, username, limit) if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { return nil, BuildErrorResponse(r, err) } else { @@ -713,8 +725,8 @@ func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, us } // AutocompleteUsers returns the users in the system based on search term. -func (c *Client4) AutocompleteUsers(username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?name=%v", username) +func (c *Client4) AutocompleteUsers(username string, limit int, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?name=%v&limit=%d", username, limit) if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { return nil, BuildErrorResponse(r, err) } else { @@ -875,7 +887,7 @@ func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) { // SearchUsers returns a list of users based on some search criteria. func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute()+"/search", search.ToJson()); err != nil { + if r, err := c.doApiPostBytes(c.GetUsersRoute()+"/search", search.ToJson()); err != nil { return nil, BuildErrorResponse(r, err) } else { defer closeBody(r) diff --git a/model/user_search.go b/model/user_search.go index 94596bdcd..68749fbe5 100644 --- a/model/user_search.go +++ b/model/user_search.go @@ -8,6 +8,10 @@ import ( "io" ) +const USER_SEARCH_MAX_LIMIT = 1000 +const USER_SEARCH_DEFAULT_LIMIT = 100 + +// UserSearch captures the parameters provided by a client for initiating a user search. type UserSearch struct { Term string `json:"term"` TeamId string `json:"team_id"` @@ -16,17 +20,38 @@ type UserSearch struct { NotInChannelId string `json:"not_in_channel_id"` AllowInactive bool `json:"allow_inactive"` WithoutTeam bool `json:"without_team"` + Limit int `json:"limit"` } // ToJson convert a User to a json string -func (u *UserSearch) ToJson() string { +func (u *UserSearch) ToJson() []byte { b, _ := json.Marshal(u) - return string(b) + return b } // UserSearchFromJson will decode the input and return a User func UserSearchFromJson(data io.Reader) *UserSearch { var us *UserSearch json.NewDecoder(data).Decode(&us) + + if us.Limit == 0 { + us.Limit = USER_SEARCH_DEFAULT_LIMIT + } + return us } + +// UserSearchOptions captures internal parameters derived from the user's permissions and a +// UserSearch request. +type UserSearchOptions struct { + // IsAdmin tracks whether or not the search is being conducted by an administrator. + IsAdmin bool + // AllowEmails allows search to examine the emails of users. + AllowEmails bool + // AllowFullNames allows search to examine the full names of users, vs. just usernames and nicknames. + AllowFullNames bool + // AllowInactive configures whether or not to return inactive users in the search results. + AllowInactive bool + // Limit limits the total number of results returned. + Limit int +} diff --git a/model/user_search_test.go b/model/user_search_test.go index 0fada7781..89c8b68f7 100644 --- a/model/user_search_test.go +++ b/model/user_search_test.go @@ -4,14 +4,14 @@ package model import ( - "strings" + "bytes" "testing" ) func TestUserSearchJson(t *testing.T) { userSearch := UserSearch{Term: NewId(), TeamId: NewId()} json := userSearch.ToJson() - ruserSearch := UserSearchFromJson(strings.NewReader(json)) + ruserSearch := UserSearchFromJson(bytes.NewReader(json)) if userSearch.Term != ruserSearch.Term { t.Fatal("Terms do not match") -- cgit v1.2.3-1-g7c22