Added the SearchPostsInTeam method to the plugin API (#10106)

This commit is contained in:
Andrew Braunstein
2019-02-12 22:41:32 -08:00
committed by Hanzei
parent 87e36a3ecf
commit c08fda1337
11 changed files with 197 additions and 31 deletions

View File

@@ -394,7 +394,7 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
results, err := c.App.SearchPostsInTeam(terms, c.App.Session.UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels, int(timeZoneOffset), page, perPage)
results, err := c.App.SearchPostsInTeamForUser(terms, c.App.Session.UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels, int(timeZoneOffset), page, perPage)
elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
metrics := c.App.Metrics

View File

@@ -342,6 +342,14 @@ func (api *PluginAPI) SearchUsers(search *model.UserSearch) ([]*model.User, *mod
return api.app.SearchUsers(search, pluginSearchUsersOptions)
}
func (api *PluginAPI) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError) {
postList, err := api.app.SearchPostsInTeam(teamId, paramsList)
if err != nil {
return nil, err
}
return postList.ToSlice(), nil
}
func (api *PluginAPI) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
// For now, don't allow overriding these via the plugin API.
userRequestorId := ""

View File

@@ -693,6 +693,52 @@ func TestPluginAPISearchChannels(t *testing.T) {
})
}
func TestPluginAPISearchPostsInTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
api := th.SetupPluginAPI()
testCases := []struct {
description string
teamId string
params []*model.SearchParams
expectedPostsLen int
}{
{
"nil params",
th.BasicTeam.Id,
nil,
0,
},
{
"empty params",
th.BasicTeam.Id,
[]*model.SearchParams{},
0,
},
{
"doesn't match any posts",
th.BasicTeam.Id,
model.ParseSearchParams("bad message", 0),
0,
},
{
"matched posts",
th.BasicTeam.Id,
model.ParseSearchParams(th.BasicPost.Message, 0),
1,
},
}
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
posts, err := api.SearchPostsInTeam(testCase.teamId, testCase.params)
assert.Nil(t, err)
assert.Equal(t, testCase.expectedPostsLen, len(posts))
})
}
}
func TestPluginAPIGetChannelsForTeamForUser(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()

View File

@@ -743,7 +743,42 @@ func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userId, team
return channel, nil
}
func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) {
func (a *App) searchPostsInTeam(teamId string, userId string, paramsList []*model.SearchParams, modifierFun func(*model.SearchParams)) (*model.PostList, *model.AppError) {
channels := []store.StoreChannel{}
for _, params := range paramsList {
// Don't allow users to search for everything.
if params.Terms == "*" {
continue
}
modifierFun(params)
channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params))
}
posts := model.NewPostList()
for _, channel := range channels {
result := <-channel
if result.Err != nil {
return nil, result.Err
}
data := result.Data.(*model.PostList)
posts.Extend(data)
}
posts.SortByCreateAt()
return posts, nil
}
func (a *App) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) {
if !*a.Config().ServiceSettings.EnablePostSearch {
return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v", teamId), http.StatusNotImplemented)
}
return a.searchPostsInTeam(teamId, "", paramsList, func(params *model.SearchParams) {
params.SearchWithoutUserId = true
})
}
func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) {
paramsList := model.ParseSearchParams(terms, timeZoneOffset)
includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels
@@ -815,7 +850,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr
}
if !*a.Config().ServiceSettings.EnablePostSearch {
return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented)
return nil, model.NewAppError("SearchPostsInTeamForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented)
}
// Since we don't support paging we just return nothing for later pages
@@ -823,39 +858,23 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr
return model.MakePostSearchResults(model.NewPostList(), nil), nil
}
channels := []store.StoreChannel{}
for _, params := range paramsList {
posts, err := a.searchPostsInTeam(teamId, userId, paramsList, func(params *model.SearchParams) {
params.IncludeDeletedChannels = includeDeleted
params.OrTerms = isOrSearch
// don't allow users to search for everything
if params.Terms != "*" {
for idx, channelName := range params.InChannels {
if strings.HasPrefix(channelName, "@") {
channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
if err != nil {
mlog.Error(fmt.Sprint(err))
continue
}
params.InChannels[idx] = channel.Name
for idx, channelName := range params.InChannels {
if strings.HasPrefix(channelName, "@") {
channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
if err != nil {
mlog.Error(fmt.Sprint(err))
continue
}
params.InChannels[idx] = channel.Name
}
channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params))
}
})
if err != nil {
return nil, err
}
posts := model.NewPostList()
for _, channel := range channels {
result := <-channel
if result.Err != nil {
return nil, result.Err
}
data := result.Data.(*model.PostList)
posts.Extend(data)
}
posts.SortByCreateAt()
return model.MakePostSearchResults(posts, nil), nil
}

View File

@@ -21,6 +21,14 @@ func NewPostList() *PostList {
}
}
func (o *PostList) ToSlice() []*Post {
var posts []*Post
for _, id := range o.Order {
posts = append(posts, o.Posts[id])
}
return posts
}
func (o *PostList) WithRewrittenImageURLs(f func(string) string) *PostList {
copy := *o
copy.Posts = make(map[string]*Post)

View File

@@ -90,3 +90,21 @@ func TestPostListSortByCreateAt(t *testing.T) {
assert.EqualValues(t, pl.Order[1], p1.Id)
assert.EqualValues(t, pl.Order[2], p2.Id)
}
func TestPostListToSlice(t *testing.T) {
pl := PostList{}
p1 := &Post{Id: NewId(), Message: NewId(), CreateAt: 2}
pl.AddPost(p1)
p2 := &Post{Id: NewId(), Message: NewId(), CreateAt: 1}
pl.AddPost(p2)
p3 := &Post{Id: NewId(), Message: NewId(), CreateAt: 3}
pl.AddPost(p3)
pl.AddOrder(p1.Id)
pl.AddOrder(p2.Id)
pl.AddOrder(p3.Id)
want := []*Post{p1, p2, p3}
assert.Equal(t, want, pl.ToSlice())
}

View File

@@ -23,6 +23,8 @@ type SearchParams struct {
OrTerms bool
IncludeDeletedChannels bool
TimeZoneOffset int
// True if this search doesn't originate from a "current user".
SearchWithoutUserId bool
}
// Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate

View File

@@ -226,6 +226,11 @@ type API interface {
// Minimum server version: 5.6
SearchUsers(search *model.UserSearch) ([]*model.User, *model.AppError)
// SearchPostsInTeam returns a list of posts in a specific team that match the given params.
//
// Minimum server version: 5.10
SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError)
// AddChannelMember creates a channel membership for a user.
AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)

