Changed post searching to allow searching by multiple users/channels

Этот коммит содержится в:
hmhealey
2015-10-23 17:28:02 -04:00
родитель 2ccf80d91d
Коммит 2383d5dd37
6 изменённых файлов: 161 добавлений и 77 удалений

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

@@ -820,45 +820,23 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
plainSearchParams, hashtagSearchParams := model.ParseSearchParams(terms) paramsList := model.ParseSearchParams(terms)
channels := []store.StoreChannel{}
var hchan store.StoreChannel for _, params := range paramsList {
if hashtagSearchParams != nil { channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
hchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, hashtagSearchParams)
} }
var pchan store.StoreChannel posts := &model.PostList{}
if plainSearchParams != nil { for _, channel := range channels {
pchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, plainSearchParams) if result := <-channel; result.Err != nil {
}
mainList := &model.PostList{}
if hchan != nil {
if result := <-hchan; result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
mainList = result.Data.(*model.PostList) data := result.Data.(*model.PostList)
posts.Extend(data)
} }
} }
plainList := &model.PostList{} w.Write([]byte(posts.ToJson()))
if pchan != nil {
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return
} else {
plainList = result.Data.(*model.PostList)
}
}
for _, postId := range plainList.Order {
if _, ok := mainList.Posts[postId]; !ok {
mainList.AddPost(plainList.Posts[postId])
mainList.AddOrder(postId)
}
}
w.Write([]byte(mainList.ToJson()))
} }

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

@@ -427,12 +427,18 @@ func TestSearchPostsInChannel(t *testing.T) {
channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
channel3 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"} post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
post3 := &model.Post{ChannelId: channel2.Id, Message: "other message with no return"} post3 := &model.Post{ChannelId: channel2.Id, Message: "other message with no return"}
post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
post4 := &model.Post{ChannelId: channel3.Id, Message: "other message with no return"}
post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
if result := Client.Must(Client.SearchPosts("channel:")).Data.(*model.PostList); len(result.Order) != 0 { if result := Client.Must(Client.SearchPosts("channel:")).Data.(*model.PostList); len(result.Order) != 0 {
t.Fatalf("wrong number of posts returned %v", len(result.Order)) t.Fatalf("wrong number of posts returned %v", len(result.Order))
} }
@@ -476,6 +482,10 @@ func TestSearchPostsInChannel(t *testing.T) {
if result := Client.Must(Client.SearchPosts("sgtitlereview channel: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 { if result := Client.Must(Client.SearchPosts("sgtitlereview channel: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order)) t.Fatalf("wrong number of posts returned %v", len(result.Order))
} }
if result := Client.Must(Client.SearchPosts("channel: " + channel2.Name + " channel: " + channel3.Name)).Data.(*model.PostList); len(result.Order) != 3 {
t.Fatalf("wrong number of posts returned :) %v :) %v", result.Posts, result.Order)
}
} }
func TestSearchPostsFromUser(t *testing.T) { func TestSearchPostsFromUser(t *testing.T) {
@@ -510,11 +520,12 @@ func TestSearchPostsFromUser(t *testing.T) {
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"} post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
// includes "X has joined the channel" messages for both user2 and user3
if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 { if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order)) t.Fatalf("wrong number of posts returned %v", len(result.Order))
} }
// note that this includes the "User2 has joined the channel" system messages
if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 { if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
t.Fatalf("wrong number of posts returned %v", len(result.Order)) t.Fatalf("wrong number of posts returned %v", len(result.Order))
} }
@@ -526,6 +537,30 @@ func TestSearchPostsFromUser(t *testing.T) {
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 { if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order)) t.Fatalf("wrong number of posts returned %v", len(result.Order))
} }
user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
Client.LoginByEmail(team.Name, user3.Email, "pwd")
Client.Must(Client.JoinChannel(channel1.Id))
Client.Must(Client.JoinChannel(channel2.Id))
if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 5 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name)).Data.(*model.PostList); len(result.Order) != 3 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " joined")).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
} }
func TestGetPostsCache(t *testing.T) { func TestGetPostsCache(t *testing.T) {

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

@@ -54,6 +54,15 @@ func (o *PostList) AddPost(post *Post) {
o.Posts[post.Id] = post o.Posts[post.Id] = post
} }
func (o *PostList) Extend(other *PostList) {
for _, postId := range other.Order {
if _, ok := o.Posts[postId]; !ok {
o.AddPost(other.Posts[postId])
o.AddOrder(postId)
}
}
}
func (o *PostList) Etag() string { func (o *PostList) Etag() string {
id := "0" id := "0"

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

@@ -34,3 +34,37 @@ func TestPostListJson(t *testing.T) {
t.Fatal("failed to serialize") t.Fatal("failed to serialize")
} }
} }
func TestPostListExtend(t *testing.T) {
l1 := PostList{}
p1 := &Post{Id: NewId(), Message: NewId()}
l1.AddPost(p1)
l1.AddOrder(p1.Id)
p2 := &Post{Id: NewId(), Message: NewId()}
l1.AddPost(p2)
l1.AddOrder(p2.Id)
l2 := PostList{}
p3 := &Post{Id: NewId(), Message: NewId()}
l2.AddPost(p3)
l2.AddOrder(p3.Id)
l2.Extend(&l1)
if len(l1.Posts) != 2 || len(l1.Order) != 2 {
t.Fatal("extending l2 changed l1")
} else if len(l2.Posts) != 3 {
t.Fatal("failed to extend posts l2")
} else if l2.Order[0] != p3.Id || l2.Order[1] != p1.Id || l2.Order[2] != p2.Id {
t.Fatal("failed to extend order of l2")
}
if len(l1.Posts) != 2 || len(l1.Order) != 2 {
t.Fatal("extending l2 again changed l1")
} else if len(l2.Posts) != 3 || len(l2.Order) != 3 {
t.Fatal("extending l2 again changed l2")
}
}

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

