Skip to content

Commit

Permalink
Issue mozilla-mobile#8690: Specify the frecency threshold for the fet…
Browse files Browse the repository at this point in the history
…ched top frecent sites
  • Loading branch information
gabrielluong committed Oct 29, 2020
1 parent 1d15599 commit d96e557
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 27 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object Versions {
const val disklrucache = "2.0.2"
const val leakcanary = "2.4"

const val mozilla_appservices = "63.0.0"
const val mozilla_appservices = "66.0.0"

const val mozilla_glean = "33.0.4"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package mozilla.components.browser.storage.memory

import androidx.annotation.VisibleForTesting
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.concept.storage.HistoryAutocompleteResult
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.concept.storage.PageObservation
Expand Down Expand Up @@ -94,7 +95,7 @@ class InMemoryHistoryStorage : HistoryStorage {
return visits
}

override suspend fun getTopFrecentSites(numItems: Int): List<TopFrecentSiteInfo> {
override suspend fun getTopFrecentSites(numItems: Int, frecencyThreshold: FrecencyThresholdOption): List<TopFrecentSiteInfo> {
throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import mozilla.components.concept.sync.SyncAuthInfo
import mozilla.components.concept.sync.SyncStatus
import mozilla.components.concept.sync.SyncableStore
import mozilla.components.concept.base.crash.CrashReporting
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.utils.segmentAwareDomainMatch
import org.json.JSONObject
Expand Down Expand Up @@ -100,9 +101,9 @@ open class PlacesHistoryStorage(
}
}

override suspend fun getTopFrecentSites(numItems: Int): List<TopFrecentSiteInfo> {
override suspend fun getTopFrecentSites(numItems: Int, frecencyThreshold: FrecencyThresholdOption): List<TopFrecentSiteInfo> {
return withContext(readScope.coroutineContext) {
places.reader().getTopFrecentSiteInfos(numItems).map { it.into() }
places.reader().getTopFrecentSiteInfos(numItems, frecencyThreshold.into()).map { it.into() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import mozilla.appservices.places.BookmarkTreeNode
import mozilla.appservices.places.SyncAuthInfo
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.concept.storage.TopFrecentSiteInfo
import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType
Expand All @@ -31,6 +32,14 @@ internal fun mozilla.components.concept.sync.SyncAuthInfo.into(): SyncAuthInfo {
)
}

/**
* Conversion from a generic [FrecencyThresholdOption] into its richer comrade within the 'places' lib.
*/
internal fun FrecencyThresholdOption.into() = when (this) {
FrecencyThresholdOption.NONE -> mozilla.appservices.places.FrecencyThresholdOption.NONE
FrecencyThresholdOption.SKIP_ONE_TIME_PAGES -> mozilla.appservices.places.FrecencyThresholdOption.SKIP_ONE_TIME_PAGES
}

/**
* Conversion from a generic [VisitType] into its richer comrade within the 'places' lib.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ interface HistoryStorage : Storage {
): List<VisitInfo>

/**
* Returns a list of the top frecent site infos limited by the given number of items
* sorted by most to least frecent.
* Returns a list of the top frecent site infos limited by the given number of items and
* frecency threshold sorted by most to least frecent.
*
* @param numItems the number of top frecent sites to return in the list.
* @param frecencyThreshold frecency threshold options for filtering visited sites based on
* their frecency score.
* @return a list of the [TopFrecentSiteInfo], most frecent first.
*/
suspend fun getTopFrecentSites(numItems: Int): List<TopFrecentSiteInfo>
suspend fun getTopFrecentSites(
numItems: Int,
frecencyThreshold: FrecencyThresholdOption
): List<TopFrecentSiteInfo>

/**
* Retrieves suggestions matching the [query].
Expand Down Expand Up @@ -146,8 +151,10 @@ data class PageVisit(
enum class RedirectSource {
// The page didn't redirect to another page.
NOT_A_SOURCE,

// The page temporarily redirected to another page.
TEMPORARY,

// The page permanently redirected to another page.
PERMANENT,
}
Expand All @@ -165,6 +172,18 @@ data class TopFrecentSiteInfo(
val title: String?
)

/**
* Frecency threshold options for fetching top frecent sites. Requests a page that was visited
* with a frecency score greater or equal to the [value].
*/
enum class FrecencyThresholdOption(val value: Long) {
/** Returns all visited pages. */
NONE(0),

/** Skip visited pages that were only visited once. */
SKIP_ONE_TIME_PAGES(101)
}

/**
* Information about a history visit.
*
Expand All @@ -187,8 +206,10 @@ data class VisitInfo(
enum class VisitType(val type: Int) {
// Internal visit type used for meta data updates. Doesn't represent an actual page visit.
NOT_A_VISIT(-1),

// User followed a link.
LINK(1),

// User typed a URL or selected it from the UI (autocomplete results, etc).
TYPED(2),
BOOKMARK(3),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.feature.top.sites.TopSite.Type.FRECENT
import mozilla.components.feature.top.sites.ext.hasUrl
import mozilla.components.feature.top.sites.ext.toTopSite
Expand Down Expand Up @@ -77,7 +78,8 @@ class DefaultTopSitesStorage(

override suspend fun getTopSites(
totalSites: Int,
includeFrecent: Boolean
includeFrecent: Boolean,
frecencyThreshold: FrecencyThresholdOption
): List<TopSite> {
val topSites = ArrayList<TopSite>()
val pinnedSites = pinnedSitesStorage.getPinnedSites().take(totalSites)
Expand All @@ -88,7 +90,7 @@ class DefaultTopSitesStorage(
// Get 'totalSites' sites for duplicate entries with
// existing pinned sites
val frecentSites = historyStorage
.getTopFrecentSites(totalSites)
.getTopFrecentSites(totalSites, frecencyThreshold)
.map { it.toTopSite() }
.filter { !pinnedSites.hasUrl(it.url) }
.take(numSitesRequired)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@

package mozilla.components.feature.top.sites

import mozilla.components.concept.storage.FrecencyThresholdOption

/**
* Top sites configuration to specify the number of top sites to display and
* whether or not to include top frecent sites in the top sites feature.
*
* @property totalSites A total number of sites that will be displayed.
* @property includeFrecent If true, includes frecent top site results.
* @property frecencyThreshold If [includeFrecent] is true, only visited sites with a frecency
* score above the given threshold will be returned. Defaults to 0 to return all visited pages
* sorted by most to least frecent.
*/
data class TopSitesConfig(
val totalSites: Int,
val includeFrecent: Boolean
val includeFrecent: Boolean,
val frecencyThreshold: FrecencyThresholdOption
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package mozilla.components.feature.top.sites

import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.support.base.observer.Observable

/**
Expand Down Expand Up @@ -41,8 +42,14 @@ interface TopSitesStorage : Observable<TopSitesStorage.Observer> {
*
* @param totalSites A total number of sites that will be retrieve if possible.
* @param includeFrecent If true, includes frecent top site results.
* @param frecencyThreshold If [includeFrecent] is true, frecency threshold options for
* filtering visited sites based on their frecency score.
*/
suspend fun getTopSites(totalSites: Int, includeFrecent: Boolean): List<TopSite>
suspend fun getTopSites(
totalSites: Int,
includeFrecent: Boolean,
frecencyThreshold: FrecencyThresholdOption
): List<TopSite>

/**
* Interface to be implemented by classes that want to observe the top site storage.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ internal class DefaultTopSitesPresenter(
val innerConfig = config.invoke()

scope.launch {
val topSites = storage.getTopSites(innerConfig.totalSites, innerConfig.includeFrecent)
val topSites = storage.getTopSites(
innerConfig.totalSites,
innerConfig.includeFrecent,
innerConfig.frecencyThreshold
)

scope.launch(Dispatchers.Main) {
view.displayTopSites(topSites)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.concept.storage.TopFrecentSiteInfo
import mozilla.components.feature.top.sites.ext.toTopSite
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import mozilla.components.support.test.whenever
import org.junit.Assert.assertEquals
Expand Down Expand Up @@ -135,22 +137,22 @@ class DefaultTopSitesStorageTest {
)
)

var topSites = defaultTopSitesStorage.getTopSites(0, false)
var topSites = defaultTopSitesStorage.getTopSites(0, false, FrecencyThresholdOption.NONE)
assertTrue(topSites.isEmpty())
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

topSites = defaultTopSitesStorage.getTopSites(1, false)
topSites = defaultTopSitesStorage.getTopSites(1, false, FrecencyThresholdOption.NONE)
assertEquals(1, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

topSites = defaultTopSitesStorage.getTopSites(2, false)
topSites = defaultTopSitesStorage.getTopSites(2, false, FrecencyThresholdOption.NONE)
assertEquals(2, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

topSites = defaultTopSitesStorage.getTopSites(5, false)
topSites = defaultTopSitesStorage.getTopSites(5, false, FrecencyThresholdOption.NONE)
assertEquals(2, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
Expand Down Expand Up @@ -188,23 +190,23 @@ class DefaultTopSitesStorageTest {
)

val frecentSite1 = TopFrecentSiteInfo("https://mozilla.com", "Mozilla")
whenever(historyStorage.getTopFrecentSites(anyInt())).thenReturn(listOf(frecentSite1))
whenever(historyStorage.getTopFrecentSites(anyInt(), any())).thenReturn(listOf(frecentSite1))

var topSites = defaultTopSitesStorage.getTopSites(0, true)
var topSites = defaultTopSitesStorage.getTopSites(0, true, FrecencyThresholdOption.NONE)
assertTrue(topSites.isEmpty())

topSites = defaultTopSitesStorage.getTopSites(1, true)
topSites = defaultTopSitesStorage.getTopSites(1, true, FrecencyThresholdOption.NONE)
assertEquals(1, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

topSites = defaultTopSitesStorage.getTopSites(2, true)
topSites = defaultTopSitesStorage.getTopSites(2, true, FrecencyThresholdOption.NONE)
assertEquals(2, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

topSites = defaultTopSitesStorage.getTopSites(5, true)
topSites = defaultTopSitesStorage.getTopSites(5, true, FrecencyThresholdOption.NONE)
assertEquals(3, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
Expand All @@ -213,15 +215,15 @@ class DefaultTopSitesStorageTest {

val frecentSite2 = TopFrecentSiteInfo("https://example.com", "Example")
val frecentSite3 = TopFrecentSiteInfo("https://getpocket.com", "Pocket")
whenever(historyStorage.getTopFrecentSites(anyInt())).thenReturn(
whenever(historyStorage.getTopFrecentSites(anyInt(), any())).thenReturn(
listOf(
frecentSite1,
frecentSite2,
frecentSite3
)
)

topSites = defaultTopSitesStorage.getTopSites(5, true)
topSites = defaultTopSitesStorage.getTopSites(5, true, FrecencyThresholdOption.NONE)
assertEquals(5, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
Expand All @@ -231,7 +233,7 @@ class DefaultTopSitesStorageTest {
assertEquals(defaultTopSitesStorage.cachedTopSites, topSites)

val frecentSite4 = TopFrecentSiteInfo("https://example2.com", "Example2")
whenever(historyStorage.getTopFrecentSites(anyInt())).thenReturn(
whenever(historyStorage.getTopFrecentSites(anyInt(), any())).thenReturn(
listOf(
frecentSite1,
frecentSite2,
Expand All @@ -240,7 +242,7 @@ class DefaultTopSitesStorageTest {
)
)

topSites = defaultTopSitesStorage.getTopSites(5, true)
topSites = defaultTopSitesStorage.getTopSites(5, true, FrecencyThresholdOption.NONE)
assertEquals(5, topSites.size)
assertEquals(defaultSite, topSites[0])
assertEquals(pinnedSite, topSites[1])
Expand Down Expand Up @@ -292,7 +294,7 @@ class DefaultTopSitesStorageTest {
val frecentSiteFirefox = TopFrecentSiteInfo("https://firefox.com", "Firefox")
val frecentSite1 = TopFrecentSiteInfo("https://getpocket.com", "Pocket")
val frecentSite2 = TopFrecentSiteInfo("https://www.example.com", "Example")
whenever(historyStorage.getTopFrecentSites(anyInt())).thenReturn(
whenever(historyStorage.getTopFrecentSites(anyInt(), any())).thenReturn(
listOf(
frecentSiteWithNoTitle,
frecentSiteFirefox,
Expand All @@ -301,7 +303,7 @@ class DefaultTopSitesStorageTest {
)
)

val topSites = defaultTopSitesStorage.getTopSites(5, true)
val topSites = defaultTopSitesStorage.getTopSites(5, true, FrecencyThresholdOption.NONE)
assertEquals(5, topSites.size)
assertEquals(defaultSiteFirefox, topSites[0])
assertEquals(pinnedSite1, topSites[1])
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ permalink: /changelog/

* **feature-top-sites**
* Added `RenameTopSiteUseCase` to rename pinned site entries. [#8751](https://github.com/mozilla-mobile/android-components/issues/8751)
* ⚠️ **This is a breaking change**: Adds a new parameter `frecencyThreshold` to `TopSitesConfig` and `TopSitesStorage.getTopSites` to specify the frecency threshold for the returned list of top frecent sites see [#8690](https://github.com/mozilla-mobile/android-components/issues/8690).

# 63.0.0

Expand Down

0 comments on commit d96e557

Please sign in to comment.