View File

@@ -2053,6 +2053,36 @@ func (s *apiRPCServer) SearchUsers(args *Z_SearchUsersArgs, returns *Z_SearchUse
return nil
}
type Z_SearchPostsInTeamArgs struct {
A string
B []*model.SearchParams
}
type Z_SearchPostsInTeamReturns struct {
A []*model.Post
B *model.AppError
}
func (g *apiRPCClient) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError) {
_args := &Z_SearchPostsInTeamArgs{teamId, paramsList}
_returns := &Z_SearchPostsInTeamReturns{}
if err := g.client.Call("Plugin.SearchPostsInTeam", _args, _returns); err != nil {
log.Printf("RPC call to SearchPostsInTeam API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) SearchPostsInTeam(args *Z_SearchPostsInTeamArgs, returns *Z_SearchPostsInTeamReturns) error {
if hook, ok := s.impl.(interface {
SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError)
}); ok {
returns.A, returns.B = hook.SearchPostsInTeam(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API SearchPostsInTeam called but not implemented."))
}
return nil
}
type Z_AddChannelMemberArgs struct {
A string
B string

View File

@@ -1983,6 +1983,31 @@ func (_m *API) SearchChannels(teamId string, term string) ([]*model.Channel, *mo
return r0, r1
}
// SearchPostsInTeam provides a mock function with given fields: teamId, paramsList
func (_m *API) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError) {
ret := _m.Called(teamId, paramsList)
var r0 []*model.Post
if rf, ok := ret.Get(0).(func(string, []*model.SearchParams) []*model.Post); ok {
r0 = rf(teamId, paramsList)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Post)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, []*model.SearchParams) *model.AppError); ok {
r1 = rf(teamId, paramsList)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SearchTeams provides a mock function with given fields: term
func (_m *API) SearchTeams(term string) ([]*model.Team, *model.AppError) {
ret := _m.Called(term)

View File

@@ -808,6 +808,11 @@ func (s *SqlPostStore) Search(teamId string, userId string, params *model.Search
deletedQueryPart = ""
}
userIdPart := "AND UserId = :UserId"
if params.SearchWithoutUserId {
userIdPart = ""
}
searchQuery := `
SELECT
*
@@ -826,7 +831,7 @@ func (s *SqlPostStore) Search(teamId string, userId string, params *model.Search
WHERE
Id = ChannelId
AND (TeamId = :TeamId OR TeamId = '')
AND UserId = :UserId
` + userIdPart + `
` + deletedQueryPart + `
CHANNEL_FILTER)
CREATEDATE_CLAUSE