summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2016-11-24 09:35:09 -0500
committerHarrison Healey <harrisonmhealey@gmail.com>2016-11-24 09:35:09 -0500
commit981ea33b8e10456bc279f36235c814305d01b243 (patch)
tree00fb6119d9ef16f60d4c0dbdaad1bd6dfbc347ed /api
parentc96ecae6da31aceabf29586cde872876b81d11d9 (diff)
downloadchat-981ea33b8e10456bc279f36235c814305d01b243.tar.gz
chat-981ea33b8e10456bc279f36235c814305d01b243.tar.bz2
chat-981ea33b8e10456bc279f36235c814305d01b243.zip
PLT-4403 Add server-based channel autocomplete, search and paging (#4585)
* Add more channel paging API * Add channel paging support to client * Add DB channel search functions * Add API for searching more channels * Add more channel search functionality to client * Add API for autocompleting channels * Add channel autocomplete functionality to the client * Move to be deprecated APIs to their own file * Final clean-up * Fixes related to feedback * Localization changes * Add unit as suffix to timeout constants
Diffstat (limited to 'api')
-rw-r--r--api/api.go1
-rw-r--r--api/channel.go73
-rw-r--r--api/channel_test.go207
-rw-r--r--api/command_join.go23
-rw-r--r--api/deprecated.go39
-rw-r--r--api/deprecated_test.go43
-rw-r--r--api/user.go9
7 files changed, 361 insertions, 34 deletions
diff --git a/api/api.go b/api/api.go
index eea70c9b5..3af23b9e0 100644
--- a/api/api.go
+++ b/api/api.go
@@ -103,6 +103,7 @@ func InitApi() {
InitEmoji()
InitStatus()
InitWebrtc()
+ InitDeprecated()
// 404 on any api route before web.go has a chance to serve it
Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404))
diff --git a/api/channel.go b/api/channel.go
index 9ec556fe6..0ffe9a668 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -6,6 +6,7 @@ package api
import (
"fmt"
"net/http"
+ "strconv"
"strings"
l4g "github.com/alecthomas/log4go"
@@ -19,7 +20,8 @@ func InitChannel() {
l4g.Debug(utils.T("api.channel.init.debug"))
BaseRoutes.Channels.Handle("/", ApiUserRequired(getChannels)).Methods("GET")
- BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
+ BaseRoutes.Channels.Handle("/more/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getMoreChannelsPage)).Methods("GET")
+ BaseRoutes.Channels.Handle("/more/search", ApiUserRequired(searchMoreChannels)).Methods("POST")
BaseRoutes.Channels.Handle("/counts", ApiUserRequired(getChannelCounts)).Methods("GET")
BaseRoutes.Channels.Handle("/members", ApiUserRequired(getMyChannelMembers)).Methods("GET")
BaseRoutes.Channels.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
@@ -28,6 +30,7 @@ func InitChannel() {
BaseRoutes.Channels.Handle("/update_header", ApiUserRequired(updateChannelHeader)).Methods("POST")
BaseRoutes.Channels.Handle("/update_purpose", ApiUserRequired(updateChannelPurpose)).Methods("POST")
BaseRoutes.Channels.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST")
+ BaseRoutes.Channels.Handle("/autocomplete", ApiUserRequired(autocompleteChannels)).Methods("GET")
BaseRoutes.NeedChannelName.Handle("/join", ApiUserRequired(join)).Methods("POST")
@@ -41,6 +44,7 @@ func InitChannel() {
BaseRoutes.NeedChannel.Handle("/remove", ApiUserRequired(removeMember)).Methods("POST")
BaseRoutes.NeedChannel.Handle("/update_last_viewed_at", ApiUserRequired(updateLastViewedAt)).Methods("POST")
BaseRoutes.NeedChannel.Handle("/set_last_viewed_at", ApiUserRequired(setLastViewedAt)).Methods("POST")
+
}
func createChannel(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -416,18 +420,29 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
+func getMoreChannelsPage(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+
+ offset, err := strconv.Atoi(params["offset"])
+ if err != nil {
+ c.SetInvalidParam("getProfiles", "offset")
+ return
+ }
+
+ limit, err := strconv.Atoi(params["limit"])
+ if err != nil {
+ c.SetInvalidParam("getProfiles", "limit")
+ return
+ }
// user is already in the team
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) {
return
}
- if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId, offset, limit); result.Err != nil {
c.Err = result.Err
return
- } else if HandleEtag(result.Data.(*model.ChannelList).Etag(), w, r) {
- return
} else {
data := result.Data.(*model.ChannelList)
w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag())
@@ -1182,3 +1197,51 @@ func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+
+func searchMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.ChannelSearchFromJson(r.Body)
+ if props == nil {
+ c.SetInvalidParam("searchMoreChannels", "")
+ return
+ }
+
+ if c.Session.GetTeamByTeamId(c.TeamId) == nil {
+ if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
+ return
+ }
+ }
+
+ if len(props.Term) == 0 {
+ c.SetInvalidParam("searchMoreChannels", "term")
+ return
+ }
+
+ if result := <-Srv.Store.Channel().SearchMore(c.Session.UserId, c.TeamId, props.Term); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ channels := result.Data.(*model.ChannelList)
+ w.Write([]byte(channels.ToJson()))
+ }
+}
+
+func autocompleteChannels(c *Context, w http.ResponseWriter, r *http.Request) {
+ term := r.URL.Query().Get("term")
+
+ if c.Session.GetTeamByTeamId(c.TeamId) == nil {
+ if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
+ return
+ }
+ }
+
+ var channels *model.ChannelList
+
+ if result := <-Srv.Store.Channel().SearchInTeam(c.TeamId, term); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ channels = result.Data.(*model.ChannelList)
+ }
+
+ w.Write([]byte(channels.ToJson()))
+}
diff --git a/api/channel_test.go b/api/channel_test.go
index 4b0ce9509..611fa9339 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -705,7 +705,7 @@ func TestGetChannel(t *testing.T) {
}
}
-func TestGetMoreChannel(t *testing.T) {
+func TestGetMoreChannelsPage(t *testing.T) {
th := Setup().InitBasic()
Client := th.BasicClient
team := th.BasicTeam
@@ -713,28 +713,64 @@ func TestGetMoreChannel(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+ channel3 := &model.Channel{DisplayName: "C Test API Name", Name: "c" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
+ channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
+
th.LoginBasic2()
- rget := Client.Must(Client.GetMoreChannels(""))
- channels := rget.Data.(*model.ChannelList)
+ if r, err := Client.GetMoreChannelsPage(0, 100); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := r.Data.(*model.ChannelList)
- if (*channels)[0].DisplayName != channel1.DisplayName {
- t.Fatal("full name didn't match")
+ // 1 for BasicChannel, 2 for open channels created above
+ if len(*channels) != 3 {
+ t.Fatal("wrong length")
+ }
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ if (*channels)[1].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
}
- if (*channels)[1].DisplayName != channel2.DisplayName {
- t.Fatal("full name didn't match")
+ if r, err := Client.GetMoreChannelsPage(0, 1); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := r.Data.(*model.ChannelList)
+
+ if len(*channels) != 1 {
+ t.Fatal("wrong length")
+ }
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
}
- // test etag caching
- if cache_result, err := Client.GetMoreChannels(rget.Etag); err != nil {
+ if r, err := Client.GetMoreChannelsPage(1, 1); err != nil {
t.Fatal(err)
- } else if cache_result.Data.(*model.ChannelList) != nil {
- t.Log(cache_result.Data)
- t.Fatal("cache should be empty")
+ } else {
+ channels := r.Data.(*model.ChannelList)
+
+ if len(*channels) != 1 {
+ t.Fatal("wrong length")
+ }
+
+ if (*channels)[0].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ Client.SetTeamId("junk")
+ if _, err := Client.GetMoreChannelsPage(0, 1); err == nil {
+ t.Fatal("should have failed - bad team id")
}
}
@@ -1448,3 +1484,148 @@ func TestGetChannelMember(t *testing.T) {
t.Fatal("should have failed - bad channel and user id")
}
}
+
+func TestSearchMoreChannels(t *testing.T) {
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+
+ channel1 := &model.Channel{DisplayName: "TestAPINameA", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ channel2 := &model.Channel{DisplayName: "TestAPINameB", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+
+ th.LoginBasic2()
+
+ if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPIName"}); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ if (*channels)[1].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPINameA"}); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPINameB"}); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: channel1.Name}); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if _, err := Client.SearchMoreChannels(model.ChannelSearch{Term: ""}); err == nil {
+ t.Fatal("should have errored - empty term")
+ }
+
+ if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "blargh"}); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if len(*channels) != 0 {
+ t.Fatal("should have no channels")
+ }
+ }
+
+ Client.SetTeamId("junk")
+ if _, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "blargh"}); err == nil {
+ t.Fatal("should have errored - bad team id")
+ }
+}
+
+func TestAutocompleteChannels(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ Client := th.BasicClient
+ team := th.BasicTeam
+
+ channel1 := &model.Channel{DisplayName: "TestAPINameA", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ channel2 := &model.Channel{DisplayName: "TestAPINameB", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+
+ channel3 := &model.Channel{DisplayName: "BadChannelC", Name: "c" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: model.NewId()}
+ channel3 = th.SystemAdminClient.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel)
+
+ channel4 := &model.Channel{DisplayName: "BadChannelD", Name: "d" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
+ channel4 = Client.Must(Client.CreateChannel(channel4)).Data.(*model.Channel)
+
+ if result, err := Client.AutocompleteChannels("TestAPIName"); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ if (*channels)[1].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if result, err := Client.AutocompleteChannels(channel1.Name); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+ }
+
+ if result, err := Client.AutocompleteChannels("BadChannelC"); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if len(*channels) != 0 {
+ t.Fatal("should have been empty")
+ }
+ }
+
+ if result, err := Client.AutocompleteChannels("BadChannelD"); err != nil {
+ t.Fatal(err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+
+ if len(*channels) != 0 {
+ t.Fatal("should have been empty")
+ }
+ }
+
+ Client.SetTeamId("junk")
+
+ if _, err := Client.AutocompleteChannels("BadChannelD"); err == nil {
+ t.Fatal("should have failed - bad team id")
+ }
+}
diff --git a/api/command_join.go b/api/command_join.go
index 2aba1bbd5..0499d503d 100644
--- a/api/command_join.go
+++ b/api/command_join.go
@@ -33,25 +33,22 @@ func (me *JoinProvider) GetCommand(c *Context) *model.Command {
}
func (me *JoinProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
- if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetByName(c.TeamId, message); result.Err != nil {
return &model.CommandResponse{Text: c.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
- channels := result.Data.(*model.ChannelList)
+ channel := result.Data.(*model.Channel)
- for _, v := range *channels {
+ if channel.Name == message {
- if v.Name == message {
-
- if v.Type != model.CHANNEL_OPEN {
- return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
- }
-
- if err, _ := JoinChannelById(c, c.Session.UserId, v.Id); err != nil {
- return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
- }
+ if channel.Type != model.CHANNEL_OPEN {
+ return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
- return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + v.Name, Text: c.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ if err, _ := JoinChannelById(c, c.Session.UserId, channel.Id); err != nil {
+ return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
+
+ return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + channel.Name, Text: c.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
diff --git a/api/deprecated.go b/api/deprecated.go
new file mode 100644
index 000000000..955613fab
--- /dev/null
+++ b/api/deprecated.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "net/http"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+// ONLY FOR APIs SCHEDULED TO BE DEPRECATED
+
+func InitDeprecated() {
+ l4g.Debug(utils.T("api.channel.init.debug"))
+
+ BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET") // SCHEDULED FOR DEPRECATION IN 3.7
+}
+
+func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ // user is already in the team
+ if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) {
+ return
+ }
+
+ if result := <-Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId, 0, 100000); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else if HandleEtag(result.Data.(*model.ChannelList).Etag(), w, r) {
+ return
+ } else {
+ data := result.Data.(*model.ChannelList)
+ w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag())
+ w.Write([]byte(data.ToJson()))
+ }
+}
diff --git a/api/deprecated_test.go b/api/deprecated_test.go
new file mode 100644
index 000000000..000b3950d
--- /dev/null
+++ b/api/deprecated_test.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "testing"
+
+ "github.com/mattermost/platform/model"
+)
+
+func TestGetMoreChannel(t *testing.T) {
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ team := th.BasicTeam
+
+ channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+
+ th.LoginBasic2()
+
+ rget := Client.Must(Client.GetMoreChannels(""))
+ channels := rget.Data.(*model.ChannelList)
+
+ if (*channels)[0].DisplayName != channel1.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ if (*channels)[1].DisplayName != channel2.DisplayName {
+ t.Fatal("full name didn't match")
+ }
+
+ // test etag caching
+ if cache_result, err := Client.GetMoreChannels(rget.Etag); err != nil {
+ t.Fatal(err)
+ } else if cache_result.Data.(*model.ChannelList) != nil {
+ t.Log(cache_result.Data)
+ t.Fatal("cache should be empty")
+ }
+}
diff --git a/api/user.go b/api/user.go
index 3a303bee4..26066dabc 100644
--- a/api/user.go
+++ b/api/user.go
@@ -2712,8 +2712,8 @@ func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Reque
searchOptions := map[string]bool{}
searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
- uchan := Srv.Store.User().SearchInChannel(channelId, term, map[string]bool{})
- nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, map[string]bool{})
+ uchan := Srv.Store.User().SearchInChannel(channelId, term, searchOptions)
+ nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions)
autocomplete := &model.UserAutocompleteInChannel{}
@@ -2758,7 +2758,10 @@ func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request)
}
}
- uchan := Srv.Store.User().Search(teamId, term, map[string]bool{})
+ searchOptions := map[string]bool{}
+ searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
+
+ uchan := Srv.Store.User().Search(teamId, term, searchOptions)
autocomplete := &model.UserAutocompleteInTeam{}