Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3-bug-fixes/pr-2968
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix pagination in team user search (make search key unique)
18 changes: 15 additions & 3 deletions services/brig/src/Brig/User/Search/TeamUserSearch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,24 @@ teamUserSearchQuery tid mbSearchText _mRoleFilter mSortBy mSortOrder =
mbQStr
)
teamFilter
( maybe
-- in combination with pagination a non-unique search specification can lead to missing results
-- therefore we use the unique `_doc` value as a tie breaker
-- - see https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-sort.html for details on `_doc`
-- - see https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-search-after.html for details on pagination and tie breaker
-- in the latter article it "is advised to duplicate (client side or [...]) the content of the _id field
-- in another field that has doc value enabled and to use this new field as the tiebreaker for the sort"
-- so alternatively we could use the user ID as a tie breaker, but this would require a change in the index mapping
(sorting ++ sortingTieBreaker)
where
sorting :: [ES.DefaultSort]
sorting =
maybe
[defaultSort SortByCreatedAt SortOrderDesc | isNothing mbQStr]
(\tuSortBy -> [defaultSort tuSortBy (fromMaybe SortOrderAsc mSortOrder)])
mSortBy
)
where
sortingTieBreaker :: [ES.DefaultSort]
sortingTieBreaker = [ES.DefaultSort (ES.FieldName "_doc") ES.Ascending Nothing Nothing Nothing Nothing]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but this looks like something different from what the docs you are linking suggest, and it may be prohibitively inefficient.

Can you try the _id trick that you were about to try? That may require an ES migration, but we've done those before, and ES doesn't server as a source of truth and in the worst case can recover from Cassandra. So I'm not too worried.


mbQStr :: Maybe Text
mbQStr =
case mbSearchText of
Expand Down
17 changes: 11 additions & 6 deletions services/brig/test/integration/API/TeamUserSearch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ testSort brig = do
let sortByProperty' :: (TestConstraints m, Ord a) => TeamUserSearchSortBy -> (User -> a) -> TeamUserSearchSortOrder -> m ()
sortByProperty' = sortByProperty tid users ownerId
for_ [SortOrderAsc, SortOrderDesc] $ \sortOrder -> do
-- FUTUREWORK: Test SortByRole when role is avaible in index
-- FUTUREWORK: Test SortByRole when role is available in index
sortByProperty' SortByEmail userEmail sortOrder
sortByProperty' SortByName userDisplayName sortOrder
sortByProperty' SortByHandle (fmap fromHandle . userHandle) sortOrder
Expand Down Expand Up @@ -144,12 +144,17 @@ testEmptyQuerySortedWithPagination :: TestConstraints m => Brig -> m ()
testEmptyQuerySortedWithPagination brig = do
(tid, userId -> ownerId, _) <- createPopulatedBindingTeamWithNamesAndHandles brig 20
refreshIndex brig
searchResultFirst10 <- executeTeamUserSearchWithMaybeState brig tid ownerId (Just "") Nothing Nothing Nothing (Just $ unsafeRange 10) Nothing
searchResultLast11 <- executeTeamUserSearchWithMaybeState brig tid ownerId (Just "") Nothing Nothing Nothing Nothing (searchPagingState searchResultFirst10)
let teamUserSearch mPs = executeTeamUserSearchWithMaybeState brig tid ownerId (Just "") Nothing (Just SortByRole) (Just SortOrderAsc) (Just $ unsafeRange 10) mPs
searchResultFirst10 <- teamUserSearch Nothing
searchResultNext10 <- teamUserSearch (searchPagingState searchResultFirst10)
searchResultLast1 <- teamUserSearch (searchPagingState searchResultNext10)
liftIO $ do
searchReturned searchResultFirst10 @?= 10
searchFound searchResultFirst10 @?= 21
searchHasMore searchResultFirst10 @?= Just True
searchReturned searchResultLast11 @?= 11
searchFound searchResultLast11 @?= 21
searchHasMore searchResultLast11 @?= Just False
searchReturned searchResultNext10 @?= 10
searchFound searchResultNext10 @?= 21
searchHasMore searchResultNext10 @?= Just True
searchReturned searchResultLast1 @?= 1
searchFound searchResultLast1 @?= 21
searchHasMore searchResultLast1 @?= Just False