mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-28 17:46:53 -04:00
POC: Cross-team recent search (#20027)
Just a quick POC to move fast :P We use a search pointer to keep track of the next row to inesrt to. For every new search we increment the pointer and do modulo 5. This means that the value will always remain between 0-4. And that way, we will always overwrite the oldest entry on every search. And while getting the results, we get all results for that user. The search parameters are json marshalled and stored as a JSON blob. This is because there is no need to search/filter them in the DB. Pending items: Tests obviously. To improve: The client needs to send the channel ids instead of channel names.
This commit is contained in:
parent
979b616189
commit
aa59c28b04
26 changed files with 488 additions and 68 deletions
26
api4/file.go
26
api4/file.go
|
|
@ -56,13 +56,14 @@ const maxMultipartFormDataBytes = 10 * 1024 // 10Kb
|
|||
|
||||
func (api *API) InitFile() {
|
||||
api.BaseRoutes.Files.Handle("", api.APISessionRequired(uploadFileStream)).Methods("POST")
|
||||
api.BaseRoutes.Files.Handle("/search", api.APISessionRequired(searchFilesForUser)).Methods("POST")
|
||||
api.BaseRoutes.File.Handle("", api.APISessionRequiredTrustRequester(getFile)).Methods("GET")
|
||||
api.BaseRoutes.File.Handle("/thumbnail", api.APISessionRequiredTrustRequester(getFileThumbnail)).Methods("GET")
|
||||
api.BaseRoutes.File.Handle("/link", api.APISessionRequired(getFileLink)).Methods("GET")
|
||||
api.BaseRoutes.File.Handle("/preview", api.APISessionRequiredTrustRequester(getFilePreview)).Methods("GET")
|
||||
api.BaseRoutes.File.Handle("/info", api.APISessionRequired(getFileInfo)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.Team.Handle("/files/search", api.APISessionRequiredDisableWhenBusy(searchFiles)).Methods("POST")
|
||||
api.BaseRoutes.Team.Handle("/files/search", api.APISessionRequiredDisableWhenBusy(searchFilesInTeam)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.PublicFile.Handle("", api.APIHandler(getPublicFile)).Methods("GET")
|
||||
|
||||
|
|
@ -734,7 +735,7 @@ func writeFileResponse(filename string, contentType string, contentSize int64, l
|
|||
http.ServeContent(w, r, filename, lastModification, fileReader)
|
||||
}
|
||||
|
||||
func searchFiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func searchFilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireTeamId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
|
|
@ -745,6 +746,16 @@ func searchFiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
searchFiles(c, w, r, c.Params.TeamId)
|
||||
}
|
||||
|
||||
func searchFilesForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Config().FeatureFlags.CommandPalette {
|
||||
searchFiles(c, w, r, "")
|
||||
}
|
||||
}
|
||||
|
||||
func searchFiles(c *Context, w http.ResponseWriter, r *http.Request, teamID string) {
|
||||
var params model.SearchParameter
|
||||
jsonErr := json.NewDecoder(r.Body).Decode(¶ms)
|
||||
if jsonErr != nil {
|
||||
|
|
@ -783,9 +794,18 @@ func searchFiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
includeDeletedChannels = *params.IncludeDeletedChannels
|
||||
}
|
||||
|
||||
modifier := ""
|
||||
if params.Modifier != nil {
|
||||
modifier = *params.Modifier
|
||||
}
|
||||
if modifier != "" && modifier != model.ModifierFiles && modifier != model.ModifierMessages {
|
||||
c.SetInvalidParam("modifier")
|
||||
return
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
results, err := c.App.SearchFilesInTeamForUser(c.AppContext, terms, c.AppContext.Session().UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage)
|
||||
results, err := c.App.SearchFilesInTeamForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage, modifier)
|
||||
|
||||
elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
|
||||
metrics := c.App.Metrics()
|
||||
|
|
|
|||
11
api4/post.go
11
api4/post.go
|
|
@ -644,9 +644,18 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request, teamId stri
|
|||
includeDeletedChannels = *params.IncludeDeletedChannels
|
||||
}
|
||||
|
||||
modifier := ""
|
||||
if params.Modifier != nil {
|
||||
modifier = *params.Modifier
|
||||
}
|
||||
if modifier != "" && modifier != model.ModifierFiles && modifier != model.ModifierMessages {
|
||||
c.SetInvalidParam("modifier")
|
||||
return
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
results, err := c.App.SearchPostsForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage)
|
||||
results, err := c.App.SearchPostsForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage, modifier)
|
||||
|
||||
elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
|
||||
metrics := c.App.Metrics()
|
||||
|
|
|
|||
|
|
@ -2458,19 +2458,23 @@ func TestSearchPostsFromUser(t *testing.T) {
|
|||
message = "sgtitlereview\n with return"
|
||||
_ = th.CreateMessagePostWithClient(client, th.BasicChannel2, message)
|
||||
|
||||
posts, _, _ := client.SearchPosts(th.BasicTeam.Id, "from: "+th.TeamAdminUser.Username, false)
|
||||
posts, _, err := client.SearchPosts(th.BasicTeam.Id, "from: "+th.TeamAdminUser.Username, false)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.TeamAdminUser.Username)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v", th.BasicUser2.Username)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" sgtitlereview", false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" sgtitlereview", false)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
|
||||
|
||||
message = "hullo"
|
||||
_ = th.CreateMessagePost(message)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" in:"+th.BasicChannel.Name, false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" in:"+th.BasicChannel.Name, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, posts.Order, 1, "wrong number of posts for search 'from: %v in:", th.BasicUser2.Username, th.BasicChannel.Name)
|
||||
|
||||
client.Login(user.Email, user.Password)
|
||||
|
|
@ -2478,19 +2482,23 @@ func TestSearchPostsFromUser(t *testing.T) {
|
|||
// wait for the join/leave messages to be created for user3 since they're done asynchronously
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username, false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username, false)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v from: %v'", th.BasicUser2.Username, user.Username)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name, false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, posts.Order, 1, "wrong number of posts")
|
||||
|
||||
message = "coconut"
|
||||
_ = th.CreateMessagePostWithClient(client, th.BasicChannel2, message)
|
||||
|
||||
posts, _, _ = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name+" coconut", false)
|
||||
posts, _, err = client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name+" coconut", false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, posts.Order, 1, "wrong number of posts")
|
||||
}
|
||||
|
||||
|
|
|
|||
23
api4/user.go
23
api4/user.go
|
|
@ -92,6 +92,7 @@ func (api *API) InitUser() {
|
|||
|
||||
api.BaseRoutes.User.Handle("/uploads", api.APISessionRequired(getUploadsForUser)).Methods("GET")
|
||||
api.BaseRoutes.User.Handle("/channel_members", api.APISessionRequired(getChannelMembersForUser)).Methods("GET")
|
||||
api.BaseRoutes.User.Handle("/recent_searches", api.APISessionRequiredDisableWhenBusy(getRecentSearches)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.Users.Handle("/invalid_emails", api.APISessionRequired(getUsersWithInvalidEmails)).Methods("GET")
|
||||
|
||||
|
|
@ -3245,3 +3246,25 @@ func getUsersWithInvalidEmails(c *Context, w http.ResponseWriter, r *http.Reques
|
|||
b, _ := json.Marshal(users)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getRecentSearches(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
|
||||
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||
return
|
||||
}
|
||||
|
||||
searchParams, err := c.App.GetRecentSearchesForUser(c.Params.UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(searchParams); err != nil {
|
||||
mlog.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,6 +689,7 @@ type AppIface interface {
|
|||
GetPublicChannelsByIdsForTeam(teamID string, channelIDs []string) (model.ChannelList, *model.AppError)
|
||||
GetPublicChannelsForTeam(teamID string, offset int, limit int) (model.ChannelList, *model.AppError)
|
||||
GetReactionsForPost(postID string) ([]*model.Reaction, *model.AppError)
|
||||
GetRecentSearchesForUser(userID string) ([]*model.SearchParams, *model.AppError)
|
||||
GetRecentlyActiveUsersForTeam(teamID string) (map[string]*model.User, *model.AppError)
|
||||
GetRecentlyActiveUsersForTeamPage(teamID string, page, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError)
|
||||
GetRemoteCluster(remoteClusterId string) (*model.RemoteCluster, *model.AppError)
|
||||
|
|
@ -955,9 +956,9 @@ type AppIface interface {
|
|||
SearchChannelsUserNotIn(teamID string, userID string, term string) (model.ChannelList, *model.AppError)
|
||||
SearchEmoji(name string, prefixOnly bool, limit int) ([]*model.Emoji, *model.AppError)
|
||||
SearchEngine() *searchengine.Broker
|
||||
SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.FileInfoList, *model.AppError)
|
||||
SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int, modifier string) (*model.FileInfoList, *model.AppError)
|
||||
SearchGroupChannels(userID, term string) (model.ChannelList, *model.AppError)
|
||||
SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError)
|
||||
SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int, modifier string) (*model.PostSearchResults, *model.AppError)
|
||||
SearchPostsInTeam(teamID string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError)
|
||||
SearchPrivateTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError)
|
||||
SearchPublicTeams(searchOpts *model.TeamSearch) ([]*model.Team, *model.AppError)
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,7 @@ func populateZipfile(w *zip.Writer, fileDatas []model.FileData) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.FileInfoList, *model.AppError) {
|
||||
func (a *App) SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int, modifier string) (*model.FileInfoList, *model.AppError) {
|
||||
paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset)
|
||||
includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels
|
||||
|
||||
|
|
@ -1279,6 +1279,7 @@ func (a *App) SearchFilesInTeamForUser(c *request.Context, terms string, userId
|
|||
finalParamsList := []*model.SearchParams{}
|
||||
|
||||
for _, params := range paramsList {
|
||||
params.Modifier = modifier
|
||||
params.OrTerms = isOrSearch
|
||||
params.IncludeDeletedChannels = includeDeleted
|
||||
// Don't allow users to search for "*"
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
|
||||
page := 0
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
|
|
@ -420,7 +420,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
|
||||
page := 1
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
|
|
@ -451,7 +451,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
|
|
@ -480,7 +480,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
|
|
@ -505,7 +505,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, results)
|
||||
|
|
@ -538,7 +538,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierFiles)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []string{}, results.Order)
|
||||
|
|
|
|||
|
|
@ -8069,6 +8069,28 @@ func (a *OpenTracingAppLayer) GetReactionsForPost(postID string) ([]*model.React
|
|||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRecentSearchesForUser")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetRecentSearchesForUser(userID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetRecentlyActiveUsersForTeam(teamID string) (map[string]*model.User, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetRecentlyActiveUsersForTeam")
|
||||
|
|
@ -14175,7 +14197,7 @@ func (a *OpenTracingAppLayer) SearchEngine() *searchengine.Broker {
|
|||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page int, perPage int) (*model.FileInfoList, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) SearchFilesInTeamForUser(c *request.Context, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page int, perPage int, modifier string) (*model.FileInfoList, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchFilesInTeamForUser")
|
||||
|
||||
|
|
@ -14187,7 +14209,7 @@ func (a *OpenTracingAppLayer) SearchFilesInTeamForUser(c *request.Context, terms
|
|||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.SearchFilesInTeamForUser(c, terms, userId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage)
|
||||
resultVar0, resultVar1 := a.app.SearchFilesInTeamForUser(c, terms, userId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage, modifier)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
|
|
@ -14219,7 +14241,7 @@ func (a *OpenTracingAppLayer) SearchGroupChannels(userID string, term string) (m
|
|||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page int, perPage int) (*model.PostSearchResults, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page int, perPage int, modifier string) (*model.PostSearchResults, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchPostsForUser")
|
||||
|
||||
|
|
@ -14231,7 +14253,7 @@ func (a *OpenTracingAppLayer) SearchPostsForUser(c *request.Context, terms strin
|
|||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.SearchPostsForUser(c, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage)
|
||||
resultVar0, resultVar1 := a.app.SearchPostsForUser(c, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage, modifier)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ func (api *PluginAPI) SearchPostsInTeamForUser(teamID string, userID string, sea
|
|||
includeDeletedChannels = *searchParams.IncludeDeletedChannels
|
||||
}
|
||||
|
||||
return api.app.SearchPostsForUser(api.ctx, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage)
|
||||
return api.app.SearchPostsForUser(api.ctx, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage, model.ModifierMessages)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) AddChannelMember(channelID, userID string) (*model.ChannelMember, *model.AppError) {
|
||||
|
|
|
|||
15
app/post.go
15
app/post.go
|
|
@ -1305,7 +1305,7 @@ func (a *App) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams)
|
|||
})
|
||||
}
|
||||
|
||||
func (a *App) SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) {
|
||||
func (a *App) SearchPostsForUser(c *request.Context, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int, modifier string) (*model.PostSearchResults, *model.AppError) {
|
||||
var postSearchResults *model.PostSearchResults
|
||||
paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset)
|
||||
includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels
|
||||
|
|
@ -1317,10 +1317,14 @@ func (a *App) SearchPostsForUser(c *request.Context, terms string, userID string
|
|||
finalParamsList := []*model.SearchParams{}
|
||||
|
||||
for _, params := range paramsList {
|
||||
params.Modifier = modifier
|
||||
params.OrTerms = isOrSearch
|
||||
params.IncludeDeletedChannels = includeDeleted
|
||||
// Don't allow users to search for "*"
|
||||
if params.Terms != "*" {
|
||||
// TODO: we have to send channel ids
|
||||
// from the front-end. Otherwise it's not possible to distinguish
|
||||
// from just the channel name at a cross-team level.
|
||||
// Convert channel names to channel IDs
|
||||
params.InChannels = a.convertChannelNamesToChannelIds(c, params.InChannels, userID, teamID, includeDeletedChannels)
|
||||
params.ExcludedChannels = a.convertChannelNamesToChannelIds(c, params.ExcludedChannels, userID, teamID, includeDeletedChannels)
|
||||
|
|
@ -1352,6 +1356,15 @@ func (a *App) SearchPostsForUser(c *request.Context, terms string, userID string
|
|||
return postSearchResults, nil
|
||||
}
|
||||
|
||||
func (a *App) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, *model.AppError) {
|
||||
searchParams, nErr := a.Srv().Store.Post().GetRecentSearchesForUser(userID)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("GetRecentSearchesForUser", "app.recent_searches.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return searchParams, nil
|
||||
}
|
||||
|
||||
func (a *App) GetFileInfosForPostWithMigration(postID string) ([]*model.FileInfo, *model.AppError) {
|
||||
|
||||
pchan := make(chan store.StoreResult, 1)
|
||||
|
|
|
|||
|
|
@ -1423,7 +1423,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
|
||||
page := 0
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{
|
||||
|
|
@ -1443,7 +1443,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
|
||||
page := 1
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{}, results.Order)
|
||||
|
|
@ -1473,7 +1473,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, resultsPage, results.Order)
|
||||
|
|
@ -1501,7 +1501,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, resultsPage, results.Order)
|
||||
|
|
@ -1525,7 +1525,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{
|
||||
|
|
@ -1557,7 +1557,7 @@ func TestSearchPostsForUser(t *testing.T) {
|
|||
th.App.Srv().SearchEngine.ElasticsearchEngine = nil
|
||||
}()
|
||||
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
||||
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage, model.ModifierMessages)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{}, results.Order)
|
||||
|
|
|
|||
1
db/migrations/mysql/000084_recent_searches.down.sql
Normal file
1
db/migrations/mysql/000084_recent_searches.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS RecentSearches;
|
||||
7
db/migrations/mysql/000084_recent_searches.up.sql
Normal file
7
db/migrations/mysql/000084_recent_searches.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS RecentSearches (
|
||||
UserId CHAR(26),
|
||||
SearchPointer int,
|
||||
Query json,
|
||||
CreateAt bigint NOT NULL,
|
||||
PRIMARY KEY (UserId, SearchPointer)
|
||||
);
|
||||
1
db/migrations/postgres/000084_recent_searches.down.sql
Normal file
1
db/migrations/postgres/000084_recent_searches.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS recentsearches;
|
||||
7
db/migrations/postgres/000084_recent_searches.up.sql
Normal file
7
db/migrations/postgres/000084_recent_searches.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS recentsearches (
|
||||
userid CHAR(26),
|
||||
searchpointer int,
|
||||
query jsonb,
|
||||
createat bigint NOT NULL,
|
||||
PRIMARY KEY (userid, searchpointer)
|
||||
);
|
||||
|
|
@ -5939,6 +5939,10 @@
|
|||
"id": "app.reaction.save.save.app_error",
|
||||
"translation": "Unable to save reaction."
|
||||
},
|
||||
{
|
||||
"id": "app.recent_searches.app_error",
|
||||
"translation": "Error fetching recent searches"
|
||||
},
|
||||
{
|
||||
"id": "app.recover.delete.app_error",
|
||||
"translation": "Unable to delete token."
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ const (
|
|||
PostPropsPreviewedPost = "previewed_post"
|
||||
)
|
||||
|
||||
const (
|
||||
ModifierMessages string = "messages"
|
||||
ModifierFiles string = "files"
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
|
|
@ -127,6 +132,7 @@ type SearchParameter struct {
|
|||
Page *int `json:"page"`
|
||||
PerPage *int `json:"per_page"`
|
||||
IncludeDeletedChannels *bool `json:"include_deleted_channels"`
|
||||
Modifier *string `json:"modifier"` // whether it's messages or file
|
||||
}
|
||||
|
||||
type AnalyticsPostCountsOptions struct {
|
||||
|
|
|
|||
|
|
@ -14,26 +14,27 @@ var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`)
|
|||
var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`)
|
||||
|
||||
type SearchParams struct {
|
||||
Terms string
|
||||
ExcludedTerms string
|
||||
IsHashtag bool
|
||||
InChannels []string
|
||||
ExcludedChannels []string
|
||||
FromUsers []string
|
||||
ExcludedUsers []string
|
||||
AfterDate string
|
||||
ExcludedAfterDate string
|
||||
BeforeDate string
|
||||
ExcludedBeforeDate string
|
||||
Extensions []string
|
||||
ExcludedExtensions []string
|
||||
OnDate string
|
||||
ExcludedDate string
|
||||
OrTerms bool
|
||||
IncludeDeletedChannels bool
|
||||
TimeZoneOffset int
|
||||
Terms string `json:"terms,omitempty"`
|
||||
ExcludedTerms string `json:"excluded_terms,omitempty"`
|
||||
IsHashtag bool `json:"ishashtag,omitempty"`
|
||||
InChannels []string `json:"in_channels,omitempty"`
|
||||
ExcludedChannels []string `json:"excluded_channels,omitempty"`
|
||||
FromUsers []string `json:"from_users,omitempty"`
|
||||
ExcludedUsers []string `json:"excluded_users,omitempty"`
|
||||
AfterDate string `json:"after_date,omitempty"`
|
||||
ExcludedAfterDate string `json:"excluded_after_date,omitempty"`
|
||||
BeforeDate string `json:"before_date,omitempty"`
|
||||
ExcludedBeforeDate string `json:"excluded_before_date,omitempty"`
|
||||
Extensions []string `json:"extensions,omitempty"`
|
||||
ExcludedExtensions []string `json:"excluded_extensions,omitempty"`
|
||||
OnDate string `json:"on_date,omitempty"`
|
||||
ExcludedDate string `json:"excluded_date,omitempty"`
|
||||
OrTerms bool `json:"or_terms,omitempty"`
|
||||
IncludeDeletedChannels bool `json:"include_deleted_channels,omitempty"`
|
||||
TimeZoneOffset int `json:"timezone_offset,omitempty"`
|
||||
// True if this search doesn't originate from a "current user".
|
||||
SearchWithoutUserId bool
|
||||
SearchWithoutUserId bool `json:"search_without_userid,omitempty"`
|
||||
Modifier string `json:"modifier"`
|
||||
}
|
||||
|
||||
// Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate
|
||||
|
|
|
|||
|
|
@ -5862,6 +5862,24 @@ func (s *OpenTracingLayerPostStore) GetPostsSinceForSync(options model.GetPostsS
|
|||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetRecentSearchesForUser")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostStore.GetRecentSearchesForUser(userID)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetRepliesForExport")
|
||||
|
|
@ -5929,6 +5947,24 @@ func (s *OpenTracingLayerPostStore) InvalidateLastPostTimeCache(channelID string
|
|||
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) LogRecentSearch(userID string, searchQuery []byte, createAt int64) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.LogRecentSearch")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.PostStore.LogRecentSearch(userID, searchQuery, createAt)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) Overwrite(post *model.Post) (*model.Post, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Overwrite")
|
||||
|
|
|
|||
|
|
@ -6637,6 +6637,27 @@ func (s *RetryLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceFo
|
|||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostStore.GetRecentSearchesForUser(userID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||
|
||||
tries := 0
|
||||
|
|
@ -6706,6 +6727,27 @@ func (s *RetryLayerPostStore) InvalidateLastPostTimeCache(channelID string) {
|
|||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) LogRecentSearch(userID string, searchQuery []byte, createAt int64) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.PostStore.LogRecentSearch(userID, searchQuery, createAt)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) Overwrite(post *model.Post) (*model.Post, error) {
|
||||
|
||||
tries := 0
|
||||
|
|
|
|||
|
|
@ -1522,11 +1522,19 @@ func (s SqlChannelStore) GetByNameIncludeDeleted(teamId string, name string, all
|
|||
}
|
||||
|
||||
func (s SqlChannelStore) getByName(teamId string, name string, includeDeleted bool, allowFromCache bool) (*model.Channel, error) {
|
||||
var query string
|
||||
if includeDeleted {
|
||||
query = "SELECT * FROM Channels WHERE (TeamId = ? OR TeamId = '') AND Name = ?"
|
||||
} else {
|
||||
query = "SELECT * FROM Channels WHERE (TeamId = ? OR TeamId = '') AND Name = ? AND DeleteAt = 0"
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Name": name})
|
||||
|
||||
if !includeDeleted {
|
||||
query = query.Where(sq.Eq{"DeleteAt": 0})
|
||||
}
|
||||
if teamId != "" {
|
||||
query = query.Where(sq.Or{
|
||||
sq.Eq{"TeamId": teamId},
|
||||
sq.Eq{"TeamId": ""},
|
||||
})
|
||||
}
|
||||
channel := model.Channel{}
|
||||
|
||||
|
|
@ -1543,7 +1551,12 @@ func (s SqlChannelStore) getByName(teamId string, name string, includeDeleted bo
|
|||
}
|
||||
}
|
||||
|
||||
if err := s.GetReplicaX().Get(&channel, query, teamId, name); err != nil {
|
||||
queryStr, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "getByName_tosql")
|
||||
}
|
||||
|
||||
if err := s.GetReplicaX().Get(&channel, queryStr, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("Channel", fmt.Sprintf("TeamId=%s&Name=%s", teamId, name))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package sqlstore
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
|
@ -507,12 +508,33 @@ func (fs SqlFileInfoStore) Search(paramsList []*model.SearchParams, userId, team
|
|||
LeftJoin("Posts as P ON FileInfo.PostId=P.Id").
|
||||
LeftJoin("Channels as C ON C.Id=P.ChannelId").
|
||||
LeftJoin("ChannelMembers as CM ON C.Id=CM.ChannelId").
|
||||
Where(sq.Or{sq.Eq{"C.TeamId": teamId}, sq.Eq{"C.TeamId": ""}}).
|
||||
Where(sq.Eq{"FileInfo.DeleteAt": 0}).
|
||||
OrderBy("FileInfo.CreateAt DESC").
|
||||
Limit(100)
|
||||
|
||||
if teamId != "" {
|
||||
query = query.Where(sq.Or{
|
||||
sq.Eq{"C.TeamId": teamId},
|
||||
sq.Eq{"C.TeamId": ""},
|
||||
})
|
||||
}
|
||||
|
||||
now := model.GetMillis()
|
||||
for _, params := range paramsList {
|
||||
if params.Modifier == model.ModifierFiles {
|
||||
// Deliberately keeping non-alphanumeric characters to
|
||||
// prevent surprises in UI.
|
||||
buf, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = fs.stores.post.LogRecentSearch(userId, buf, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
params.Terms = removeNonAlphaNumericUnquotedTerms(params.Terms, " ")
|
||||
|
||||
if !params.IncludeDeletedChannels {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ package sqlstore
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -1802,11 +1804,13 @@ func (s *SqlPostStore) buildSearchPostFilterClause(teamID string, fromUsers []st
|
|||
}
|
||||
|
||||
// Sub-query builder.
|
||||
sb := s.getSubQueryBuilder().Select("Id").From("Users, TeamMembers").Where(
|
||||
sq.And{
|
||||
sq.Eq{"TeamMembers.TeamId": teamID},
|
||||
sq.Expr("Users.Id = TeamMembers.UserId"),
|
||||
})
|
||||
sb := s.getSubQueryBuilder().
|
||||
Select("Id").
|
||||
From("Users, TeamMembers").
|
||||
Where(sq.Expr("Users.Id = TeamMembers.UserId"))
|
||||
if teamID != "" {
|
||||
sb = sb.Where(sq.Eq{"TeamMembers.TeamId": teamID})
|
||||
}
|
||||
sb = s.buildSearchUserFilterClause(fromUsers, false, userByUsername, sb)
|
||||
sb = s.buildSearchUserFilterClause(excludedUsers, true, userByUsername, sb)
|
||||
subQuery, subQueryArgs, err := sb.ToSql()
|
||||
|
|
@ -2522,7 +2526,7 @@ func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId str
|
|||
}
|
||||
|
||||
//nolint:unparam
|
||||
func (s *SqlPostStore) SearchPostsForUser(paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) {
|
||||
func (s *SqlPostStore) SearchPostsForUser(paramsList []*model.SearchParams, userID, teamId string, page, perPage int) (*model.PostSearchResults, error) {
|
||||
// Since we don't support paging for DB search, we just return nothing for later pages
|
||||
if page > 0 {
|
||||
return model.MakePostSearchResults(model.NewPostList(), nil), nil
|
||||
|
|
@ -2532,11 +2536,22 @@ func (s *SqlPostStore) SearchPostsForUser(paramsList []*model.SearchParams, user
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
now := model.GetMillis()
|
||||
pchan := make(chan store.StoreResult, len(paramsList))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, params := range paramsList {
|
||||
// Deliberately keeping non-alphanumeric characters to
|
||||
// prevent surprises in UI.
|
||||
buf, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.LogRecentSearch(userID, buf, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove any unquoted term that contains only non-alphanumeric chars
|
||||
// ex: abcd "**" && abc >> abcd "**" abc
|
||||
params.Terms = removeNonAlphaNumericUnquotedTerms(params.Terms, " ")
|
||||
|
|
@ -2545,7 +2560,7 @@ func (s *SqlPostStore) SearchPostsForUser(paramsList []*model.SearchParams, user
|
|||
|
||||
go func(params *model.SearchParams) {
|
||||
defer wg.Done()
|
||||
postList, err := s.search(teamId, userId, params, false, false)
|
||||
postList, err := s.search(teamId, userID, params, false, false)
|
||||
pchan <- store.StoreResult{Data: postList, NErr: err}
|
||||
}(params)
|
||||
}
|
||||
|
|
@ -2568,6 +2583,103 @@ func (s *SqlPostStore) SearchPostsForUser(paramsList []*model.SearchParams, user
|
|||
return model.MakePostSearchResults(posts, nil), nil
|
||||
}
|
||||
|
||||
const lastSearchesLimit = 5
|
||||
|
||||
func (s *SqlPostStore) LogRecentSearch(userID string, searchQuery []byte, createAt int64) error {
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "begin_transaction")
|
||||
}
|
||||
|
||||
defer finalizeTransactionX(transaction)
|
||||
|
||||
var lastSearchPointer int
|
||||
var queryStr string
|
||||
// get search_pointer
|
||||
// We coalesce to -1 because we want to start from 0
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
queryStr = `SELECT COALESCE((props->>'last_search_pointer')::integer, -1)
|
||||
FROM Users
|
||||
WHERE Id=?`
|
||||
} else {
|
||||
queryStr = `SELECT COALESCE(CAST(JSON_EXTRACT(Props, '$.last_search_pointer') as unsigned), -1)
|
||||
FROM Users
|
||||
WHERE Id=?`
|
||||
}
|
||||
err = transaction.Get(&lastSearchPointer, queryStr, userID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to find last_search_pointer for user=%s", userID)
|
||||
}
|
||||
|
||||
// (ptr+1)%lastSearchesLimit
|
||||
lastSearchPointer = (lastSearchPointer + 1) % lastSearchesLimit
|
||||
|
||||
if s.IsBinaryParamEnabled() {
|
||||
searchQuery = AppendBinaryFlag(searchQuery)
|
||||
}
|
||||
|
||||
// insert at pointer
|
||||
query := s.getQueryBuilder().
|
||||
Insert("RecentSearches").
|
||||
Columns("UserId", "SearchPointer", "Query", "CreateAt").
|
||||
Values(userID, lastSearchPointer, searchQuery, createAt)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
query = query.SuffixExpr(sq.Expr("ON CONFLICT (userid, searchpointer) DO UPDATE SET Query = ?, CreateAt = ?", searchQuery, createAt))
|
||||
} else {
|
||||
query = query.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Query = ?, CreateAt = ?", searchQuery, createAt))
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "log_recent_search_tosql")
|
||||
}
|
||||
|
||||
if _, err2 := transaction.Exec(queryString, args...); err2 != nil {
|
||||
return errors.Wrapf(err2, "failed to upsert recent_search for user=%s", userID)
|
||||
}
|
||||
|
||||
// write ptr on users prop
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
_, err = transaction.Exec(`UPDATE Users
|
||||
SET Props = jsonb_set(Props, $1, $2)
|
||||
WHERE Id = $3`, jsonKeyPath("last_search_pointer"), jsonStringVal(strconv.Itoa(lastSearchPointer)), userID)
|
||||
} else {
|
||||
_, err = transaction.Exec(`UPDATE Users
|
||||
SET Props = JSON_SET(Props, ?, ?)
|
||||
WHERE Id = ?`, "$.last_search_pointer", strconv.Itoa(lastSearchPointer), userID)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update last_search_pointer for user=%s", userID)
|
||||
}
|
||||
|
||||
if err2 := transaction.Commit(); err2 != nil {
|
||||
return errors.Wrap(err2, "commit_transaction")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error) {
|
||||
params := [][]byte{}
|
||||
err := s.GetReplicaX().Select(¶ms, `SELECT query
|
||||
FROM RecentSearches
|
||||
WHERE UserId=?
|
||||
ORDER BY CreateAt DESC`, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get recent searches for user=%s", userID)
|
||||
}
|
||||
|
||||
res := make([]*model.SearchParams, len(params))
|
||||
for i, param := range params {
|
||||
err = json.Unmarshal(param, &res[i])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal recent search query for user=%s", userID)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetOldestEntityCreationTime() (int64, error) {
|
||||
query := s.getQueryBuilder().Select("MIN(min_createat) min_createat").
|
||||
Suffix(`FROM (
|
||||
|
|
|
|||
|
|
@ -366,6 +366,8 @@ type PostStore interface {
|
|||
GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error)
|
||||
GetDirectPostParentsForExportAfter(limit int, afterID string) ([]*model.DirectPostForExport, error)
|
||||
SearchPostsForUser(paramsList []*model.SearchParams, userID, teamID string, page, perPage int) (*model.PostSearchResults, error)
|
||||
GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error)
|
||||
LogRecentSearch(userID string, searchQuery []byte, createAt int64) error
|
||||
GetOldestEntityCreationTime() (int64, error)
|
||||
HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error)
|
||||
GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error)
|
||||
|
|
|
|||
|
|
@ -610,6 +610,29 @@ func (_m *PostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOpti
|
|||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// GetRecentSearchesForUser provides a mock function with given fields: userID
|
||||
func (_m *PostStore) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error) {
|
||||
ret := _m.Called(userID)
|
||||
|
||||
var r0 []*model.SearchParams
|
||||
if rf, ok := ret.Get(0).(func(string) []*model.SearchParams); ok {
|
||||
r0 = rf(userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.SearchParams)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(userID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRepliesForExport provides a mock function with given fields: parentID
|
||||
func (_m *PostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||
ret := _m.Called(parentID)
|
||||
|
|
@ -682,6 +705,20 @@ func (_m *PostStore) InvalidateLastPostTimeCache(channelID string) {
|
|||
_m.Called(channelID)
|
||||
}
|
||||
|
||||
// LogRecentSearch provides a mock function with given fields: userID, searchQuery, createAt
|
||||
func (_m *PostStore) LogRecentSearch(userID string, searchQuery []byte, createAt int64) error {
|
||||
ret := _m.Called(userID, searchQuery, createAt)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, []byte, int64) error); ok {
|
||||
r0 = rf(userID, searchQuery, createAt)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Overwrite provides a mock function with given fields: post
|
||||
func (_m *PostStore) Overwrite(post *model.Post) (*model.Post, error) {
|
||||
ret := _m.Called(post)
|
||||
|
|
|
|||
|
|
@ -5306,6 +5306,22 @@ func (s *TimerLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceFo
|
|||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) GetRecentSearchesForUser(userID string) ([]*model.SearchParams, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
result, err := s.PostStore.GetRecentSearchesForUser(userID)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetRecentSearchesForUser", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
|
|
@ -5369,6 +5385,22 @@ func (s *TimerLayerPostStore) InvalidateLastPostTimeCache(channelID string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) LogRecentSearch(userID string, searchQuery []byte, createAt int64) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.PostStore.LogRecentSearch(userID, searchQuery, createAt)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.LogRecentSearch", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) Overwrite(post *model.Post) (*model.Post, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue