Skip to content

Commit

Permalink
Navigational links as top hits (duckduckgo#5374)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1174433894299346/1208855071397751/f

### Description
Ensure that Navigational links can be shown as Top Hits

### Steps to test this PR
Check https://app.asana.com/0/1174433894299346/1208909865959915/f for
different scenarios
  • Loading branch information
malmstein authored Dec 12, 2024
1 parent db85ca9 commit f0e5e1b
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,6 @@ class BrowserTabViewModelTest {
mockAutoCompleteRepository,
mockTabRepository,
mockUserStageStore,
coroutineRule.testDispatcherProvider,
mockAutocompleteTabsFeature,
)
val fireproofWebsiteRepositoryImpl = FireproofWebsiteRepositoryImpl(
Expand Down Expand Up @@ -2368,9 +2367,9 @@ class BrowserTabViewModelTest {
fun whenSearchSuggestionSubmittedWithBookmarksThenAutoCompleteSearchSelectionPixelSent() = runTest {
whenever(mockSavedSitesRepository.hasBookmarks()).thenReturn(true)
whenever(mockNavigationHistory.hasHistory()).thenReturn(false)
val suggestions = listOf(AutoCompleteSearchSuggestion("", false), AutoCompleteBookmarkSuggestion("", "", ""))
val suggestions = listOf(AutoCompleteSearchSuggestion("", false, false), AutoCompleteBookmarkSuggestion("", "", ""))
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteResult("", suggestions))
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false))
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false, false))

val argumentCaptor = argumentCaptor<Map<String, String>>()
verify(mockPixel).fire(eq(AppPixelName.AUTOCOMPLETE_SEARCH_PHRASE_SELECTION), argumentCaptor.capture(), any(), any())
Expand All @@ -2384,7 +2383,7 @@ class BrowserTabViewModelTest {
whenever(mockSavedSitesRepository.hasBookmarks()).thenReturn(false)
whenever(mockNavigationHistory.hasHistory()).thenReturn(false)
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteResult("", emptyList()))
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false))
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false, false))

