From d026e264cf4aaca2762960390ff96e4480100966 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Wed, 13 Nov 2024 18:07:11 +0100 Subject: [PATCH 01/22] Add a new column to TabsEntity to track the last access time --- .../java/com/duckduckgo/app/global/db/AppDatabase.kt | 9 ++++++++- .../java/com/duckduckgo/app/tabs/model/TabEntitiy.kt | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt index 99985184defe..056e36668607 100644 --- a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt +++ b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt @@ -72,7 +72,7 @@ import com.duckduckgo.savedsites.store.SavedSitesRelationsDao @Database( exportSchema = true, - version = 55, + version = 56, entities = [ TdsTracker::class, TdsEntity::class, @@ -674,6 +674,12 @@ class MigrationsProvider(val context: Context, val settingsDataStore: SettingsDa } } + private val MIGRATION_55_TO_56: Migration = object : Migration(55, 56) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `tabs` ADD COLUMN `lastAccessTime` TEXT") + } + } + /** * WARNING ⚠️ * This needs to happen because Room doesn't support UNIQUE (...) ON CONFLICT REPLACE when creating the bookmarks table. @@ -754,6 +760,7 @@ class MigrationsProvider(val context: Context, val settingsDataStore: SettingsDa MIGRATION_52_TO_53, MIGRATION_53_TO_54, MIGRATION_54_TO_55, + MIGRATION_55_TO_56 ) @Deprecated( diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt index 0aa047f4677e..b0c2a8927555 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt @@ -46,6 +46,7 @@ data class TabEntity( var tabPreviewFile: String? = null, var sourceTabId: String? = null, var deletable: Boolean = false, + var lastAccessTime: String? = null, ) val TabEntity.isBlank: Boolean From 42ca3b45d6379ee02519604d2908b46054e9df1f Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 17:07:25 +0100 Subject: [PATCH 02/22] Add methods to update the tab's last access time --- app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt | 8 ++++++++ .../com/duckduckgo/app/tabs/model/TabDataRepository.kt | 6 ++++++ .../java/com/duckduckgo/app/tabs/model/TabRepository.kt | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt index caa0cb4caa9f..3f4ac4088814 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData import androidx.room.* import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabSelectionEntity +import com.duckduckgo.common.utils.formatters.time.DatabaseDateFormatter.Companion.timestamp import com.duckduckgo.common.utils.swap import com.duckduckgo.di.scopes.AppScope import dagger.SingleInstanceIn @@ -165,6 +166,13 @@ abstract class TabsDao { return tabs().lastOrNull() } + + @Query("update tabs set lastAccessTime=:lastAccessTime where tabId=:tabId") + abstract fun updateTabLastAccess( + tabId: String, + lastAccessTime: String? = timestamp(), + ) + @Query("update tabs set url=:url, title=:title, viewed=:viewed where tabId=:tabId") abstract fun updateUrlAndTitle( tabId: String, diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index 0338a282c400..cd8c36d26cf9 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -228,6 +228,12 @@ class TabDataRepository @Inject constructor( } } + override suspend fun updateTabLastAccess(tabId: String) { + databaseExecutor().scheduleDirect { + tabsDao.updateTabLastAccess(tabId) + } + } + override fun retrieveSiteData(tabId: String): MutableLiveData { val storedData = siteData[tabId] if (storedData != null) { diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index 0e7e877c8020..1065ad778095 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -41,6 +41,8 @@ interface TabRepository { val liveSelectedTab: LiveData + val flowSelectedTab: Flow + val tabSwitcherData: Flow /** @@ -71,6 +73,8 @@ interface TabRepository { suspend fun updateTabPosition(from: Int, to: Int) + suspend fun updateTabLastAccess(tabId: String) + /** * @return record if it exists, otherwise a new one */ From 072a34536327c11638b33fdf050de7597abee0d6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 17:08:05 +0100 Subject: [PATCH 03/22] Update the last access time whenever a tab's activated --- .../main/java/com/duckduckgo/app/browser/BrowserActivity.kt | 2 ++ .../java/com/duckduckgo/app/browser/BrowserViewModel.kt | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt index f9a1c5199cbf..200ec9a02c5e 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt @@ -272,6 +272,8 @@ open class BrowserActivity : DuckDuckGoActivity() { lastActiveTabs.add(tab.tabId) + viewModel.onTabActivated(tab.tabId) + val fragment = supportFragmentManager.findFragmentByTag(tab.tabId) as? BrowserTabFragment if (fragment == null) { openNewTab(tab.tabId, tab.url, tab.skipHome, intent?.getBooleanExtra(LAUNCH_FROM_EXTERNAL_EXTRA, false) ?: false) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt index fcf92bc0d38f..b21bb639abd3 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt @@ -303,6 +303,12 @@ class BrowserViewModel @Inject constructor( } } } + + fun onTabActivated(tabId: String) { + viewModelScope.launch(dispatchers.io()) { + tabRepository.updateTabLastAccess(tabId) + } + } } /** From 1025c3ce6ea8d439ce97fd5639cb6c0261bca6a2 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 18:00:23 +0100 Subject: [PATCH 04/22] Use LocalDateTime as a last access time column type --- .../com/duckduckgo/app/global/db/AppDatabase.kt | 2 ++ .../java/com/duckduckgo/app/tabs/db/TabsDao.kt | 3 ++- .../com/duckduckgo/app/tabs/model/TabEntitiy.kt | 14 +++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt index 056e36668607..03aa2d3bb52f 100644 --- a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt +++ b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt @@ -56,6 +56,7 @@ import com.duckduckgo.app.statistics.store.PendingPixelDao import com.duckduckgo.app.survey.db.SurveyDao import com.duckduckgo.app.survey.model.Survey import com.duckduckgo.app.tabs.db.TabsDao +import com.duckduckgo.app.tabs.model.LocalDateTimeTypeConverter import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabSelectionEntity import com.duckduckgo.app.trackerdetection.db.* @@ -122,6 +123,7 @@ import com.duckduckgo.savedsites.store.SavedSitesRelationsDao LocationPermissionTypeConverter::class, QueryParamsTypeConverter::class, EntityTypeConverter::class, + LocalDateTimeTypeConverter::class, ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt index 3f4ac4088814..27e7e1bffafa 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt @@ -25,6 +25,7 @@ import com.duckduckgo.common.utils.swap import com.duckduckgo.di.scopes.AppScope import dagger.SingleInstanceIn import kotlinx.coroutines.flow.Flow +import java.time.LocalDateTime @Dao @SingleInstanceIn(AppScope::class) @@ -170,7 +171,7 @@ abstract class TabsDao { @Query("update tabs set lastAccessTime=:lastAccessTime where tabId=:tabId") abstract fun updateTabLastAccess( tabId: String, - lastAccessTime: String? = timestamp(), + lastAccessTime: LocalDateTime = LocalDateTime.now(), ) @Query("update tabs set url=:url, title=:title, viewed=:viewed where tabId=:tabId") diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt index b0c2a8927555..0fc514cf8c33 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt @@ -20,6 +20,9 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import com.duckduckgo.common.utils.formatters.time.DatabaseDateFormatter +import java.time.LocalDateTime @Entity( tableName = "tabs", @@ -46,8 +49,17 @@ data class TabEntity( var tabPreviewFile: String? = null, var sourceTabId: String? = null, var deletable: Boolean = false, - var lastAccessTime: String? = null, + var lastAccessTime: LocalDateTime? = null, ) val TabEntity.isBlank: Boolean get() = title == null && url == null + + +class LocalDateTimeTypeConverter { + @TypeConverter + fun convertForDb(date: LocalDateTime): String = DatabaseDateFormatter.timestamp(date) + + @TypeConverter + fun convertFromDb(value: String?): LocalDateTime? = value?.let { LocalDateTime.parse(it) } +} From e130a04ad67cb1e6efe49dab571610360cabf757 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 18:00:45 +0100 Subject: [PATCH 05/22] Remove unused code --- .../java/com/duckduckgo/app/tabs/model/TabRepository.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index 1065ad778095..8691584ed642 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -41,8 +41,6 @@ interface TabRepository { val liveSelectedTab: LiveData - val flowSelectedTab: Flow - val tabSwitcherData: Flow /** @@ -118,4 +116,10 @@ interface TabRepository { suspend fun setIsUserNew(isUserNew: Boolean) suspend fun setTabLayoutType(layoutType: LayoutType) + + suspend fun getOpenTabCount(): Int + + suspend fun getActiveTabCountWithinDays(days: Int): Int + + suspend fun getInactiveTabCountBetweenDays(startDay: Int, endDay: Int = -1) } From 61ef0a8f2834dafb6af74daa825b605c29accfd9 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 18:33:14 +0100 Subject: [PATCH 06/22] Make TabEntity properties immutable --- .../app/tabs/model/TabDataRepository.kt | 26 +++++++++++++++++-- .../duckduckgo/app/tabs/model/TabEntitiy.kt | 20 +++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index cd8c36d26cf9..cc247b037be1 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -47,6 +47,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import java.time.LocalDateTime +import java.time.ZoneOffset @SingleInstanceIn(AppScope::class) class TabDataRepository @Inject constructor( @@ -192,6 +194,27 @@ class TabDataRepository @Inject constructor( tabSwitcherDataStore.setTabLayoutType(layoutType) } + override suspend fun getOpenTabCount(): Int { + return tabsDao.tabs().size + } + + override suspend fun getActiveTabCountWithinDays(days: Long): Int { + val now = LocalDateTime.now(ZoneOffset.UTC) + val xDaysAgo = now.minusDays(days) + return tabsDao.tabs().filter { + it.lastAccessTime?.isAfter(xDaysAgo) == true + }.size + } + + override suspend fun getInactiveTabCountBetweenDays(daysStart: Long, daysEnd: Long): Int { + val now = LocalDateTime.now(ZoneOffset.UTC) + val start = now.minusDays(daysStart) + val end = now.minusDays(daysEnd) + return tabsDao.tabs().filter { + it.lastAccessTime?.isBefore(start) == true && it.lastAccessTime?.isAfter(end) == true + }.size + } + override suspend fun addNewTabAfterExistingTab( url: String?, tabId: String, @@ -341,8 +364,7 @@ class TabDataRepository @Inject constructor( Timber.w("Cannot find tab for tab ID") return@scheduleDirect } - tab.tabPreviewFile = fileName - tabsDao.updateTab(tab) + tabsDao.updateTab(tab.copy(tabPreviewFile = fileName)) Timber.i("Updated tab preview image. $tabId now uses $fileName") deleteOldPreviewImages(tabId, fileName) diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt index 0fc514cf8c33..a824abcf91bc 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt @@ -40,16 +40,16 @@ import java.time.LocalDateTime ], ) data class TabEntity( - @PrimaryKey var tabId: String, - var url: String? = null, - var title: String? = null, - var skipHome: Boolean = false, - var viewed: Boolean = true, - var position: Int, - var tabPreviewFile: String? = null, - var sourceTabId: String? = null, - var deletable: Boolean = false, - var lastAccessTime: LocalDateTime? = null, + @PrimaryKey val tabId: String, + val url: String? = null, + val title: String? = null, + val skipHome: Boolean = false, + val viewed: Boolean = true, + val position: Int, + val tabPreviewFile: String? = null, + val sourceTabId: String? = null, + val deletable: Boolean = false, + val lastAccessTime: LocalDateTime? = null, ) val TabEntity.isBlank: Boolean From 38767dcd2a63d4d6c6979b0e4dc629c73db848d5 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 18:33:55 +0100 Subject: [PATCH 07/22] Update the tab stats methods --- app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt | 3 ++- .../main/java/com/duckduckgo/app/tabs/model/TabRepository.kt | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt index 27e7e1bffafa..98f1f3320680 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt @@ -26,6 +26,7 @@ import com.duckduckgo.di.scopes.AppScope import dagger.SingleInstanceIn import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime +import java.time.ZoneOffset @Dao @SingleInstanceIn(AppScope::class) @@ -171,7 +172,7 @@ abstract class TabsDao { @Query("update tabs set lastAccessTime=:lastAccessTime where tabId=:tabId") abstract fun updateTabLastAccess( tabId: String, - lastAccessTime: LocalDateTime = LocalDateTime.now(), + lastAccessTime: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC), ) @Query("update tabs set url=:url, title=:title, viewed=:viewed where tabId=:tabId") diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index 8691584ed642..be3a9665d152 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -119,7 +119,7 @@ interface TabRepository { suspend fun getOpenTabCount(): Int - suspend fun getActiveTabCountWithinDays(days: Int): Int + suspend fun getActiveTabCountWithinDays(days: Long): Int - suspend fun getInactiveTabCountBetweenDays(startDay: Int, endDay: Int = -1) + suspend fun getInactiveTabCountBetweenDays(startDaysBack: Long, endDaysBack: Long = Long.MAX_VALUE): Int } From 88783b7d88f17102631490c486014fcfcb8c0485 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 20:41:10 +0100 Subject: [PATCH 08/22] Refactor the tab stats method signatures --- .../duckduckgo/app/tabs/model/TabDataRepository.kt | 14 +++++++------- .../com/duckduckgo/app/tabs/model/TabRepository.kt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index cc247b037be1..e81e3ee4061e 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -198,20 +198,20 @@ class TabDataRepository @Inject constructor( return tabsDao.tabs().size } - override suspend fun getActiveTabCountWithinDays(days: Long): Int { + override suspend fun getActiveTabCount(maxDaysSinceOpened: Long): Int { val now = LocalDateTime.now(ZoneOffset.UTC) - val xDaysAgo = now.minusDays(days) + val maxDaysAgo = now.minusDays(maxDaysSinceOpened) return tabsDao.tabs().filter { - it.lastAccessTime?.isAfter(xDaysAgo) == true + it.lastAccessTime?.isAfter(maxDaysAgo) == true }.size } - override suspend fun getInactiveTabCountBetweenDays(daysStart: Long, daysEnd: Long): Int { + override suspend fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long?): Int { val now = LocalDateTime.now(ZoneOffset.UTC) - val start = now.minusDays(daysStart) - val end = now.minusDays(daysEnd) + val start = now.minusDays(atLeastDaysOld) + val end = atMostDaysOld?.let { now.minusDays(it) } return tabsDao.tabs().filter { - it.lastAccessTime?.isBefore(start) == true && it.lastAccessTime?.isAfter(end) == true + it.lastAccessTime?.isBefore(start) == true && (end == null || it.lastAccessTime?.isAfter(end) == true) }.size } diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index be3a9665d152..5b52977b2cda 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -119,7 +119,7 @@ interface TabRepository { suspend fun getOpenTabCount(): Int - suspend fun getActiveTabCountWithinDays(days: Long): Int + suspend fun getActiveTabCount(maxDaysSinceOpened: Long): Int - suspend fun getInactiveTabCountBetweenDays(startDaysBack: Long, endDaysBack: Long = Long.MAX_VALUE): Int + suspend fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long? = null): Int } From 44c577a6de7e9748d754372d06a2218aab4f6411 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 14 Nov 2024 20:41:24 +0100 Subject: [PATCH 09/22] Add unit tests --- .../app/tabs/model/TabDataRepositoryTest.kt | 149 ++++++++++++++++++ .../duckduckgo/app/tabs/model/TabEntitiy.kt | 2 +- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt index 12f0256e3f3d..4f9379d3c93c 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt @@ -46,6 +46,8 @@ import org.junit.Assert.* import org.junit.Rule import org.junit.Test import org.mockito.kotlin.* +import java.time.LocalDateTime +import java.time.ZoneOffset class TabDataRepositoryTest { @@ -420,6 +422,153 @@ class TabDataRepositoryTest { job.cancel() } + @Test + fun getOpenTabCountReturnsCorrectCount() = runTest { + // Arrange: Add some tabs to the repository + whenever(mockDao.tabs()).thenReturn(listOf( + TabEntity(tabId = "tab1"), + TabEntity(tabId = "tab2"), + TabEntity(tabId = "tab3") + )) + val testee = tabDataRepository() + + // Act: Call getOpenTabCount() + val openTabCount = testee.getOpenTabCount() + + // Assert: Verify the count is correct + assertEquals(3, openTabCount) + } + + @Test + fun getActiveTabCountReturnsZeroWhenNoTabs() = runTest { + // Arrange: No tabs in the repository + whenever(mockDao.tabs()).thenReturn(emptyList()) + val testee = tabDataRepository() + + // Act: Call getActiveTabCount() + val inactiveTabCount = testee.getActiveTabCount(7) + + // Assert: Verify the count is zero + assertEquals(0, inactiveTabCount) + } + + @Test + fun getActiveTabCountReturnsZeroWhenNullTabs() = runTest { + // Arrange: Only null tabs in the repository + val tab1 = TabEntity(tabId = "tab1") + whenever(mockDao.tabs()).thenReturn(listOf(tab1)) + val testee = tabDataRepository() + + // Act: Call getActiveTabCount() + val inactiveTabCount = testee.getActiveTabCount(7) + + // Assert: Verify the count is zero + assertEquals(0, inactiveTabCount) + } + + @Test + fun getActiveTabCountReturnsCorrectCountWhenTabsYoungerThanSpecifiedDay() = runTest { + // Arrange: No tabs in the repository + val now = LocalDateTime.now(ZoneOffset.UTC) + val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(6)) + val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(8)) + val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(10)) + val tab4 = TabEntity(tabId = "tab4") + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) + val testee = tabDataRepository() + + // Act: Call getActiveTabCount() + val inactiveTabCount = testee.getActiveTabCount(9) + + // Assert: Verify the count is 2 + assertEquals(2, inactiveTabCount) + } + + @Test + fun getInactiveTabCountReturnsZeroWhenNoTabs() = runTest { + // Arrange: No tabs in the repository + whenever(mockDao.tabs()).thenReturn(emptyList()) + val testee = tabDataRepository() + + // Act: Call getInactiveTabCount() + val inactiveTabCount = testee.getInactiveTabCount(7, 12) + + // Assert: Verify the count is zero + assertEquals(0, inactiveTabCount) + } + + @Test + fun getInactiveTabCountReturnsCorrectCountWhenAllTabsOlderThanSpecifiedDay() = runTest { + // Arrange: Add some tabs with different last access times + val now = LocalDateTime.now(ZoneOffset.UTC) + val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(8)) + val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10)) + val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(9)) + val tab4 = TabEntity(tabId = "tab4") + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) + val testee = tabDataRepository() + + // Act: Call getInactiveTabCount() + val inactiveTabCount = testee.getInactiveTabCount(9) + + // Assert: Verify the count is correct + assertEquals(2, inactiveTabCount) + } + + @Test + fun getInactiveTabCountReturnsCorrectCountWhenAllTabsInactiveWithinRange() = runTest { + // Arrange: Add some tabs with different last access times + val now = LocalDateTime.now(ZoneOffset.UTC) + val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(8)) + val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10)) + val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(9)) + val tab4 = TabEntity(tabId = "tab4") + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) + val testee = tabDataRepository() + + // Act: Call getInactiveTabCount() + val inactiveTabCount = testee.getInactiveTabCount(7, 12) + + // Assert: Verify the count is correct + assertEquals(3, inactiveTabCount) + } + + @Test + fun getInactiveTabCountReturnsZeroWhenNoTabsInactiveWithinRange() = runTest { + // Arrange: Add some tabs with different last access times + val now = LocalDateTime.now(ZoneOffset.UTC) + val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(5)) + val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(6)) + val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(13)) + val tab4 = TabEntity(tabId = "tab4") + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3)) + val testee = tabDataRepository() + + // Act: Call getInactiveTabCount() + val inactiveTabCount = testee.getInactiveTabCount(7, 12) + + // Assert: Verify the count is zero + assertEquals(0, inactiveTabCount) + } + + @Test + fun getInactiveTabCountReturnsCorrectCountWhenSomeTabsInactiveWithinRange() = runTest { + // Arrange: Add some tabs with different last access times + val now = LocalDateTime.now(ZoneOffset.UTC) + val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(5)) + val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10)) + val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(15)) + val tab4 = TabEntity(tabId = "tab4") + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3)) + val testee = tabDataRepository() + + // Act: Call getInactiveTabCount() + val inactiveTabCount = testee.getInactiveTabCount(7, 12) + + // Assert: Verify the count is correct + assertEquals(1, inactiveTabCount) + } + private fun tabDataRepository( dao: TabsDao = mockDatabase(), entityLookup: EntityLookup = mock(), diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt index a824abcf91bc..3fc1a33e7db6 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt @@ -45,7 +45,7 @@ data class TabEntity( val title: String? = null, val skipHome: Boolean = false, val viewed: Boolean = true, - val position: Int, + val position: Int = 0, val tabPreviewFile: String? = null, val sourceTabId: String? = null, val deletable: Boolean = false, From 0a8b84ce2000638a6953bbf7d09f1eafca9cdcad Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 10:52:07 +0100 Subject: [PATCH 10/22] Make tab data repository methods non-suspendable --- .../com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt | 4 ++-- .../java/com/duckduckgo/app/tabs/model/TabDataRepository.kt | 6 +++--- .../java/com/duckduckgo/app/tabs/model/TabRepository.kt | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt index 4f9379d3c93c..98ca9d4b334a 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt @@ -541,7 +541,7 @@ class TabDataRepositoryTest { val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(6)) val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(13)) val tab4 = TabEntity(tabId = "tab4") - whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3)) + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() // Act: Call getInactiveTabCount() @@ -559,7 +559,7 @@ class TabDataRepositoryTest { val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10)) val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(15)) val tab4 = TabEntity(tabId = "tab4") - whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3)) + whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() // Act: Call getInactiveTabCount() diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index e81e3ee4061e..c61c013037df 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -194,11 +194,11 @@ class TabDataRepository @Inject constructor( tabSwitcherDataStore.setTabLayoutType(layoutType) } - override suspend fun getOpenTabCount(): Int { + override fun getOpenTabCount(): Int { return tabsDao.tabs().size } - override suspend fun getActiveTabCount(maxDaysSinceOpened: Long): Int { + override fun getActiveTabCount(maxDaysSinceOpened: Long): Int { val now = LocalDateTime.now(ZoneOffset.UTC) val maxDaysAgo = now.minusDays(maxDaysSinceOpened) return tabsDao.tabs().filter { @@ -206,7 +206,7 @@ class TabDataRepository @Inject constructor( }.size } - override suspend fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long?): Int { + override fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long?): Int { val now = LocalDateTime.now(ZoneOffset.UTC) val start = now.minusDays(atLeastDaysOld) val end = atMostDaysOld?.let { now.minusDays(it) } diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index 5b52977b2cda..d05735c736be 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -117,9 +117,9 @@ interface TabRepository { suspend fun setTabLayoutType(layoutType: LayoutType) - suspend fun getOpenTabCount(): Int + fun getOpenTabCount(): Int - suspend fun getActiveTabCount(maxDaysSinceOpened: Long): Int + fun getActiveTabCount(maxDaysSinceOpened: Long): Int - suspend fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long? = null): Int + fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long? = null): Int } From a7cb6347d6a24f2efb614afd7a3bac5bb3e0fae4 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 10:52:55 +0100 Subject: [PATCH 11/22] Add tab stats bucketing helper methods and tests --- .../app/tabs/store/TabStatsBucketingTest.kt | 309 ++++++++++++++++++ .../app/tabs/store/TabStatsBucketing.kt | 120 +++++++ 2 files changed, 429 insertions(+) create mode 100644 app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt create mode 100644 app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt new file mode 100644 index 000000000000..1a96e7cacae3 --- /dev/null +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -0,0 +1,309 @@ +package com.duckduckgo.app.tabs.store +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.duckduckgo.app.tabs.model.TabRepository +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.kotlin.whenever + +class DefaultTabStatsBucketingTest { + + private val tabRepository = mock() + + private lateinit var defaultTabStatsBucketing: DefaultTabStatsBucketing + + @Before + fun setup() { + defaultTabStatsBucketing = DefaultTabStatsBucketing(tabRepository) + } + + @Test + fun testGetTabCountBucketExactly1() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(1) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("1", result) + } + + @Test + fun testGetTabCountBucketZero() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(0) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("1", result) + } + + @Test + fun testGetTabCountBucket() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(5) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("2-5", result) + } + + @Test + fun testGetTabCountBucket6To10() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(8) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("6-10", result) + } + + @Test + fun testGetTabCountBucket11To20() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(11) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("11-20", result) + } + + @Test + fun testGetTabCountBucketMoreThan20() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(40) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("21-40", result) + } + + @Test + fun testGetTabCountBucket41To60() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(50) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("41-60", result) + } + + @Test + fun testGetTabCountBucket61To80() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(70) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("61-80", result) + } + + @Test + fun testGetTabCountBucket81To100() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(90) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("81-100", result) + } + + @Test + fun testGetTabCountBucket101To125() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(110) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("101-125", result) + } + + @Test + fun testGetTabCountBucket126To150() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(130) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("126-150", result) + } + + @Test + fun testGetTabCountBucket151To250() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(200) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("151-250", result) + } + + @Test + fun testGetTabCountBucket251To500() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(300) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("251-500", result) + } + + @Test + fun testGetTabCountBucketMaxValue() = runBlocking { + whenever(tabRepository.getOpenTabCount()).thenReturn(600) + val result = defaultTabStatsBucketing.getTabCountBucket() + assertEquals("501+", result) + } + + @Test + fun testGet7DaysActiveTabBucketZero() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(0) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet7DaysActiveTabBucketExactly1() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(1) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet7DaysActiveTabBucket2To5() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(2) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals("2-5", result) + } + + @Test + fun testGet7DaysActiveTabBucket6To10() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(10) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals("6-10", result) + } + + @Test + fun testGet7DaysActiveTabBucket11To20() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(15) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals("11-20", result) + } + + @Test + fun testGet7DaysActiveTabBucketMoreThan20() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(25) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals(">20", result) + } + + @Test + fun testGet7DaysActiveTabBucketALotMoreThan20() = runBlocking { + whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(250) + val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + assertEquals(">20", result) + } + + @Test + fun testGet1WeeksInactiveTabBucketZero() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(0) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet1WeeksInactiveTabBucketExactly1() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(1) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet1WeeksInactiveTabBucket2To5() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(3) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals("2-5", result) + } + + @Test + fun testGet1WeeksInactiveTabBucket6To10() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(8) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals("6-10", result) + } + + @Test + fun testGet1WeeksInactiveTabBucket11To20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(15) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals("11-20", result) + } + + @Test + fun testGet1WeeksInactiveTabBucketMoreThan20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(25) + val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + assertEquals(">20", result) + } + + @Test + fun testGet2WeeksInactiveTabBucketZero() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(0) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet2WeeksInactiveTabBucketExactly1() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(1) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet2WeeksInactiveTabBucket2To5() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(5) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals("2-5", result) + } + + @Test + fun testGet2WeeksInactiveTabBucket6To10() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(6) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals("6-10", result) + } + + @Test + fun testGet2WeeksInactiveTabBucket11To20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(20) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals("11-20", result) + } + + @Test + fun testGet2WeeksInactiveTabBucketMoreThan20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(199) + val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + assertEquals(">20", result) + } + + @Test + fun testGet3WeeksInactiveTabBucketZero() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(0) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet3WeeksInactiveTabBucketExactly1() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(1) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals("0-1", result) + } + + @Test + fun testGet3WeeksInactiveTabBucket2To5() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(5) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals("2-5", result) + } + + @Test + fun testGet3WeeksInactiveTabBucket6To10() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(10) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals("6-10", result) + } + + @Test + fun testGet3WeeksInactiveTabBucket11To20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(11) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals("11-20", result) + } + + @Test + fun testGet3WeeksInactiveTabBucketMoreThan20() = runBlocking { + whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(21) + val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + assertEquals(">20", result) + } +} diff --git a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt new file mode 100644 index 000000000000..be5aaef72cf9 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.tabs.store + +import com.duckduckgo.app.tabs.model.TabRepository +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ACTIVE_TABS_DAYS_LIMIT +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.BUCKETS +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.EXACTLY_1_BUCKET +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.MORE_THAN_20_BUCKET +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ONE_WEEK_INACTIVE_LIMIT +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.THREE_WEEKS_INACTIVE_LIMIT +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.TWO_WEEKS_INACTIVE_LIMIT +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface TabStatsBucketing { + suspend fun getTabCountBucket(): String + suspend fun get7DaysActiveTabBucket(): String + suspend fun get1WeeksInactiveTabBucket(): String + suspend fun get2WeeksInactiveTabBucket(): String + suspend fun get3WeeksInactiveTabBucket(): String + + companion object { + const val ACTIVE_TABS_DAYS_LIMIT = 7L + val ONE_WEEK_INACTIVE_LIMIT = 8L..14 + val TWO_WEEKS_INACTIVE_LIMIT = 15L..21 + const val THREE_WEEKS_INACTIVE_LIMIT = 22L + + const val EXACTLY_1_BUCKET = "1" + const val MORE_THAN_20_BUCKET = ">20" + + val BUCKETS = listOf( + 0..1, + 2..5, + 6..10, + 11..20, + 21..40, + 41..60, + 61..80, + 81..100, + 101..125, + 126..150, + 151..250, + 251..500, + 501..Int.MAX_VALUE + ) + } +} + +@ContributesBinding(AppScope::class) +class DefaultTabStatsBucketing @Inject constructor( + private val tabRepository: TabRepository +) : TabStatsBucketing { + override suspend fun getTabCountBucket(): String { + val count = tabRepository.getOpenTabCount() + val bucket = BUCKETS.first { bucket -> + count in bucket + } + return when (bucket) { + BUCKETS.first() -> { + EXACTLY_1_BUCKET + } + BUCKETS.last() -> { + "${bucket.first}+" + } + else -> { + "${bucket.first}-${bucket.last}" + } + } + } + + override suspend fun get7DaysActiveTabBucket(): String { + val count = tabRepository.getActiveTabCount(ACTIVE_TABS_DAYS_LIMIT) + return getTabActivityBucket(count) + } + + override suspend fun get1WeeksInactiveTabBucket(): String { + val count = tabRepository.getInactiveTabCount(ONE_WEEK_INACTIVE_LIMIT.first, ONE_WEEK_INACTIVE_LIMIT.last) + return getTabActivityBucket(count) + } + + override suspend fun get2WeeksInactiveTabBucket(): String { + val count = tabRepository.getInactiveTabCount(TWO_WEEKS_INACTIVE_LIMIT.first, TWO_WEEKS_INACTIVE_LIMIT.last) + return getTabActivityBucket(count) + } + + override suspend fun get3WeeksInactiveTabBucket(): String { + val count = tabRepository.getInactiveTabCount(THREE_WEEKS_INACTIVE_LIMIT) + return getTabActivityBucket(count) + } + + private fun getTabActivityBucket(count: Int): String { + val bucket = BUCKETS.first { bucket -> + count in bucket + } + return when { + bucket.last > 20 -> { + MORE_THAN_20_BUCKET + } + else -> { + "${bucket.first}-${bucket.last}" + } + } + } +} From 697086265c2fe1f387b7af27284a1ef827011801 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 10:54:02 +0100 Subject: [PATCH 12/22] Add tab stats parameters to the tab_manager_clicked_daily pixel --- .../app/browser/BrowserTabViewModelTest.kt | 3 +++ .../app/browser/BrowserTabViewModel.kt | 25 ++++++++++++++++++- .../duckduckgo/app/statistics/pixels/Pixel.kt | 6 +++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 4fbaad47dcb3..00c0332d2ed6 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -175,6 +175,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelValues.DAX_FIRE_DIALOG_CT import com.duckduckgo.app.surrogates.SurrogateResponse import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabRepository +import com.duckduckgo.app.tabs.store.TabStatsBucketing import com.duckduckgo.app.trackerdetection.EntityLookup import com.duckduckgo.app.trackerdetection.model.TrackerStatus import com.duckduckgo.app.trackerdetection.model.TrackerType @@ -494,6 +495,7 @@ class BrowserTabViewModelTest { private val mockAutocompleteTabsFeature: AutocompleteTabsFeature = mock() private val fakeCustomHeadersPlugin = FakeCustomHeadersProvider(emptyMap()) private val mockBrokenSitePrompt: BrokenSitePrompt = mock() + private val mockTabStatsBucketing: TabStatsBucketing = mock() @Before fun before() = runTest { @@ -661,6 +663,7 @@ class BrowserTabViewModelTest { showOnAppLaunchOptionHandler = mockShowOnAppLaunchHandler, customHeadersProvider = fakeCustomHeadersPlugin, brokenSitePrompt = mockBrokenSitePrompt, + tabStatsBucketing = mockTabStatsBucketing, ) testee.loadData("abc", null, false, false) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index accbe08084aa..11ba43516fae 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -232,6 +232,7 @@ import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_SEARCH_WEBSITE_SELECT import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_DAX_CTA_CANCEL_BUTTON import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_SEARCH_CUSTOM import com.duckduckgo.app.pixels.AppPixelName.ONBOARDING_VISIT_SITE_CUSTOM +import com.duckduckgo.app.pixels.AppPixelName.TAB_MANAGER_CLICKED_DAILY import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature import com.duckduckgo.app.privacy.db.NetworkLeaderboardDao import com.duckduckgo.app.privacy.db.UserAllowListRepository @@ -246,6 +247,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelValues.DAX_FIRE_DIALOG_CT import com.duckduckgo.app.surrogates.SurrogateResponse import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabRepository +import com.duckduckgo.app.tabs.store.TabStatsBucketing import com.duckduckgo.app.trackerdetection.model.TrackingEvent import com.duckduckgo.app.usage.search.SearchCountDao import com.duckduckgo.autofill.api.AutofillCapabilityChecker @@ -344,6 +346,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -430,6 +433,7 @@ class BrowserTabViewModel @Inject constructor( private val showOnAppLaunchOptionHandler: ShowOnAppLaunchOptionHandler, private val customHeadersProvider: CustomHeadersProvider, private val brokenSitePrompt: BrokenSitePrompt, + private val tabStatsBucketing: TabStatsBucketing, ) : WebViewClientListener, EditSavedSiteListener, DeleteBookmarkListener, @@ -2825,7 +2829,26 @@ class BrowserTabViewModel @Inject constructor( fun userLaunchingTabSwitcher() { command.value = LaunchTabSwitcher pixel.fire(AppPixelName.TAB_MANAGER_CLICKED) - pixel.fire(AppPixelName.TAB_MANAGER_CLICKED_DAILY, emptyMap(), emptyMap(), Daily()) + fireDailyLaunchPixel() + } + + private fun fireDailyLaunchPixel() { + val tabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabCountBucket() } + val activeTabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get7DaysActiveTabBucket() } + val inactive1w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get1WeeksInactiveTabBucket() } + val inactive2w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get2WeeksInactiveTabBucket() } + val inactive3w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get3WeeksInactiveTabBucket() } + + viewModelScope.launch { + val params = mapOf( + PixelParameter.TAB_COUNT to tabCount.await(), + PixelParameter.TAB_ACTIVE_7D to activeTabCount.await(), + PixelParameter.TAB_INACTIVE_1W to inactive1w.await(), + PixelParameter.TAB_INACTIVE_2W to inactive2w.await(), + PixelParameter.TAB_INACTIVE_3W to inactive3w.await(), + ) + pixel.fire(TAB_MANAGER_CLICKED_DAILY, params, emptyMap(), Daily()) + } } private fun isFireproofWebsite(domain: String? = site?.domain): Boolean { diff --git a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt index 8de07d743664..f11d387cf99d 100644 --- a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt +++ b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt @@ -17,6 +17,7 @@ package com.duckduckgo.app.statistics.pixels import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count +import javax.swing.text.TableView.TableRow /** Primary interface for sending anonymous analytics events (pixels). */ interface Pixel { @@ -61,6 +62,11 @@ interface Pixel { const val FROM_ONBOARDING = "from_onboarding" const val ADDRESS_BAR = "address_bar" const val LAUNCH_SCREEN = "launch_screen" + const val TAB_COUNT = "tab_count" + const val TAB_ACTIVE_7D = "tab_active_7d" + const val TAB_INACTIVE_1W = "tab_inactive_1w" + const val TAB_INACTIVE_2W = "tab_inactive_2w" + const val TAB_INACTIVE_3W = "tab_inactive_3w" } object PixelValues { From 4d653496820afc9deba1f2e17fc1525df1d3c81a Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 12:26:35 +0100 Subject: [PATCH 13/22] Add tests for pixel parameters being included --- .../app/browser/BrowserTabViewModelTest.kt | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 00c0332d2ed6..58372ef8676b 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -38,6 +38,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer +import androidx.lifecycle.viewModelScope import androidx.room.Room import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation @@ -255,6 +256,7 @@ import java.time.LocalDateTime import java.util.UUID import java.util.concurrent.TimeUnit import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -263,6 +265,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy @@ -5445,12 +5448,32 @@ class BrowserTabViewModelTest { } @Test - fun whenUserLaunchingTabSwitcherThenLaunchTabSwitcherCommandSentAndPixelFired() { + fun whenUserLaunchingTabSwitcherThenLaunchTabSwitcherCommandSentAndPixelFired() = runTest { + val tabCount = "61-80" + val active7d = "21+" + val inactive1w = "11-20" + val inactive2w = "6-10" + val inactive3w = "0" + + whenever(mockTabStatsBucketing.getTabCountBucket()).thenReturn(tabCount) + whenever(mockTabStatsBucketing.get7DaysActiveTabBucket()).thenReturn(active7d) + whenever(mockTabStatsBucketing.get1WeeksInactiveTabBucket()).thenReturn(inactive1w) + whenever(mockTabStatsBucketing.get2WeeksInactiveTabBucket()).thenReturn(inactive2w) + whenever(mockTabStatsBucketing.get3WeeksInactiveTabBucket()).thenReturn(inactive3w) + + val params = mapOf( + PixelParameter.TAB_COUNT to tabCount, + PixelParameter.TAB_ACTIVE_7D to active7d, + PixelParameter.TAB_INACTIVE_1W to inactive1w, + PixelParameter.TAB_INACTIVE_2W to inactive2w, + PixelParameter.TAB_INACTIVE_3W to inactive3w, + ) + testee.userLaunchingTabSwitcher() assertCommandIssued() verify(mockPixel).fire(AppPixelName.TAB_MANAGER_CLICKED) - verify(mockPixel).fire(AppPixelName.TAB_MANAGER_CLICKED_DAILY, emptyMap(), emptyMap(), Daily()) + verify(mockPixel).fire(AppPixelName.TAB_MANAGER_CLICKED_DAILY, params, emptyMap(), Daily()) } @Test From 296c0864c0e7ca9226db4f88d2ce57a8fd8b1feb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 12:27:41 +0100 Subject: [PATCH 14/22] Fix the tab bucket ranges and refactor the way buckets are calculated --- .../app/tabs/model/TabDataRepositoryTest.kt | 25 ++--- .../app/tabs/store/TabStatsBucketingTest.kt | 92 +++++++++---------- .../app/tabs/model/TabDataRepository.kt | 14 +-- .../app/tabs/store/TabStatsBucketing.kt | 76 +++++++-------- .../app/tabs/model/TabRepository.kt | 11 ++- 5 files changed, 99 insertions(+), 119 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt index 98ca9d4b334a..a6c3638d1345 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt @@ -432,7 +432,6 @@ class TabDataRepositoryTest { )) val testee = tabDataRepository() - // Act: Call getOpenTabCount() val openTabCount = testee.getOpenTabCount() // Assert: Verify the count is correct @@ -445,8 +444,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(emptyList()) val testee = tabDataRepository() - // Act: Call getActiveTabCount() - val inactiveTabCount = testee.getActiveTabCount(7) + val inactiveTabCount = testee.countTabsWithinDayRange(0,7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -459,8 +457,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1)) val testee = tabDataRepository() - // Act: Call getActiveTabCount() - val inactiveTabCount = testee.getActiveTabCount(7) + val inactiveTabCount = testee.countTabsWithinDayRange(0,7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -477,8 +474,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - // Act: Call getActiveTabCount() - val inactiveTabCount = testee.getActiveTabCount(9) + val inactiveTabCount = testee.countTabsWithinDayRange(0,9) // Assert: Verify the count is 2 assertEquals(2, inactiveTabCount) @@ -490,8 +486,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(emptyList()) val testee = tabDataRepository() - // Act: Call getInactiveTabCount() - val inactiveTabCount = testee.getInactiveTabCount(7, 12) + val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -508,8 +503,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - // Act: Call getInactiveTabCount() - val inactiveTabCount = testee.getInactiveTabCount(9) + val inactiveTabCount = testee.countTabsWithinDayRange(9) // Assert: Verify the count is correct assertEquals(2, inactiveTabCount) @@ -526,8 +520,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - // Act: Call getInactiveTabCount() - val inactiveTabCount = testee.getInactiveTabCount(7, 12) + val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) // Assert: Verify the count is correct assertEquals(3, inactiveTabCount) @@ -544,8 +537,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - // Act: Call getInactiveTabCount() - val inactiveTabCount = testee.getInactiveTabCount(7, 12) + val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -562,8 +554,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - // Act: Call getInactiveTabCount() - val inactiveTabCount = testee.getInactiveTabCount(7, 12) + val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) // Assert: Verify the count is correct assertEquals(1, inactiveTabCount) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt index 1a96e7cacae3..d1b5e2149d98 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -134,176 +134,176 @@ class DefaultTabStatsBucketingTest { @Test fun testGet7DaysActiveTabBucketZero() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(0) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() - assertEquals("0-1", result) + assertEquals("0", result) } @Test fun testGet7DaysActiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(1) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() - assertEquals("0-1", result) + assertEquals("1-5", result) } @Test - fun testGet7DaysActiveTabBucket2To5() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(2) + fun testGet7DaysActiveTabBucket1To5() = runBlocking { + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(2) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() - assertEquals("2-5", result) + assertEquals("1-5", result) } @Test fun testGet7DaysActiveTabBucket6To10() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(10) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("6-10", result) } @Test fun testGet7DaysActiveTabBucket11To20() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(15) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("11-20", result) } @Test fun testGet7DaysActiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(25) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() - assertEquals(">20", result) + assertEquals("21+", result) } @Test fun testGet7DaysActiveTabBucketALotMoreThan20() = runBlocking { - whenever(tabRepository.getActiveTabCount(TabStatsBucketing.ACTIVE_TABS_DAYS_LIMIT)).thenReturn(250) + whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(250) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() - assertEquals(">20", result) + assertEquals("21+", result) } @Test fun testGet1WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(0) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("0", result) } @Test fun testGet1WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(1) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("1-5", result) } @Test - fun testGet1WeeksInactiveTabBucket2To5() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(3) + fun testGet1WeeksInactiveTabBucket1To5() = runBlocking { + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(3) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() - assertEquals("2-5", result) + assertEquals("1-5", result) } @Test fun testGet1WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(8) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(8) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet1WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(15) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet1WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.first, TabStatsBucketing.ONE_WEEK_INACTIVE_LIMIT.last)).thenReturn(25) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() - assertEquals(">20", result) + assertEquals("21+", result) } @Test fun testGet2WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(0) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("0", result) } @Test fun testGet2WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(1) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("1-5", result) } @Test - fun testGet2WeeksInactiveTabBucket2To5() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(5) + fun testGet2WeeksInactiveTabBucket1To5() = runBlocking { + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() - assertEquals("2-5", result) + assertEquals("1-5", result) } @Test fun testGet2WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(6) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(6) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet2WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(20) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(20) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet2WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.first, TabStatsBucketing.TWO_WEEKS_INACTIVE_LIMIT.last)).thenReturn(199) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(199) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() - assertEquals(">20", result) + assertEquals("21+", result) } @Test fun testGet3WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(0) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("0", result) } @Test fun testGet3WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(1) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() - assertEquals("0-1", result) + assertEquals("1-5", result) } @Test - fun testGet3WeeksInactiveTabBucket2To5() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(5) + fun testGet3WeeksInactiveTabBucket1To5() = runBlocking { + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() - assertEquals("2-5", result) + assertEquals("1-5", result) } @Test fun testGet3WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(10) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet3WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(11) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(11) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet3WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.getInactiveTabCount(TabStatsBucketing.THREE_WEEKS_INACTIVE_LIMIT)).thenReturn(21) + whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(21) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() - assertEquals(">20", result) + assertEquals("21+", result) } } diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index c61c013037df..0c3b24dad435 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -198,18 +198,10 @@ class TabDataRepository @Inject constructor( return tabsDao.tabs().size } - override fun getActiveTabCount(maxDaysSinceOpened: Long): Int { + override fun countTabsWithinDayRange(accessOlderThan: Long, accessNotMoreThan: Long?): Int { val now = LocalDateTime.now(ZoneOffset.UTC) - val maxDaysAgo = now.minusDays(maxDaysSinceOpened) - return tabsDao.tabs().filter { - it.lastAccessTime?.isAfter(maxDaysAgo) == true - }.size - } - - override fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long?): Int { - val now = LocalDateTime.now(ZoneOffset.UTC) - val start = now.minusDays(atLeastDaysOld) - val end = atMostDaysOld?.let { now.minusDays(it) } + val start = now.minusDays(accessOlderThan) + val end = accessNotMoreThan?.let { now.minusDays(it).minusSeconds(1) } // subtracted a second to make the end limit inclusive return tabsDao.tabs().filter { it.lastAccessTime?.isBefore(start) == true && (end == null || it.lastAccessTime?.isAfter(end) == true) }.size diff --git a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt index be5aaef72cf9..b23a2c2836c2 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt @@ -17,13 +17,11 @@ package com.duckduckgo.app.tabs.store import com.duckduckgo.app.tabs.model.TabRepository -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ACTIVE_TABS_DAYS_LIMIT -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.BUCKETS -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.EXACTLY_1_BUCKET -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.MORE_THAN_20_BUCKET -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ONE_WEEK_INACTIVE_LIMIT -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.THREE_WEEKS_INACTIVE_LIMIT -import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.TWO_WEEKS_INACTIVE_LIMIT +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ACTIVITY_BUCKETS +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.ONE_WEEK_IN_DAYS +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.TAB_COUNT_BUCKETS +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.THREE_WEEKS_IN_DAYS +import com.duckduckgo.app.tabs.store.TabStatsBucketing.Companion.TWO_WEEKS_IN_DAYS import com.duckduckgo.di.scopes.AppScope import com.squareup.anvil.annotations.ContributesBinding import javax.inject.Inject @@ -36,15 +34,11 @@ interface TabStatsBucketing { suspend fun get3WeeksInactiveTabBucket(): String companion object { - const val ACTIVE_TABS_DAYS_LIMIT = 7L - val ONE_WEEK_INACTIVE_LIMIT = 8L..14 - val TWO_WEEKS_INACTIVE_LIMIT = 15L..21 - const val THREE_WEEKS_INACTIVE_LIMIT = 22L + const val ONE_WEEK_IN_DAYS = 7L + const val TWO_WEEKS_IN_DAYS = 14L + const val THREE_WEEKS_IN_DAYS = 21L - const val EXACTLY_1_BUCKET = "1" - const val MORE_THAN_20_BUCKET = ">20" - - val BUCKETS = listOf( + val TAB_COUNT_BUCKETS = listOf( 0..1, 2..5, 6..10, @@ -59,6 +53,14 @@ interface TabStatsBucketing { 251..500, 501..Int.MAX_VALUE ) + + val ACTIVITY_BUCKETS = listOf( + 0..0, + 1..5, + 6..10, + 11..20, + 21..Int.MAX_VALUE + ) } } @@ -68,49 +70,39 @@ class DefaultTabStatsBucketing @Inject constructor( ) : TabStatsBucketing { override suspend fun getTabCountBucket(): String { val count = tabRepository.getOpenTabCount() - val bucket = BUCKETS.first { bucket -> - count in bucket - } - return when (bucket) { - BUCKETS.first() -> { - EXACTLY_1_BUCKET - } - BUCKETS.last() -> { - "${bucket.first}+" - } - else -> { - "${bucket.first}-${bucket.last}" - } - } + return getBucketLabel(count, TAB_COUNT_BUCKETS) } override suspend fun get7DaysActiveTabBucket(): String { - val count = tabRepository.getActiveTabCount(ACTIVE_TABS_DAYS_LIMIT) - return getTabActivityBucket(count) + val count = tabRepository.countTabsWithinDayRange(accessOlderThan = 0, accessNotMoreThan = ONE_WEEK_IN_DAYS) + return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get1WeeksInactiveTabBucket(): String { - val count = tabRepository.getInactiveTabCount(ONE_WEEK_INACTIVE_LIMIT.first, ONE_WEEK_INACTIVE_LIMIT.last) - return getTabActivityBucket(count) + val count = tabRepository.countTabsWithinDayRange(accessOlderThan = ONE_WEEK_IN_DAYS, accessNotMoreThan = TWO_WEEKS_IN_DAYS) + return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get2WeeksInactiveTabBucket(): String { - val count = tabRepository.getInactiveTabCount(TWO_WEEKS_INACTIVE_LIMIT.first, TWO_WEEKS_INACTIVE_LIMIT.last) - return getTabActivityBucket(count) + val count = tabRepository.countTabsWithinDayRange(accessOlderThan = TWO_WEEKS_IN_DAYS, accessNotMoreThan = THREE_WEEKS_IN_DAYS) + return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get3WeeksInactiveTabBucket(): String { - val count = tabRepository.getInactiveTabCount(THREE_WEEKS_INACTIVE_LIMIT) - return getTabActivityBucket(count) + val count = tabRepository.countTabsWithinDayRange(accessOlderThan = THREE_WEEKS_IN_DAYS) + return getBucketLabel(count, ACTIVITY_BUCKETS) } - private fun getTabActivityBucket(count: Int): String { - val bucket = BUCKETS.first { bucket -> + private fun getBucketLabel(count: Int, buckets: List): String { + val bucket = buckets.first { bucket -> count in bucket } - return when { - bucket.last > 20 -> { - MORE_THAN_20_BUCKET + return when (bucket) { + buckets.first() -> { + bucket.last.toString() + } + buckets.last() -> { + "${bucket.first}+" } else -> { "${bucket.first}-${bucket.last}" diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index d05735c736be..5aae5dd851a2 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -119,7 +119,12 @@ interface TabRepository { fun getOpenTabCount(): Int - fun getActiveTabCount(maxDaysSinceOpened: Long): Int - - fun getInactiveTabCount(atLeastDaysOld: Long, atMostDaysOld: Long? = null): Int + /** + * Returns the number of tabs, given a range of days within which the tab was last accessed. + * + * @param accessOlderThan the minimum number of days (exclusive) since the tab was last accessed + * @param accessNotMoreThan the maximum number of days (inclusive) since the tab was last accessed (optional) + * @return the number of tabs that are inactive + */ + fun countTabsWithinDayRange(accessOlderThan: Long, accessNotMoreThan: Long? = null): Int } From 3724bd68235f735cbe461d9a83ddf600a93f67fd Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 12:32:49 +0100 Subject: [PATCH 15/22] Fix ktlint issues --- .../app/browser/BrowserTabViewModelTest.kt | 3 --- .../app/tabs/model/TabDataRepositoryTest.kt | 22 ++++++++++--------- .../duckduckgo/app/global/db/AppDatabase.kt | 2 +- .../com/duckduckgo/app/tabs/db/TabsDao.kt | 4 +--- .../app/tabs/model/TabDataRepository.kt | 4 ++-- .../app/tabs/store/TabStatsBucketing.kt | 6 ++--- .../duckduckgo/app/tabs/model/TabEntitiy.kt | 1 - .../duckduckgo/app/statistics/pixels/Pixel.kt | 1 - 8 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 58372ef8676b..1fc26ef2371c 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -38,7 +38,6 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import androidx.lifecycle.viewModelScope import androidx.room.Room import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation @@ -256,7 +255,6 @@ import java.time.LocalDateTime import java.util.UUID import java.util.concurrent.TimeUnit import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -265,7 +263,6 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt index a6c3638d1345..a501531c540e 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt @@ -37,6 +37,8 @@ import com.duckduckgo.common.test.CoroutineTestRule import com.duckduckgo.common.test.InstantSchedulersRule import com.duckduckgo.duckplayer.api.DuckPlayer import com.duckduckgo.privacy.config.api.ContentBlocking +import java.time.LocalDateTime +import java.time.ZoneOffset import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch @@ -46,8 +48,6 @@ import org.junit.Assert.* import org.junit.Rule import org.junit.Test import org.mockito.kotlin.* -import java.time.LocalDateTime -import java.time.ZoneOffset class TabDataRepositoryTest { @@ -425,11 +425,13 @@ class TabDataRepositoryTest { @Test fun getOpenTabCountReturnsCorrectCount() = runTest { // Arrange: Add some tabs to the repository - whenever(mockDao.tabs()).thenReturn(listOf( - TabEntity(tabId = "tab1"), - TabEntity(tabId = "tab2"), - TabEntity(tabId = "tab3") - )) + whenever(mockDao.tabs()).thenReturn( + listOf( + TabEntity(tabId = "tab1"), + TabEntity(tabId = "tab2"), + TabEntity(tabId = "tab3"), + ), + ) val testee = tabDataRepository() val openTabCount = testee.getOpenTabCount() @@ -444,7 +446,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(emptyList()) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0,7) + val inactiveTabCount = testee.countTabsWithinDayRange(0, 7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -457,7 +459,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0,7) + val inactiveTabCount = testee.countTabsWithinDayRange(0, 7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -474,7 +476,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0,9) + val inactiveTabCount = testee.countTabsWithinDayRange(0, 9) // Assert: Verify the count is 2 assertEquals(2, inactiveTabCount) diff --git a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt index 03aa2d3bb52f..d9fad2a5e178 100644 --- a/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt +++ b/app/src/main/java/com/duckduckgo/app/global/db/AppDatabase.kt @@ -762,7 +762,7 @@ class MigrationsProvider(val context: Context, val settingsDataStore: SettingsDa MIGRATION_52_TO_53, MIGRATION_53_TO_54, MIGRATION_54_TO_55, - MIGRATION_55_TO_56 + MIGRATION_55_TO_56, ) @Deprecated( diff --git a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt index 98f1f3320680..32cedd2d886f 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt @@ -20,13 +20,12 @@ import androidx.lifecycle.LiveData import androidx.room.* import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabSelectionEntity -import com.duckduckgo.common.utils.formatters.time.DatabaseDateFormatter.Companion.timestamp import com.duckduckgo.common.utils.swap import com.duckduckgo.di.scopes.AppScope import dagger.SingleInstanceIn -import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime import java.time.ZoneOffset +import kotlinx.coroutines.flow.Flow @Dao @SingleInstanceIn(AppScope::class) @@ -168,7 +167,6 @@ abstract class TabsDao { return tabs().lastOrNull() } - @Query("update tabs set lastAccessTime=:lastAccessTime where tabId=:tabId") abstract fun updateTabLastAccess( tabId: String, diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index 0c3b24dad435..468daa7ca375 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -35,6 +35,8 @@ import com.duckduckgo.di.scopes.AppScope import dagger.SingleInstanceIn import io.reactivex.Scheduler import io.reactivex.schedulers.Schedulers +import java.time.LocalDateTime +import java.time.ZoneOffset import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -47,8 +49,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber -import java.time.LocalDateTime -import java.time.ZoneOffset @SingleInstanceIn(AppScope::class) class TabDataRepository @Inject constructor( diff --git a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt index b23a2c2836c2..b16078ae3e12 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt @@ -51,7 +51,7 @@ interface TabStatsBucketing { 126..150, 151..250, 251..500, - 501..Int.MAX_VALUE + 501..Int.MAX_VALUE, ) val ACTIVITY_BUCKETS = listOf( @@ -59,14 +59,14 @@ interface TabStatsBucketing { 1..5, 6..10, 11..20, - 21..Int.MAX_VALUE + 21..Int.MAX_VALUE, ) } } @ContributesBinding(AppScope::class) class DefaultTabStatsBucketing @Inject constructor( - private val tabRepository: TabRepository + private val tabRepository: TabRepository, ) : TabStatsBucketing { override suspend fun getTabCountBucket(): String { val count = tabRepository.getOpenTabCount() diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt index 3fc1a33e7db6..677797119841 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabEntitiy.kt @@ -55,7 +55,6 @@ data class TabEntity( val TabEntity.isBlank: Boolean get() = title == null && url == null - class LocalDateTimeTypeConverter { @TypeConverter fun convertForDb(date: LocalDateTime): String = DatabaseDateFormatter.timestamp(date) diff --git a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt index f11d387cf99d..38d710cfab8e 100644 --- a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt +++ b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/pixels/Pixel.kt @@ -17,7 +17,6 @@ package com.duckduckgo.app.statistics.pixels import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count -import javax.swing.text.TableView.TableRow /** Primary interface for sending anonymous analytics events (pixels). */ interface Pixel { From 46495faa6fae24268d77bc71477557a35c87011a Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 12:37:10 +0100 Subject: [PATCH 16/22] Rename the tab counting method for better understanding --- .../app/tabs/model/TabDataRepositoryTest.kt | 16 +++--- .../app/tabs/store/TabStatsBucketingTest.kt | 50 +++++++++---------- .../app/tabs/model/TabDataRepository.kt | 2 +- .../app/tabs/store/TabStatsBucketing.kt | 8 +-- .../app/tabs/model/TabRepository.kt | 2 +- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt index a501531c540e..514ebe75a2c4 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt @@ -446,7 +446,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(emptyList()) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0, 7) + val inactiveTabCount = testee.countTabsAccessedWithinRange(0, 7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -459,7 +459,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0, 7) + val inactiveTabCount = testee.countTabsAccessedWithinRange(0, 7) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -476,7 +476,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(0, 9) + val inactiveTabCount = testee.countTabsAccessedWithinRange(0, 9) // Assert: Verify the count is 2 assertEquals(2, inactiveTabCount) @@ -488,7 +488,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(emptyList()) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) + val inactiveTabCount = testee.countTabsAccessedWithinRange(7, 12) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -505,7 +505,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(9) + val inactiveTabCount = testee.countTabsAccessedWithinRange(9) // Assert: Verify the count is correct assertEquals(2, inactiveTabCount) @@ -522,7 +522,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) + val inactiveTabCount = testee.countTabsAccessedWithinRange(7, 12) // Assert: Verify the count is correct assertEquals(3, inactiveTabCount) @@ -539,7 +539,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) + val inactiveTabCount = testee.countTabsAccessedWithinRange(7, 12) // Assert: Verify the count is zero assertEquals(0, inactiveTabCount) @@ -556,7 +556,7 @@ class TabDataRepositoryTest { whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4)) val testee = tabDataRepository() - val inactiveTabCount = testee.countTabsWithinDayRange(7, 12) + val inactiveTabCount = testee.countTabsAccessedWithinRange(7, 12) // Assert: Verify the count is correct assertEquals(1, inactiveTabCount) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt index d1b5e2149d98..b00b72052e66 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -134,175 +134,175 @@ class DefaultTabStatsBucketingTest { @Test fun testGet7DaysActiveTabBucketZero() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(0) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("0", result) } @Test fun testGet7DaysActiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(1) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("1-5", result) } @Test fun testGet7DaysActiveTabBucket1To5() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(2) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(2) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("1-5", result) } @Test fun testGet7DaysActiveTabBucket6To10() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(10) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("6-10", result) } @Test fun testGet7DaysActiveTabBucket11To20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(15) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("11-20", result) } @Test fun testGet7DaysActiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(25) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("21+", result) } @Test fun testGet7DaysActiveTabBucketALotMoreThan20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(250) + whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(250) val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() assertEquals("21+", result) } @Test fun testGet1WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(0) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("0", result) } @Test fun testGet1WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(1) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet1WeeksInactiveTabBucket1To5() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(3) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(3) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet1WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(8) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(8) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet1WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(15) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet1WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(25) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() assertEquals("21+", result) } @Test fun testGet2WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("0", result) } @Test fun testGet2WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet2WeeksInactiveTabBucket1To5() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet2WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(6) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(6) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet2WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(20) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(20) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet2WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(199) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(199) val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() assertEquals("21+", result) } @Test fun testGet3WeeksInactiveTabBucketZero() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("0", result) } @Test fun testGet3WeeksInactiveTabBucketExactly1() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet3WeeksInactiveTabBucket1To5() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("1-5", result) } @Test fun testGet3WeeksInactiveTabBucket6To10() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(10) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("6-10", result) } @Test fun testGet3WeeksInactiveTabBucket11To20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(11) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(11) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("11-20", result) } @Test fun testGet3WeeksInactiveTabBucketMoreThan20() = runBlocking { - whenever(tabRepository.countTabsWithinDayRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(21) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(21) val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() assertEquals("21+", result) } diff --git a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt index 468daa7ca375..6e35c1172fca 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt @@ -198,7 +198,7 @@ class TabDataRepository @Inject constructor( return tabsDao.tabs().size } - override fun countTabsWithinDayRange(accessOlderThan: Long, accessNotMoreThan: Long?): Int { + override fun countTabsAccessedWithinRange(accessOlderThan: Long, accessNotMoreThan: Long?): Int { val now = LocalDateTime.now(ZoneOffset.UTC) val start = now.minusDays(accessOlderThan) val end = accessNotMoreThan?.let { now.minusDays(it).minusSeconds(1) } // subtracted a second to make the end limit inclusive diff --git a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt index b16078ae3e12..786ecb7cf8ed 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt @@ -74,22 +74,22 @@ class DefaultTabStatsBucketing @Inject constructor( } override suspend fun get7DaysActiveTabBucket(): String { - val count = tabRepository.countTabsWithinDayRange(accessOlderThan = 0, accessNotMoreThan = ONE_WEEK_IN_DAYS) + val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = 0, accessNotMoreThan = ONE_WEEK_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get1WeeksInactiveTabBucket(): String { - val count = tabRepository.countTabsWithinDayRange(accessOlderThan = ONE_WEEK_IN_DAYS, accessNotMoreThan = TWO_WEEKS_IN_DAYS) + val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = ONE_WEEK_IN_DAYS, accessNotMoreThan = TWO_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get2WeeksInactiveTabBucket(): String { - val count = tabRepository.countTabsWithinDayRange(accessOlderThan = TWO_WEEKS_IN_DAYS, accessNotMoreThan = THREE_WEEKS_IN_DAYS) + val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = TWO_WEEKS_IN_DAYS, accessNotMoreThan = THREE_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } override suspend fun get3WeeksInactiveTabBucket(): String { - val count = tabRepository.countTabsWithinDayRange(accessOlderThan = THREE_WEEKS_IN_DAYS) + val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = THREE_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } diff --git a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt index 5aae5dd851a2..41b0eff8d37e 100644 --- a/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt +++ b/browser-api/src/main/java/com/duckduckgo/app/tabs/model/TabRepository.kt @@ -126,5 +126,5 @@ interface TabRepository { * @param accessNotMoreThan the maximum number of days (inclusive) since the tab was last accessed (optional) * @return the number of tabs that are inactive */ - fun countTabsWithinDayRange(accessOlderThan: Long, accessNotMoreThan: Long? = null): Int + fun countTabsAccessedWithinRange(accessOlderThan: Long, accessNotMoreThan: Long? = null): Int } From 72df1c2bf0927af57e85aedc7b5b6b77eafeeb6a Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 13:43:34 +0100 Subject: [PATCH 17/22] Rename the methods for improved understanding --- .../app/browser/BrowserTabViewModelTest.kt | 10 +- .../app/tabs/store/TabStatsBucketingTest.kt | 152 +++++++++--------- .../app/browser/BrowserTabViewModel.kt | 10 +- .../app/tabs/store/TabStatsBucketing.kt | 20 +-- 4 files changed, 96 insertions(+), 96 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index 1fc26ef2371c..d921cd9a6fb8 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -5452,11 +5452,11 @@ class BrowserTabViewModelTest { val inactive2w = "6-10" val inactive3w = "0" - whenever(mockTabStatsBucketing.getTabCountBucket()).thenReturn(tabCount) - whenever(mockTabStatsBucketing.get7DaysActiveTabBucket()).thenReturn(active7d) - whenever(mockTabStatsBucketing.get1WeeksInactiveTabBucket()).thenReturn(inactive1w) - whenever(mockTabStatsBucketing.get2WeeksInactiveTabBucket()).thenReturn(inactive2w) - whenever(mockTabStatsBucketing.get3WeeksInactiveTabBucket()).thenReturn(inactive3w) + whenever(mockTabStatsBucketing.getNumberOfOpenTabs()).thenReturn(tabCount) + whenever(mockTabStatsBucketing.getTabsActiveLastWeek()).thenReturn(active7d) + whenever(mockTabStatsBucketing.getTabsActiveOneWeekAgo()).thenReturn(inactive1w) + whenever(mockTabStatsBucketing.getTabsActiveTwoWeeksAgo()).thenReturn(inactive2w) + whenever(mockTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo()).thenReturn(inactive3w) val params = mapOf( PixelParameter.TAB_COUNT to tabCount, diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt index b00b72052e66..9d3535caa21d 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -35,275 +35,275 @@ class DefaultTabStatsBucketingTest { } @Test - fun testGetTabCountBucketExactly1() = runBlocking { + fun testGetNumberOfOpenTabsExactly1() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(1) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("1", result) } @Test - fun testGetTabCountBucketZero() = runBlocking { + fun testGetNumberOfOpenTabsZero() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(0) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("1", result) } @Test - fun testGetTabCountBucket() = runBlocking { + fun testGetNumberOfOpenTabs() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(5) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("2-5", result) } @Test - fun testGetTabCountBucket6To10() = runBlocking { + fun testGetNumberOfOpenTabs6To10() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(8) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("6-10", result) } @Test - fun testGetTabCountBucket11To20() = runBlocking { + fun testGetNumberOfOpenTabs11To20() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(11) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("11-20", result) } @Test - fun testGetTabCountBucketMoreThan20() = runBlocking { + fun testGetNumberOfOpenTabsMoreThan20() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(40) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("21-40", result) } @Test - fun testGetTabCountBucket41To60() = runBlocking { + fun testGetNumberOfOpenTabs41To60() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(50) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("41-60", result) } @Test - fun testGetTabCountBucket61To80() = runBlocking { + fun testGetNumberOfOpenTabs61To80() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(70) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("61-80", result) } @Test - fun testGetTabCountBucket81To100() = runBlocking { + fun testGetNumberOfOpenTabs81To100() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(90) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("81-100", result) } @Test - fun testGetTabCountBucket101To125() = runBlocking { + fun testGetNumberOfOpenTabs101To125() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(110) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("101-125", result) } @Test - fun testGetTabCountBucket126To150() = runBlocking { + fun testGetNumberOfOpenTabs126To150() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(130) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("126-150", result) } @Test - fun testGetTabCountBucket151To250() = runBlocking { + fun testGetNumberOfOpenTabs151To250() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(200) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("151-250", result) } @Test - fun testGetTabCountBucket251To500() = runBlocking { + fun testGetNumberOfOpenTabs251To500() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(300) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("251-500", result) } @Test - fun testGetTabCountBucketMaxValue() = runBlocking { + fun testGetNumberOfOpenTabsMaxValue() = runBlocking { whenever(tabRepository.getOpenTabCount()).thenReturn(600) - val result = defaultTabStatsBucketing.getTabCountBucket() + val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("501+", result) } @Test - fun testGet7DaysActiveTabBucketZero() = runBlocking { + fun testGetTabsActiveLastWeekZero() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(0) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("0", result) } @Test - fun testGet7DaysActiveTabBucketExactly1() = runBlocking { + fun testGetTabsActiveLastWeekExactly1() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(1) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("1-5", result) } @Test - fun testGet7DaysActiveTabBucket1To5() = runBlocking { + fun testGetTabsActiveLastWeek1To5() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(2) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("1-5", result) } @Test - fun testGet7DaysActiveTabBucket6To10() = runBlocking { + fun testGetTabsActiveLastWeek6To10() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(10) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("6-10", result) } @Test - fun testGet7DaysActiveTabBucket11To20() = runBlocking { + fun testGetTabsActiveLastWeek11To20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(15) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("11-20", result) } @Test - fun testGet7DaysActiveTabBucketMoreThan20() = runBlocking { + fun testGetTabsActiveLastWeekMoreThan20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(25) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("21+", result) } @Test - fun testGet7DaysActiveTabBucketALotMoreThan20() = runBlocking { + fun testGetTabsActiveLastWeekALotMoreThan20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(250) - val result = defaultTabStatsBucketing.get7DaysActiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("21+", result) } @Test - fun testGet1WeeksInactiveTabBucketZero() = runBlocking { + fun testGetTabsActiveOneWeekAgoZero() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(0) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("0", result) } @Test fun testGet1WeeksInactiveTabBucketExactly1() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(1) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("1-5", result) } @Test fun testGet1WeeksInactiveTabBucket1To5() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(3) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("1-5", result) } @Test - fun testGet1WeeksInactiveTabBucket6To10() = runBlocking { + fun testGetTabsActiveOneWeekAgo6To10() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(8) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("6-10", result) } @Test - fun testGet1WeeksInactiveTabBucket11To20() = runBlocking { + fun testGetTabsActiveOneWeekAgo11To20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(15) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("11-20", result) } @Test - fun testGet1WeeksInactiveTabBucketMoreThan20() = runBlocking { + fun testGetTabsActiveOneWeekAgoMoreThan20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(25) - val result = defaultTabStatsBucketing.get1WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("21+", result) } @Test - fun testGet2WeeksInactiveTabBucketZero() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoZero() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("0", result) } @Test - fun testGet2WeeksInactiveTabBucketExactly1() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoExactly1() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("1-5", result) } @Test - fun testGet2WeeksInactiveTabBucket1To5() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo1To5() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("1-5", result) } @Test - fun testGet2WeeksInactiveTabBucket6To10() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo6To10() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(6) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("6-10", result) } @Test - fun testGet2WeeksInactiveTabBucket11To20() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo11To20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(20) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("11-20", result) } @Test - fun testGet2WeeksInactiveTabBucketMoreThan20() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoMoreThan20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(199) - val result = defaultTabStatsBucketing.get2WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("21+", result) } @Test - fun testGet3WeeksInactiveTabBucketZero() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoZero() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("0", result) } @Test - fun testGet3WeeksInactiveTabBucketExactly1() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoExactly1() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("1-5", result) } @Test - fun testGet3WeeksInactiveTabBucket1To5() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo1To5() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("1-5", result) } @Test - fun testGet3WeeksInactiveTabBucket6To10() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo6To10() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(10) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("6-10", result) } @Test - fun testGet3WeeksInactiveTabBucket11To20() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo11To20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(11) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("11-20", result) } @Test - fun testGet3WeeksInactiveTabBucketMoreThan20() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoMoreThan20() = runBlocking { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(21) - val result = defaultTabStatsBucketing.get3WeeksInactiveTabBucket() + val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("21+", result) } } diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index 11ba43516fae..176538ef5bbc 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -2833,11 +2833,11 @@ class BrowserTabViewModel @Inject constructor( } private fun fireDailyLaunchPixel() { - val tabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabCountBucket() } - val activeTabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get7DaysActiveTabBucket() } - val inactive1w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get1WeeksInactiveTabBucket() } - val inactive2w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get2WeeksInactiveTabBucket() } - val inactive3w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.get3WeeksInactiveTabBucket() } + val tabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getNumberOfOpenTabs() } + val activeTabCount = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveLastWeek() } + val inactive1w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveOneWeekAgo() } + val inactive2w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveTwoWeeksAgo() } + val inactive3w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() } viewModelScope.launch { val params = mapOf( diff --git a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt index 786ecb7cf8ed..d31686965504 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/store/TabStatsBucketing.kt @@ -27,11 +27,11 @@ import com.squareup.anvil.annotations.ContributesBinding import javax.inject.Inject interface TabStatsBucketing { - suspend fun getTabCountBucket(): String - suspend fun get7DaysActiveTabBucket(): String - suspend fun get1WeeksInactiveTabBucket(): String - suspend fun get2WeeksInactiveTabBucket(): String - suspend fun get3WeeksInactiveTabBucket(): String + suspend fun getNumberOfOpenTabs(): String + suspend fun getTabsActiveLastWeek(): String + suspend fun getTabsActiveOneWeekAgo(): String + suspend fun getTabsActiveTwoWeeksAgo(): String + suspend fun getTabsActiveMoreThanThreeWeeksAgo(): String companion object { const val ONE_WEEK_IN_DAYS = 7L @@ -68,27 +68,27 @@ interface TabStatsBucketing { class DefaultTabStatsBucketing @Inject constructor( private val tabRepository: TabRepository, ) : TabStatsBucketing { - override suspend fun getTabCountBucket(): String { + override suspend fun getNumberOfOpenTabs(): String { val count = tabRepository.getOpenTabCount() return getBucketLabel(count, TAB_COUNT_BUCKETS) } - override suspend fun get7DaysActiveTabBucket(): String { + override suspend fun getTabsActiveLastWeek(): String { val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = 0, accessNotMoreThan = ONE_WEEK_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } - override suspend fun get1WeeksInactiveTabBucket(): String { + override suspend fun getTabsActiveOneWeekAgo(): String { val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = ONE_WEEK_IN_DAYS, accessNotMoreThan = TWO_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } - override suspend fun get2WeeksInactiveTabBucket(): String { + override suspend fun getTabsActiveTwoWeeksAgo(): String { val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = TWO_WEEKS_IN_DAYS, accessNotMoreThan = THREE_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } - override suspend fun get3WeeksInactiveTabBucket(): String { + override suspend fun getTabsActiveMoreThanThreeWeeksAgo(): String { val count = tabRepository.countTabsAccessedWithinRange(accessOlderThan = THREE_WEEKS_IN_DAYS) return getBucketLabel(count, ACTIVITY_BUCKETS) } From f25c6cd447d58108ab50da5f4056f0ecc933a713 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 16:46:13 +0100 Subject: [PATCH 18/22] Fix a failing test --- .../ShowOnAppLaunchOptionHandlerImplTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt index d663c75048eb..e2186c21cdf6 100644 --- a/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt +++ b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt @@ -790,6 +790,10 @@ class ShowOnAppLaunchOptionHandlerImplTest { TODO("Not yet implemented") } + override suspend fun updateTabLastAccess(tabId: String) { + TODO("Not yet implemented") + } + override fun retrieveSiteData(tabId: String): MutableLiveData { TODO("Not yet implemented") } @@ -851,5 +855,16 @@ class ShowOnAppLaunchOptionHandlerImplTest { override suspend fun setTabLayoutType(layoutType: LayoutType) { TODO("Not yet implemented") } + + override fun getOpenTabCount(): Int { + TODO("Not yet implemented") + } + + override fun countTabsAccessedWithinRange( + accessOlderThan: Long, + accessNotMoreThan: Long? + ): Int { + TODO("Not yet implemented") + } } } From 04acf5eda965e643d923a61ce2359eea9e102ef8 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 15 Nov 2024 16:47:08 +0100 Subject: [PATCH 19/22] Fix ktlint issues --- .../duckduckgo/app/tabs/store/TabStatsBucketingTest.kt | 8 ++++++-- .../ShowOnAppLaunchOptionHandlerImplTest.kt | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt index 9d3535caa21d..03ffb9756db7 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -253,14 +253,18 @@ class DefaultTabStatsBucketingTest { @Test fun testGetTabsActiveTwoWeeksAgo11To20() = runBlocking { - whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(20) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn( + 20, + ) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("11-20", result) } @Test fun testGetTabsActiveTwoWeeksAgoMoreThan20() = runBlocking { - whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(199) + whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn( + 199, + ) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("21+", result) } diff --git a/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt index e2186c21cdf6..0067de644847 100644 --- a/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt +++ b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt @@ -862,7 +862,7 @@ class ShowOnAppLaunchOptionHandlerImplTest { override fun countTabsAccessedWithinRange( accessOlderThan: Long, - accessNotMoreThan: Long? + accessNotMoreThan: Long?, ): Int { TODO("Not yet implemented") } From bc07789fe5c1dfd2d92028d6e9958f6a1460b3bd Mon Sep 17 00:00:00 2001 From: 0nko Date: Tue, 19 Nov 2024 13:18:05 +0100 Subject: [PATCH 20/22] Move the bucketing unit tests to non-device tests --- .../java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/{androidTest => test}/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt (100%) diff --git a/app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt similarity index 100% rename from app/src/androidTest/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt rename to app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt From c0e4c2e3592f93b7f442fefbf31efae44c459029 Mon Sep 17 00:00:00 2001 From: 0nko Date: Tue, 19 Nov 2024 14:17:25 +0100 Subject: [PATCH 21/22] Replace runBlocking with runTest --- .../app/tabs/store/TabStatsBucketingTest.kt | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt b/app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt index 03ffb9756db7..511ee02756d8 100644 --- a/app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt +++ b/app/src/test/java/com/duckduckgo/app/tabs/store/TabStatsBucketingTest.kt @@ -16,7 +16,7 @@ package com.duckduckgo.app.tabs.store */ import com.duckduckgo.app.tabs.model.TabRepository -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -35,224 +35,224 @@ class DefaultTabStatsBucketingTest { } @Test - fun testGetNumberOfOpenTabsExactly1() = runBlocking { + fun testGetNumberOfOpenTabsExactly1() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(1) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("1", result) } @Test - fun testGetNumberOfOpenTabsZero() = runBlocking { + fun testGetNumberOfOpenTabsZero() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(0) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("1", result) } @Test - fun testGetNumberOfOpenTabs() = runBlocking { + fun testGetNumberOfOpenTabs() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(5) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("2-5", result) } @Test - fun testGetNumberOfOpenTabs6To10() = runBlocking { + fun testGetNumberOfOpenTabs6To10() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(8) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("6-10", result) } @Test - fun testGetNumberOfOpenTabs11To20() = runBlocking { + fun testGetNumberOfOpenTabs11To20() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(11) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("11-20", result) } @Test - fun testGetNumberOfOpenTabsMoreThan20() = runBlocking { + fun testGetNumberOfOpenTabsMoreThan20() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(40) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("21-40", result) } @Test - fun testGetNumberOfOpenTabs41To60() = runBlocking { + fun testGetNumberOfOpenTabs41To60() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(50) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("41-60", result) } @Test - fun testGetNumberOfOpenTabs61To80() = runBlocking { + fun testGetNumberOfOpenTabs61To80() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(70) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("61-80", result) } @Test - fun testGetNumberOfOpenTabs81To100() = runBlocking { + fun testGetNumberOfOpenTabs81To100() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(90) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("81-100", result) } @Test - fun testGetNumberOfOpenTabs101To125() = runBlocking { + fun testGetNumberOfOpenTabs101To125() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(110) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("101-125", result) } @Test - fun testGetNumberOfOpenTabs126To150() = runBlocking { + fun testGetNumberOfOpenTabs126To150() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(130) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("126-150", result) } @Test - fun testGetNumberOfOpenTabs151To250() = runBlocking { + fun testGetNumberOfOpenTabs151To250() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(200) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("151-250", result) } @Test - fun testGetNumberOfOpenTabs251To500() = runBlocking { + fun testGetNumberOfOpenTabs251To500() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(300) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("251-500", result) } @Test - fun testGetNumberOfOpenTabsMaxValue() = runBlocking { + fun testGetNumberOfOpenTabsMaxValue() = runTest { whenever(tabRepository.getOpenTabCount()).thenReturn(600) val result = defaultTabStatsBucketing.getNumberOfOpenTabs() assertEquals("501+", result) } @Test - fun testGetTabsActiveLastWeekZero() = runBlocking { + fun testGetTabsActiveLastWeekZero() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("0", result) } @Test - fun testGetTabsActiveLastWeekExactly1() = runBlocking { + fun testGetTabsActiveLastWeekExactly1() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("1-5", result) } @Test - fun testGetTabsActiveLastWeek1To5() = runBlocking { + fun testGetTabsActiveLastWeek1To5() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(2) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("1-5", result) } @Test - fun testGetTabsActiveLastWeek6To10() = runBlocking { + fun testGetTabsActiveLastWeek6To10() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("6-10", result) } @Test - fun testGetTabsActiveLastWeek11To20() = runBlocking { + fun testGetTabsActiveLastWeek11To20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("11-20", result) } @Test - fun testGetTabsActiveLastWeekMoreThan20() = runBlocking { + fun testGetTabsActiveLastWeekMoreThan20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("21+", result) } @Test - fun testGetTabsActiveLastWeekALotMoreThan20() = runBlocking { + fun testGetTabsActiveLastWeekALotMoreThan20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(0, TabStatsBucketing.ONE_WEEK_IN_DAYS)).thenReturn(250) val result = defaultTabStatsBucketing.getTabsActiveLastWeek() assertEquals("21+", result) } @Test - fun testGetTabsActiveOneWeekAgoZero() = runBlocking { + fun testGetTabsActiveOneWeekAgoZero() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("0", result) } @Test - fun testGet1WeeksInactiveTabBucketExactly1() = runBlocking { + fun testGet1WeeksInactiveTabBucketExactly1() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("1-5", result) } @Test - fun testGet1WeeksInactiveTabBucket1To5() = runBlocking { + fun testGet1WeeksInactiveTabBucket1To5() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(3) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("1-5", result) } @Test - fun testGetTabsActiveOneWeekAgo6To10() = runBlocking { + fun testGetTabsActiveOneWeekAgo6To10() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(8) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("6-10", result) } @Test - fun testGetTabsActiveOneWeekAgo11To20() = runBlocking { + fun testGetTabsActiveOneWeekAgo11To20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(15) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("11-20", result) } @Test - fun testGetTabsActiveOneWeekAgoMoreThan20() = runBlocking { + fun testGetTabsActiveOneWeekAgoMoreThan20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.ONE_WEEK_IN_DAYS, TabStatsBucketing.TWO_WEEKS_IN_DAYS)).thenReturn(25) val result = defaultTabStatsBucketing.getTabsActiveOneWeekAgo() assertEquals("21+", result) } @Test - fun testGetTabsActiveTwoWeeksAgoZero() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoZero() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("0", result) } @Test - fun testGetTabsActiveTwoWeeksAgoExactly1() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoExactly1() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("1-5", result) } @Test - fun testGetTabsActiveTwoWeeksAgo1To5() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo1To5() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("1-5", result) } @Test - fun testGetTabsActiveTwoWeeksAgo6To10() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo6To10() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(6) val result = defaultTabStatsBucketing.getTabsActiveTwoWeeksAgo() assertEquals("6-10", result) } @Test - fun testGetTabsActiveTwoWeeksAgo11To20() = runBlocking { + fun testGetTabsActiveTwoWeeksAgo11To20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn( 20, ) @@ -261,7 +261,7 @@ class DefaultTabStatsBucketingTest { } @Test - fun testGetTabsActiveTwoWeeksAgoMoreThan20() = runBlocking { + fun testGetTabsActiveTwoWeeksAgoMoreThan20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.TWO_WEEKS_IN_DAYS, TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn( 199, ) @@ -270,42 +270,42 @@ class DefaultTabStatsBucketingTest { } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgoZero() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoZero() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(0) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("0", result) } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgoExactly1() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoExactly1() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(1) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("1-5", result) } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgo1To5() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo1To5() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(5) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("1-5", result) } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgo6To10() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo6To10() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(10) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("6-10", result) } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgo11To20() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgo11To20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(11) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("11-20", result) } @Test - fun testGetTabsActiveMoreThanThreeWeeksAgoMoreThan20() = runBlocking { + fun testGetTabsActiveMoreThanThreeWeeksAgoMoreThan20() = runTest { whenever(tabRepository.countTabsAccessedWithinRange(TabStatsBucketing.THREE_WEEKS_IN_DAYS)).thenReturn(21) val result = defaultTabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() assertEquals("21+", result) From 71e3a02ff31def89bb2d5dc278ecb6b917ab80a8 Mon Sep 17 00:00:00 2001 From: 0nko Date: Tue, 19 Nov 2024 14:24:26 +0100 Subject: [PATCH 22/22] Sync the stats on the IO dispatcher --- .../main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index 176538ef5bbc..d43be5f95105 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -2839,7 +2839,7 @@ class BrowserTabViewModel @Inject constructor( val inactive2w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveTwoWeeksAgo() } val inactive3w = viewModelScope.async(dispatchers.io()) { tabStatsBucketing.getTabsActiveMoreThanThreeWeeksAgo() } - viewModelScope.launch { + viewModelScope.launch(dispatchers.io()) { val params = mapOf( PixelParameter.TAB_COUNT to tabCount.await(), PixelParameter.TAB_ACTIVE_7D to activeTabCount.await(),