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
Этот коммит содержится в:
Joram Wilander
2016-11-24 09:35:09 -05:00
коммит произвёл Harrison Healey
родитель c96ecae6da
Коммит 981ea33b8e
27 изменённых файлов: 1337 добавлений и 381 удалений

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

@@ -5,6 +5,8 @@ package store
import (
"database/sql"
"fmt"
"strings"
l4g "github.com/alecthomas/log4go"
"github.com/go-gorp/gorp"
@@ -65,6 +67,8 @@ func (s SqlChannelStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_channelmembers_channel_id", "ChannelMembers", "ChannelId")
s.CreateIndexIfNotExists("idx_channelmembers_user_id", "ChannelMembers", "UserId")
s.CreateFullTextIndexIfNotExists("idx_channels_txt", "Channels", "Name, DisplayName")
}
func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel {
@@ -392,7 +396,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel
return storeChannel
}
func (s SqlChannelStore) GetMoreChannels(teamId string, userId string) StoreChannel {
func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
@@ -418,8 +422,10 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string) StoreChan
AND TeamId = :TeamId2
AND UserId = :UserId
AND DeleteAt = 0)
ORDER BY DisplayName`,
map[string]interface{}{"TeamId1": teamId, "TeamId2": teamId, "UserId": userId})
ORDER BY DisplayName
LIMIT :Limit
OFFSET :Offset`,
map[string]interface{}{"TeamId1": teamId, "TeamId2": teamId, "UserId": userId, "Limit": limit, "Offset": offset})
if err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.GetMoreChannels", "store.sql_channel.get_more_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
@@ -1104,3 +1110,110 @@ func (s SqlChannelStore) GetMembersForUser(teamId string, userId string) StoreCh
return storeChannel
}
func (s SqlChannelStore) SearchInTeam(teamId string, term string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
searchQuery := `
SELECT
*
FROM
Channels
WHERE
TeamId = :TeamId
AND Type = 'O'
AND DeleteAt = 0
SEARCH_CLAUSE
ORDER BY DisplayName
LIMIT 100`
storeChannel <- s.performSearch(searchQuery, term, map[string]interface{}{"TeamId": teamId})
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
searchQuery := `
SELECT
*
FROM
Channels
WHERE
TeamId = :TeamId
AND Type = 'O'
AND DeleteAt = 0
AND Id NOT IN (SELECT
Channels.Id
FROM
Channels,
ChannelMembers
WHERE
Id = ChannelId
AND TeamId = :TeamId
AND UserId = :UserId
AND DeleteAt = 0)
SEARCH_CLAUSE
ORDER BY DisplayName
LIMIT 100`
storeChannel <- s.performSearch(searchQuery, term, map[string]interface{}{"TeamId": teamId, "UserId": userId})
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) performSearch(searchQuery string, term string, parameters map[string]interface{}) StoreResult {
result := StoreResult{}
// these chars have special meaning and can be treated as spaces
for _, c := range specialSearchChar {
term = strings.Replace(term, c, " ", -1)
}
if term == "" {
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
splitTerm := strings.Fields(term)
for i, t := range strings.Fields(term) {
if i == len(splitTerm)-1 {
splitTerm[i] = t + ":*"
} else {
splitTerm[i] = t + ":* &"
}
}
term = strings.Join(splitTerm, " ")
searchClause := fmt.Sprintf("AND (%s) @@ to_tsquery('simple', :Term)", "Name || ' ' || DisplayName")
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
splitTerm := strings.Fields(term)
for i, t := range strings.Fields(term) {
splitTerm[i] = "+" + t + "*"
}
term = strings.Join(splitTerm, " ")
searchClause := fmt.Sprintf("AND MATCH(%s) AGAINST (:Term IN BOOLEAN MODE)", "Name, DisplayName")
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
}
var channels model.ChannelList
parameters["Term"] = term
if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil {
result.Err = model.NewLocAppError("SqlChannelStore.Search", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error())
} else {
result.Data = &channels
}
return result
}

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

