Merge pull request #7536 from hayaksi1/fix/3434-mention-suggestion-ranking

Rank the mention suggestions by how well they match what was typed
This commit is contained in:
Benoit Marty
2026-08-28 10:39:24 +02:00
committed by GitHub
2 changed files with 56 additions and 1 deletions
@@ -115,9 +115,10 @@ class SuggestionsProcessor(
.filterUpTo(MAX_BATCH_ITEMS) { member ->
isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query)
}
.sortedBy { member -> matchRank(member, query) }
.map(ResolvedSuggestion::Member)
if ("room".contains(query) && canSendRoomMention) {
if ("room".startsWith(query, ignoreCase = true) && canSendRoomMention) {
listOf(ResolvedSuggestion.AtRoom) + matchingMembers
} else {
matchingMembers
@@ -125,6 +126,18 @@ class SuggestionsProcessor(
}
}
/**
* Members whose display name starts with the query come first, then members whose user id
* localpart starts with it, then the remaining matches on any other part of the name or id.
*/
private fun matchRank(member: RoomMember, query: String): Int {
return when {
member.displayName?.startsWith(query, ignoreCase = true) == true -> 0
member.userId.extractedDisplayName.startsWith(query, ignoreCase = true) -> 1
else -> 2
}
}
companion object {
// We don't want to retrieve thousands of members
private const val MAX_BATCH_ITEMS = 100
@@ -383,6 +383,48 @@ class SuggestionsProcessorTest {
)
}
@Test
fun `processing Mention suggestion returns the members matching the start of the query first`() = runTest {
val matchingOnAnyPart = aRoomMember(userId = UserId("@carol:server.org"), displayName = "Not Alice")
val matchingOnUserIdStart = aRoomMember(userId = UserId("@alicia:server.org"), displayName = null)
val matchingOnDisplayNameStart = aRoomMember(userId = UserId("@zoe:server.org"), displayName = "Alice")
val result = suggestionsProcessor.process(
suggestion = aMentionSuggestion("ali"),
roomMembersState = RoomMembersState.Ready(
persistentListOf(matchingOnAnyPart, matchingOnUserIdStart, matchingOnDisplayNameStart)
),
roomAliasSuggestions = emptyList(),
currentUserId = A_USER_ID_2,
canSendRoomMention = { false },
isInThread = false,
)
assertThat(result).isEqualTo(
listOf(
ResolvedSuggestion.Member(matchingOnDisplayNameStart),
ResolvedSuggestion.Member(matchingOnUserIdStart),
ResolvedSuggestion.Member(matchingOnAnyPart),
)
)
}
@Test
fun `processing Mention suggestion returns room ignoring the case of the query`() = runTest {
val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "alice")
val result = suggestionsProcessor.process(
suggestion = aMentionSuggestion("RO"),
roomMembersState = RoomMembersState.Ready(persistentListOf(aRoomMember)),
roomAliasSuggestions = emptyList(),
currentUserId = A_USER_ID_2,
canSendRoomMention = { true },
isInThread = false,
)
assertThat(result).isEqualTo(
listOf(
ResolvedSuggestion.AtRoom,
)
)
}
@Test
fun `processing Mention suggestion with return matching display name but not room if not allowed`() = runTest {
val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "ro")