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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.wordpress.android.ui.activitylog

import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.ui.jetpack.JetpackCapabilitiesUseCase
import javax.inject.Inject

/**
* Determines which activity log groups should be hidden from the user for a given site.
*
* WordPress.com backs up Simple sites internally and writes those events to the activity log regardless of plan,
* so the activity endpoint can return backup ("rewind") and scan activities for sites whose plan does not include
* those products. The web Activity Log hides these groups (see getActivityLogHiddenGroups in wp-calypso), and the
* apps need to do the same to stay consistent with it.
*
* The groups stay visible when the site has the "backups-self-serve" plan feature (the same check the web makes).
* The plan feature list is not guaranteed to reflect standalone product purchases on self-hosted Jetpack sites,
* so each group also stays visible when the matching Jetpack product was purchased.
*/
class GetActivityLogHiddenGroupsUseCase @Inject constructor(
private val jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase
) {
fun getHiddenGroups(site: SiteModel): List<String> {
if (hasBackupsSelfServeFeature(site)) {
return emptyList()
}
val purchasedProducts = jetpackCapabilitiesUseCase.getCachedJetpackPurchasedProducts(site.siteId)
return listOfNotNull(
REWIND_GROUP.takeUnless { purchasedProducts.backup },
SCAN_GROUP.takeUnless { purchasedProducts.scan }
)
}

private fun hasBackupsSelfServeFeature(site: SiteModel) =
site.planActiveFeatures?.split(",")?.contains(BACKUPS_SELF_SERVE_FEATURE) == true
}

private const val BACKUPS_SELF_SERVE_FEATURE = "backups-self-serve"
private const val REWIND_GROUP = "rewind"
private const val SCAN_GROUP = "scan"
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId
import org.wordpress.android.fluxc.model.activity.ActivityTypeModel
import org.wordpress.android.fluxc.store.ActivityLogStore
import org.wordpress.android.fluxc.store.ActivityLogStore.FetchActivityTypesPayload
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.modules.BG_THREAD
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.ui.activitylog.GetActivityLogHiddenGroupsUseCase
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.UiState.Content
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.UiState.Error
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.UiState.FullscreenLoading
Expand All @@ -27,6 +29,8 @@ import javax.inject.Named

class ActivityLogTypeFilterViewModel @Inject constructor(
private val activityLogStore: ActivityLogStore,
private val siteStore: SiteStore,
private val getActivityLogHiddenGroupsUseCase: GetActivityLogHiddenGroupsUseCase,
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher) {
Expand Down Expand Up @@ -70,11 +74,21 @@ class ActivityLogTypeFilterViewModel @Inject constructor(
if (response.isError) {
_uiState.value = buildErrorUiState()
} else {
_uiState.value = buildContentUiState(response.activityTypeModels)
_uiState.value = buildContentUiState(filterOutHiddenGroups(response.activityTypeModels))
}
}
}

/**
* Groups hidden from the activity log list are removed from the filter too, so the user cannot select a
* filter that would always produce an empty list.
*/
private fun filterOutHiddenGroups(activityTypes: List<ActivityTypeModel>): List<ActivityTypeModel> {
val site = siteStore.getSiteBySiteId(remoteSiteId.value) ?: return activityTypes
val hiddenGroups = getActivityLogHiddenGroupsUseCase.getHiddenGroups(site)
return activityTypes.filterNot { hiddenGroups.contains(it.key) }
}