@@ -309,7 +309,7 @@ func TestChannelStoreDelete(t *testing.T) {
t.Fatal("invalid number of channels")
}
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId)
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 0, 100)
list = cresult.Data.(*model.ChannelList)
if len(*list) != 1 {
@@ -621,7 +621,10 @@ func TestChannelStoreGetMoreChannels(t *testing.T) {
o5.Type = model.CHANNEL_PRIVATE
Must(store.Channel().Save(&o5))
cresult := <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId)
cresult := <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 0, 100)
if cresult.Err != nil {
t.Fatal(cresult.Err)
}
list := cresult.Data.(*model.ChannelList)
if len(*list) != 1 {
@@ -632,10 +635,38 @@ func TestChannelStoreGetMoreChannels(t *testing.T) {
t.Fatal("missing channel")
}
o6 := model.Channel{}
o6.TeamId = o1.TeamId
o6.DisplayName = "ChannelA"
o6.Name = "a" + model.NewId() + "b"
o6.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o6))
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 0, 100)
list = cresult.Data.(*model.ChannelList)
if len(*list) != 2 {
t.Fatal("wrong list length")
}
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 0, 1)
list = cresult.Data.(*model.ChannelList)
if len(*list) != 1 {
t.Fatal("wrong list length")
}
cresult = <-store.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 1, 1)
list = cresult.Data.(*model.ChannelList)
if len(*list) != 1 {
t.Fatal("wrong list length")
}
if r1 := <-store.Channel().AnalyticsTypeCount(o1.TeamId, model.CHANNEL_OPEN); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(int64) != 2 {
if r1.Data.(int64) != 3 {
t.Log(r1.Data)
t.Fatal("wrong value")
}
@@ -1055,3 +1086,180 @@ func TestUpdateExtrasByUser(t *testing.T) {
t.Fatal("failed to update extras by user: %v", result.Err)
}
}
func TestChannelStoreSearchMore(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "ChannelA"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o1))
o2 := model.Channel{}
o2.TeamId = model.NewId()
o2.DisplayName = "Channel2"
o2.Name = "a" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o2))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m1))
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = model.NewId()
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m2))
m3 := model.ChannelMember{}
m3.ChannelId = o2.Id
m3.UserId = model.NewId()
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m3))
o3 := model.Channel{}
o3.TeamId = o1.TeamId
o3.DisplayName = "ChannelA"
o3.Name = "a" + model.NewId() + "b"
o3.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o3))
o4 := model.Channel{}
o4.TeamId = o1.TeamId
o4.DisplayName = "ChannelB"
o4.Name = "a" + model.NewId() + "b"
o4.Type = model.CHANNEL_PRIVATE
Must(store.Channel().Save(&o4))
o5 := model.Channel{}
o5.TeamId = o1.TeamId
o5.DisplayName = "ChannelC"
o5.Name = "a" + model.NewId() + "b"
o5.Type = model.CHANNEL_PRIVATE
Must(store.Channel().Save(&o5))
if result := <-store.Channel().SearchMore(m1.UserId, o1.TeamId, "ChannelA"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) == 0 {
t.Fatal("should not be empty")
}
if (*channels)[0].Name != o3.Name {
t.Fatal("wrong channel returned")
}
}
if result := <-store.Channel().SearchMore(m1.UserId, o1.TeamId, o4.Name); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 0 {
t.Fatal("should be empty")
}
}
if result := <-store.Channel().SearchMore(m1.UserId, o1.TeamId, o3.Name); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) == 0 {
t.Fatal("should not be empty")
}
if (*channels)[0].Name != o3.Name {
t.Fatal("wrong channel returned")
}
}
}
func TestChannelStoreSearchInTeam(t *testing.T) {
Setup()
o1 := model.Channel{}
o1.TeamId = model.NewId()
o1.DisplayName = "ChannelA"
o1.Name = "a" + model.NewId() + "b"
o1.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o1))
o2 := model.Channel{}
o2.TeamId = model.NewId()
o2.DisplayName = "Channel2"
o2.Name = "a" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o2))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m1))
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = model.NewId()
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m2))
m3 := model.ChannelMember{}
m3.ChannelId = o2.Id
m3.UserId = model.NewId()
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&m3))
o3 := model.Channel{}
o3.TeamId = o1.TeamId
o3.DisplayName = "ChannelA"
o3.Name = "a" + model.NewId() + "b"
o3.Type = model.CHANNEL_OPEN
Must(store.Channel().Save(&o3))
o4 := model.Channel{}
o4.TeamId = o1.TeamId
o4.DisplayName = "ChannelB"
o4.Name = "a" + model.NewId() + "b"
o4.Type = model.CHANNEL_PRIVATE
Must(store.Channel().Save(&o4))
o5 := model.Channel{}
o5.TeamId = o1.TeamId
o5.DisplayName = "ChannelC"
o5.Name = "a" + model.NewId() + "b"
o5.Type = model.CHANNEL_PRIVATE
Must(store.Channel().Save(&o5))
if result := <-store.Channel().SearchInTeam(o1.TeamId, "ChannelA"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 2 {
t.Fatal("wrong length")
}
}
if result := <-store.Channel().SearchInTeam(o1.TeamId, ""); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) == 0 {
t.Fatal("should not be empty")
}
}
if result := <-store.Channel().SearchInTeam(o1.TeamId, "blargh"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 0 {
t.Fatal("should be empty")
}
}
}

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

@@ -1120,7 +1120,7 @@ func (us SqlUserStore) Search(teamId string, term string, options map[string]boo
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Username ASC
LIMIT 50`
LIMIT 100`
} else {
searchQuery = `
SELECT
@@ -1264,7 +1264,7 @@ func (us SqlUserStore) performSearch(searchQuery string, term string, options ma
term = strings.Join(splitTerm, " ")
searchType = convertMySQLFullTextColumnsToPostgres(searchType)
searchClause := fmt.Sprintf("AND (%s) @@ to_tsquery(:Term)", searchType)
searchClause := fmt.Sprintf("AND (%s) @@ to_tsquery('simple', :Term)", searchType)
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
splitTerm := strings.Fields(term)

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

@@ -90,7 +90,7 @@ type ChannelStore interface {
PermanentDeleteByTeam(teamId string) StoreChannel
GetByName(team_id string, domain string) StoreChannel
GetChannels(teamId string, userId string) StoreChannel
GetMoreChannels(teamId string, userId string) StoreChannel
GetMoreChannels(teamId string, userId string, offset int, limit int) StoreChannel
GetChannelCounts(teamId string, userId string) StoreChannel
GetTeamChannels(teamId string) StoreChannel
GetAll(teamId string) StoreChannel
@@ -113,6 +113,8 @@ type ChannelStore interface {
AnalyticsTypeCount(teamId string, channelType string) StoreChannel
ExtraUpdateByUser(userId string, time int64) StoreChannel
GetMembersForUser(teamId string, userId string) StoreChannel
SearchInTeam(teamId string, term string) StoreChannel
SearchMore(userId string, teamId string, term string) StoreChannel
}
type PostStore interface {