@@ -31,9 +31,9 @@ func splitWords(text string) []string {
return words return words
} }
func parseSearchFlags(input []string) ([]string, map[string]string) { func parseSearchFlags(input []string) ([]string, [][2]string) {
words := []string{} words := []string{}
flags := make(map[string]string) flags := [][2]string{}
skipNextWord := false skipNextWord := false
for i, word := range input { for i, word := range input {
@@ -52,10 +52,10 @@ func parseSearchFlags(input []string) ([]string, map[string]string) {
// check for case insensitive equality // check for case insensitive equality
if strings.EqualFold(flag, searchFlag) { if strings.EqualFold(flag, searchFlag) {
if value != "" { if value != "" {
flags[searchFlag] = value flags = append(flags, [2]string{searchFlag, value})
isFlag = true isFlag = true
} else if i < len(input)-1 { } else if i < len(input)-1 {
flags[searchFlag] = input[i+1] flags = append(flags, [2]string{searchFlag, input[i+1]})
skipNextWord = true skipNextWord = true
isFlag = true isFlag = true
} }
@@ -75,56 +75,77 @@ func parseSearchFlags(input []string) ([]string, map[string]string) {
return words, flags return words, flags
} }
func ParseSearchParams(text string) (*SearchParams, *SearchParams) { func ParseSearchParams(text string) []*SearchParams {
words, flags := parseSearchFlags(splitWords(text)) words, flags := parseSearchFlags(splitWords(text))
hashtagTerms := []string{} hashtagTermList := []string{}
plainTerms := []string{} plainTermList := []string{}
for _, word := range words { for _, word := range words {
if validHashtag.MatchString(word) { if validHashtag.MatchString(word) {
hashtagTerms = append(hashtagTerms, word) hashtagTermList = append(hashtagTermList, word)
} else { } else {
plainTerms = append(plainTerms, word) plainTermList = append(plainTermList, word)
} }
} }
inChannel := flags["channel"] hashtagTerms := strings.Join(hashtagTermList, " ")
if inChannel == "" { plainTerms := strings.Join(plainTermList, " ")
inChannel = flags["in"]
}
fromUser := flags["from"] inChannels := []string{}
fromUsers := []string{}
var plainParams *SearchParams for _, flagPair := range flags {
if len(plainTerms) > 0 { flag := flagPair[0]
plainParams = &SearchParams{ value := flagPair[1]
Terms: strings.Join(plainTerms, " "),
IsHashtag: false, if flag == "in" || flag == "channel" {
InChannel: inChannel, inChannels = append(inChannels, value)
FromUser: fromUser, } else if flag == "from" {
fromUsers = append(fromUsers, value)
} }
} }
var hashtagParams *SearchParams if len(inChannels) == 0 {
if len(hashtagTerms) > 0 { inChannels = append(inChannels, "")
hashtagParams = &SearchParams{ }
Terms: strings.Join(hashtagTerms, " "), if len(fromUsers) == 0 {
IsHashtag: true, fromUsers = append(fromUsers, "")
InChannel: inChannel, }
FromUser: fromUser,
paramsList := []*SearchParams{}
for _, inChannel := range inChannels {
for _, fromUser := range fromUsers {
if len(plainTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: plainTerms,
IsHashtag: false,
InChannel: inChannel,
FromUser: fromUser,
})
}
if len(hashtagTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: hashtagTerms,
IsHashtag: true,
InChannel: inChannel,
FromUser: fromUser,
})
}
// special case for when no terms are specified but we still have a filter
if len(plainTerms) == 0 && len(hashtagTerms) == 0 {
paramsList = append(paramsList, &SearchParams{
Terms: "",
IsHashtag: true,
InChannel: inChannel,
FromUser: fromUser,
})
}
} }
} }
// special case for when no terms are specified but we still have a filter return paramsList
if plainParams == nil && hashtagParams == nil && (inChannel != "" || fromUser != "") {
plainParams = &SearchParams{
Terms: "",
IsHashtag: false,
InChannel: inChannel,
FromUser: fromUser,
}
}
return plainParams, hashtagParams
} }

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

@@ -28,25 +28,25 @@ func TestParseSearchFlags(t *testing.T) {
if words, flags := parseSearchFlags(splitWords("apple banana from:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { if words, flags := parseSearchFlags(splitWords("apple banana from:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
t.Fatalf("got incorrect words %v", words) t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 1 || flags["from"] != "chan" { } else if len(flags) != 1 || flags[0][0] != "from" || flags[0][1] != "chan" {
t.Fatalf("got incorrect flags %v", flags) t.Fatalf("got incorrect flags %v", flags)
} }
if words, flags := parseSearchFlags(splitWords("apple banana from: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { if words, flags := parseSearchFlags(splitWords("apple banana from: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
t.Fatalf("got incorrect words %v", words) t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 1 || flags["from"] != "chan" { } else if len(flags) != 1 || flags[0][0] != "from" || flags[0][1] != "chan" {
t.Fatalf("got incorrect flags %v", flags) t.Fatalf("got incorrect flags %v", flags)
} }
if words, flags := parseSearchFlags(splitWords("apple banana in: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { if words, flags := parseSearchFlags(splitWords("apple banana in: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
t.Fatalf("got incorrect words %v", words) t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 1 || flags["in"] != "chan" { } else if len(flags) != 1 || flags[0][0] != "in" || flags[0][1] != "chan" {
t.Fatalf("got incorrect flags %v", flags) t.Fatalf("got incorrect flags %v", flags)
} }
if words, flags := parseSearchFlags(splitWords("apple banana channel:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { if words, flags := parseSearchFlags(splitWords("apple banana channel:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
t.Fatalf("got incorrect words %v", words) t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 1 || flags["channel"] != "chan" { } else if len(flags) != 1 || flags[0][0] != "channel" || flags[0][1] != "chan" {
t.Fatalf("got incorrect flags %v", flags) t.Fatalf("got incorrect flags %v", flags)
} }
@@ -64,7 +64,14 @@ func TestParseSearchFlags(t *testing.T) {
if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" { if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" {
t.Fatalf("got incorrect words %v", words) t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 2 || flags["channel"] != "first" || flags["in"] != "second" { } else if len(flags) != 2 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "in" || flags[1][1] != "second" {
t.Fatalf("got incorrect flags %v", flags)
}
if words, flags := parseSearchFlags(splitWords("channel: first channel: second from: third from: fourth")); len(words) != 0 {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 4 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "channel" || flags[1][1] != "second" ||
flags[2][0] != "from" || flags[2][1] != "third" || flags[3][0] != "from" || flags[3][1] != "fourth" {
t.Fatalf("got incorrect flags %v", flags) t.Fatalf("got incorrect flags %v", flags)
} }
} }