private fun buildErrorUiState() =
Error.ConnectionError(Action(UiStringRes(R.string.retry)).apply { action = ::onRetryClicked })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.wordpress.android.fluxc.model.activity.ActivityTypeModel
import org.wordpress.android.fluxc.store.ActivityLogStore
import org.wordpress.android.fluxc.store.ActivityLogStore.OnActivityLogFetched
import org.wordpress.android.ui.activitylog.ActivityLogNavigationEvents
import org.wordpress.android.ui.activitylog.GetActivityLogHiddenGroupsUseCase
import org.wordpress.android.ui.activitylog.list.ActivityLogListItem
import org.wordpress.android.ui.jetpack.JetpackCapabilitiesUseCase
import org.wordpress.android.ui.jetpack.backup.download.BackupDownloadRequestState
Expand Down Expand Up @@ -69,7 +70,8 @@ class ActivityLogViewModel @Inject constructor(
private val resourceProvider: ResourceProvider,
private val statsDateUtils: StatsDateUtils,
private val activityLogTracker: ActivityLogTracker,
private val jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase
private val jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase,
private val getActivityLogHiddenGroupsUseCase: GetActivityLogHiddenGroupsUseCase
) : ViewModel() {
enum class ActivityLogListStatus {
CAN_LOAD_MORE,
Expand Down Expand Up @@ -336,7 +338,9 @@ class ActivityLogViewModel @Inject constructor(
loadMore,
currentDateRangeFilter?.first?.let { Date(it) },
currentDateRangeFilter?.second?.let { Date(it) },
currentActivityTypeFilter.map { it.key }
currentActivityTypeFilter.map { it.key },
// The Backup screen exists to show the rewind group, so never hide groups there
if (rewindableOnly) emptyList() else getActivityLogHiddenGroupsUseCase.getHiddenGroups(site)
)
fetchActivitiesJob = viewModelScope.launch {
val result = activityLogStore.fetchActivities(payload)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.wordpress.android.ui.activitylog

import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.ui.jetpack.JetpackCapabilitiesUseCase
import org.wordpress.android.ui.jetpack.JetpackCapabilitiesUseCase.JetpackPurchasedProducts

private const val SITE_ID = 123L

@RunWith(MockitoJUnitRunner::class)
class GetActivityLogHiddenGroupsUseCaseTest {
@Mock
private lateinit var jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase

private lateinit var useCase: GetActivityLogHiddenGroupsUseCase
private lateinit var site: SiteModel

@Before
fun setUp() {
useCase = GetActivityLogHiddenGroupsUseCase(jetpackCapabilitiesUseCase)
site = SiteModel().apply { siteId = SITE_ID }
}

@Test
fun `no groups hidden, when site has the backups-self-serve feature`() {
site.planActiveFeatures = "subscriber-unlimited-imports,backups-self-serve,support"

assertThat(useCase.getHiddenGroups(site)).isEmpty()
}

@Test
fun `rewind and scan hidden, when site has no backup feature or purchases`() {
site.planActiveFeatures = "subscriber-unlimited-imports,support"
stubPurchasedProducts(scan = false, backup = false)

assertThat(useCase.getHiddenGroups(site)).containsExactly("rewind", "scan")
}

@Test
fun `rewind and scan hidden, when site has no plan features and no purchases`() {
site.planActiveFeatures = null
stubPurchasedProducts(scan = false, backup = false)

assertThat(useCase.getHiddenGroups(site)).containsExactly("rewind", "scan")
}

@Test
fun `only scan hidden, when site purchased a backup product`() {
site.planActiveFeatures = null
stubPurchasedProducts(scan = false, backup = true)

assertThat(useCase.getHiddenGroups(site)).containsExactly("scan")
}

@Test
fun `only rewind hidden, when site purchased a scan product`() {
site.planActiveFeatures = null
stubPurchasedProducts(scan = true, backup = false)

assertThat(useCase.getHiddenGroups(site)).containsExactly("rewind")
}

@Test
fun `no groups hidden, when site purchased backup and scan products`() {
site.planActiveFeatures = null
stubPurchasedProducts(scan = true, backup = true)

assertThat(useCase.getHiddenGroups(site)).isEmpty()
}

private fun stubPurchasedProducts(scan: Boolean, backup: Boolean) {
whenever(jetpackCapabilitiesUseCase.getCachedJetpackPurchasedProducts(SITE_ID))
.thenReturn(JetpackPurchasedProducts(scan = scan, backup = backup))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import org.mockito.kotlin.whenever
import org.wordpress.android.BaseUnitTest
import org.wordpress.android.fluxc.action.ActivityLogAction.FETCH_ACTIVITY_TYPES
import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.activity.ActivityTypeModel
import org.wordpress.android.fluxc.store.ActivityLogStore
import org.wordpress.android.fluxc.store.ActivityLogStore.ActivityTypesError
import org.wordpress.android.fluxc.store.ActivityLogStore.ActivityTypesErrorType.GENERIC_ERROR
import org.wordpress.android.fluxc.store.ActivityLogStore.FetchActivityTypesPayload
import org.wordpress.android.fluxc.store.ActivityLogStore.OnActivityTypesFetched
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.ui.activitylog.GetActivityLogHiddenGroupsUseCase
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.ListItemUiState
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.ListItemUiState.ActivityType
import org.wordpress.android.ui.activitylog.list.filter.ActivityLogTypeFilterViewModel.UiState
Expand All @@ -40,10 +43,18 @@ class ActivityLogTypeFilterViewModelTest : BaseUnitTest() {
@Mock
private lateinit var activityLogStore: ActivityLogStore

@Mock
private lateinit var siteStore: SiteStore

@Mock
private lateinit var getActivityLogHiddenGroupsUseCase: GetActivityLogHiddenGroupsUseCase

@Before
fun setUp() {
viewModel = ActivityLogTypeFilterViewModel(
activityLogStore,
siteStore,
getActivityLogHiddenGroupsUseCase,
testDispatcher(),
testDispatcher()
)
Expand Down Expand Up @@ -207,6 +218,20 @@ class ActivityLogTypeFilterViewModelTest : BaseUnitTest() {
).containsExactlyElementsOf(initialSelection)
}

@Test
fun `hidden groups are not shown in the list, when site hides activity log groups`() = test {
init(activityTypeCount = 3)
val site = SiteModel()
whenever(siteStore.getSiteBySiteId(REMOTE_SITE_ID)).thenReturn(site)
whenever(getActivityLogHiddenGroupsUseCase.getHiddenGroups(site)).thenReturn(listOf("1"))

startVM()

assertThat(
(viewModel.uiState.value as Content).items.filterIsInstance(ActivityType::class.java).map { it.id }
).containsExactly("2", "3")
}

@Test
fun `date range passed to the api, when provided on view model start`() = test {
val after = 1L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.wordpress.android.fluxc.store.ActivityLogStore.FetchActivityLogPayloa
import org.wordpress.android.fluxc.store.ActivityLogStore.OnActivityLogFetched
import org.wordpress.android.ui.activitylog.ActivityLogNavigationEvents
import org.wordpress.android.ui.activitylog.ActivityLogNavigationEvents.DownloadBackupFile
import org.wordpress.android.ui.activitylog.GetActivityLogHiddenGroupsUseCase
import org.wordpress.android.ui.activitylog.list.ActivityLogListItem
import org.wordpress.android.ui.activitylog.list.ActivityLogListItem.Notice
import org.wordpress.android.ui.activitylog.list.ActivityLogListItem.Progress.Type.BACKUP_DOWNLOAD
Expand Down Expand Up @@ -127,6 +128,9 @@ class ActivityLogViewModelTest : BaseUnitTest() {
@Mock
private lateinit var postDismissBackupDownloadUseCase: PostDismissBackupDownloadUseCase

@Mock
private lateinit var getActivityLogHiddenGroupsUseCase: GetActivityLogHiddenGroupsUseCase

private lateinit var fetchActivityLogCaptor: KArgumentCaptor<FetchActivityLogPayload>
private lateinit var formatDateRangeTimezoneCaptor: KArgumentCaptor<String>
private lateinit var viewModel: ActivityLogViewModel
Expand All @@ -152,7 +156,8 @@ class ActivityLogViewModelTest : BaseUnitTest() {
resourceProvider,
mStatsDateUtils,
activityLogTracker,
jetpackCapabilitiesUseCase
jetpackCapabilitiesUseCase,
getActivityLogHiddenGroupsUseCase
)
viewModel.site = site
viewModel.rewindableOnly = rewindableOnly
Expand Down Expand Up @@ -195,6 +200,25 @@ class ActivityLogViewModelTest : BaseUnitTest() {
assertFetchEvents()
}

@Test
fun fetchesEventsWithHiddenGroupsExcluded() = test {
whenever(getActivityLogHiddenGroupsUseCase.getHiddenGroups(site)).thenReturn(listOf("rewind", "scan"))

viewModel.start(site, rewindableOnly)

verify(store).fetchActivities(fetchActivityLogCaptor.capture())
assertEquals(listOf("rewind", "scan"), fetchActivityLogCaptor.lastValue.notGroups)
}

@Test
fun fetchesEventsWithoutHiddenGroupsWhenRewindableOnly() = test {
viewModel.start(site, rewindableOnly = true)

verify(store).fetchActivities(fetchActivityLogCaptor.capture())
assertTrue(fetchActivityLogCaptor.lastValue.notGroups.isEmpty())
verify(getActivityLogHiddenGroupsUseCase, never()).getHiddenGroups(anyOrNull())
}

@Test
fun fetchesEventsOnPullToRefresh() = test {
viewModel.onPullToRefresh()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ class ActivityLogRestClient @Inject constructor(
payload.groups.forEachIndexed { index, value ->
params["group[$index]"] = value
}
payload.notGroups.forEachIndexed { index, value ->
params["not_group[$index]"] = value
}
return params
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ class ActivityLogStore
val loadMore: Boolean = false,
val after: Date? = null,
val before: Date? = null,
val groups: List<String> = listOf()
val groups: List<String> = listOf(),
val notGroups: List<String> = listOf()
) : Payload<BaseRequest.BaseNetworkError>()

class FetchRewindStatePayload(val site: SiteModel) : Payload<BaseRequest.BaseNetworkError>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,23 @@ class ActivityLogRestClientTest {
}
}

@Test
fun fetchActivity_passesNotGroupParamsToBuildRequest() = test {
initFetchActivity()
val payload = FetchActivityLogPayload(
site,
false,
notGroups = listOf("rewind", "scan")
)

activityRestClient.fetchActivity(payload, number, offset)

with(paramsCaptor.firstValue) {
assertEquals("rewind", this["not_group[0]"])
assertEquals("scan", this["not_group[1]"])
}
}

@Test
fun fetchActivity_passesOnlyNonEmptyParamsToBuildRequest() = test {
initFetchActivity()
Expand Down
Loading