val argumentCaptor = argumentCaptor<Map<String, String>>()
verify(mockPixel).fire(eq(AppPixelName.AUTOCOMPLETE_SEARCH_PHRASE_SELECTION), argumentCaptor.capture(), any(), any())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import com.duckduckgo.app.tabs.model.TabEntity
import com.duckduckgo.app.tabs.model.TabRepository
import com.duckduckgo.common.utils.AppUrl
import com.duckduckgo.common.utils.AppUrl.Url
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.common.utils.UrlScheme
import com.duckduckgo.common.utils.baseHost
import com.duckduckgo.common.utils.toStringDropScheme
Expand Down Expand Up @@ -80,6 +79,7 @@ interface AutoComplete {
data class AutoCompleteSearchSuggestion(
override val phrase: String,
val isUrl: Boolean,
val isAllowedInTopHits: Boolean,
) : AutoCompleteSuggestion(phrase)

data class AutoCompleteDefaultSuggestion(
Expand Down Expand Up @@ -134,7 +134,6 @@ class AutoCompleteApi @Inject constructor(
private val autoCompleteRepository: AutoCompleteRepository,
private val tabRepository: TabRepository,
private val userStageStore: UserStageStore,
private val dispatcherProvider: DispatcherProvider,
private val autocompleteTabsFeature: AutocompleteTabsFeature,
) : AutoComplete {

Expand All @@ -155,7 +154,8 @@ class AutoCompleteApi @Inject constructor(
val bookmarksFavoritesTabsAndHistory = combineBookmarksFavoritesTabsAndHistory(bookmarks, favorites, tabs, historyResults)
val topHits = getTopHits(bookmarksFavoritesTabsAndHistory, searchResults)
val filteredBookmarksFavoritesTabsAndHistory = filterBookmarksAndTabsAndHistory(bookmarksFavoritesTabsAndHistory, topHits)
val distinctSearchResults = getDistinctSearchResults(searchResults, topHits, filteredBookmarksFavoritesTabsAndHistory)
val middleSectionSearchResults = makeSearchResultsNotAllowedInTopHits(searchResults)
val distinctSearchResults = getDistinctSearchResults(middleSectionSearchResults, topHits, filteredBookmarksFavoritesTabsAndHistory)

(topHits + distinctSearchResults + filteredBookmarksFavoritesTabsAndHistory).distinctBy {
Pair(it.phrase, it::class.java)
Expand Down Expand Up @@ -193,11 +193,12 @@ class AutoCompleteApi @Inject constructor(
bookmarksAndFavoritesAndTabsAndHistory: List<AutoCompleteSuggestion>,
searchResults: List<AutoCompleteSearchSuggestion>,
): List<AutoCompleteSuggestion> {
return (searchResults + bookmarksAndFavoritesAndTabsAndHistory).filter {
return (bookmarksAndFavoritesAndTabsAndHistory + searchResults).filter {
when (it) {
is AutoCompleteHistorySearchSuggestion -> it.isAllowedInTopHits
is AutoCompleteHistorySuggestion -> it.isAllowedInTopHits
is AutoCompleteUrlSuggestion -> true
is AutoCompleteSearchSuggestion -> it.isAllowedInTopHits
else -> false
}
}.take(maximumNumberOfTopHits)
Expand All @@ -213,13 +214,24 @@ class AutoCompleteApi @Inject constructor(
.take(maxBottomSection)
}

private fun makeSearchResultsNotAllowedInTopHits(searchResults: List<AutoCompleteSearchSuggestion>): List<AutoCompleteSearchSuggestion> {
// we allow for search results to show navigational links if they are not favorites or bookmarks and not in top hits
return searchResults.map {
it.copy(
isAllowedInTopHits = false,
)
}
}

private fun getDistinctSearchResults(
searchResults: List<AutoCompleteSearchSuggestion>,
topHits: List<AutoCompleteSuggestion>,
filteredBookmarksAndTabsAndHistory: List<AutoCompleteSuggestion>,
): List<AutoCompleteSearchSuggestion> {
val distinctPhrases = (topHits + filteredBookmarksAndTabsAndHistory).distinctBy { it.phrase }.map { it.phrase }.toSet()
// we allow for navigational search results if they are not part of top hits
val distinctPhrases = (filteredBookmarksAndTabsAndHistory).distinctBy { it.phrase }.map { it.phrase }.toSet()
val distinctPairs = (topHits + filteredBookmarksAndTabsAndHistory).distinctBy { Pair(it.phrase, it::class.java) }.size

val maxSearchResults = maximumNumberOfSuggestions - distinctPairs
return searchResults.distinctBy { it.phrase }.filterNot { it.phrase in distinctPhrases }.take(maxSearchResults)
}
Expand Down Expand Up @@ -300,6 +312,7 @@ class AutoCompleteApi @Inject constructor(
val searchSuggestion = AutoCompleteSearchSuggestion(
phrase = rawResult.phrase.formatIfUrl(),
isUrl = rawResult.isNav ?: UriString.isWebUrl(rawResult.phrase),
isAllowedInTopHits = rawResult.isNav ?: UriString.isWebUrl(rawResult.phrase),
)
searchSuggestionsList.add(searchSuggestion)
}
Expand Down Expand Up @@ -422,6 +435,7 @@ class AutoCompleteApi @Inject constructor(
isAllowedInTopHits = isAllowedInTopHits(entry),
)
}

is VisitedSERP -> {
AutoCompleteHistorySearchSuggestion(
phrase = entry.query.formatIfUrl(),
Expand Down Expand Up @@ -484,7 +498,7 @@ class AutoCompleteApi @Inject constructor(
return this.toUri().toStringDropScheme().removePrefix("www.")
}

private data class RankedSuggestion<T : AutoCompleteSuggestion> (
private data class RankedSuggestion<T : AutoCompleteSuggestion>(
val suggestion: T,
val score: Int = DEFAULT_SCORE,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.A
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteHistoryRelatedSuggestion.AutoCompleteHistorySearchSuggestion
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteHistoryRelatedSuggestion.AutoCompleteHistorySuggestion
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteHistoryRelatedSuggestion.AutoCompleteInAppMessageSuggestion
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteUrlSuggestion
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteUrlSuggestion.AutoCompleteBookmarkSuggestion
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteUrlSuggestion.AutoCompleteSwitchToTabSuggestion
import com.duckduckgo.app.browser.autocomplete.AutoCompleteViewHolder.EmptySuggestionViewHolder
Expand Down Expand Up @@ -81,6 +82,7 @@ class BrowserAutoCompleteSuggestionsAdapter(
suggestions[position] is AutoCompleteInAppMessageSuggestion -> IN_APP_MESSAGE_TYPE
suggestions[position] is AutoCompleteDefaultSuggestion -> DEFAULT_TYPE
suggestions[position] is AutoCompleteSwitchToTabSuggestion -> SWITCH_TO_TAB_TYPE
suggestions[position] is AutoCompleteUrlSuggestion -> SWITCH_TO_TAB_TYPE
else -> SUGGESTION_TYPE
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,11 @@ sealed class AutoCompleteViewHolder(itemView: View) : RecyclerView.ViewHolder(it
editQueryImage.setImageResource(R.drawable.ic_autocomplete_down_20dp)
}

root.tag = SEARCH_ITEM
if (item.isAllowedInTopHits) {
root.tag = OTHER_ITEM
} else {
root.tag = SEARCH_ITEM
}
}
}

Expand Down
Loading

0 comments on commit f0e5e1b

Please sign in to comment.