Skip to content

Commit

Permalink
Merge pull request #12256 from woocommerce/fix/last-update-with-faile…
Browse files Browse the repository at this point in the history
…d-source

Fix last update with failed source
  • Loading branch information
atorresveiga authored Aug 14, 2024
2 parents 56c3a9f + 6936f3e commit b4366a1
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,30 @@ class AnalyticsUpdateDataStore @Inject constructor(
*/
fun observeLastUpdate(
rangeSelection: StatsTimeRangeSelection,
analyticData: List<AnalyticData>
analyticData: List<AnalyticData>,
shouldAllDataBePresent: Boolean = true
): Flow<Long?> {
val timestampKeys = analyticData.map { data ->
getTimeStampKey(rangeSelection.identifier, data)
}
return observeLastUpdate(timestampKeys)
return observeLastUpdate(timestampKeys, shouldAllDataBePresent)
}

private fun observeLastUpdate(
timestampKeys: List<String>
timestampKeys: List<String>,
shouldAllDataBePresent: Boolean
): Flow<Long?> {
val flows = timestampKeys.map { timestampKey ->
dataStore.data.map { prefs -> prefs[longPreferencesKey(timestampKey)] }
}
return combine(flows) { lastUpdateMillisArray -> lastUpdateMillisArray.filterNotNull() }
.filter { notNullValues -> notNullValues.size == timestampKeys.size }
.filter { notNullValues ->
if (shouldAllDataBePresent) {
notNullValues.size == timestampKeys.size
} else {
true
}
}
.map { lastUpdateValues -> lastUpdateValues.min() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class ObserveLastUpdate @Inject constructor(
) {
operator fun invoke(
selectedRange: StatsTimeRangeSelection,
analyticDataList: List<AnalyticsUpdateDataStore.AnalyticData>
analyticDataList: List<AnalyticsUpdateDataStore.AnalyticData>,
shouldAllDataBePresent: Boolean = true
): Flow<Long?> {
return analyticsUpdateDataStore.observeLastUpdate(
rangeSelection = selectedRange,
analyticData = analyticDataList
analyticData = analyticDataList,
shouldAllDataBePresent = shouldAllDataBePresent
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ class DashboardStatsViewModel @AssistedInject constructor(
listOf(
AnalyticsUpdateDataStore.AnalyticData.REVENUE,
AnalyticsUpdateDataStore.AnalyticData.VISITORS
)
),
false
).collect { lastUpdateMillis -> _lastUpdateStats.value = lastUpdateMillis }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ class AnalyticsHubViewModelTest : BaseUnitTest() {
fun `when last update information changes, then update view state as expected`() = testBlocking {
val lastUpdateTimestamp = 123456789L
whenever(
observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any())
observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any(), shouldAllDataBePresent = eq(true))
).thenReturn(flowOf(lastUpdateTimestamp))
whenever(dateUtils.getDateOrTimeFromMillis(lastUpdateTimestamp)).thenReturn("9:35 AM")
configureVisibleCards()
Expand Down Expand Up @@ -845,7 +845,13 @@ class AnalyticsHubViewModelTest : BaseUnitTest() {
isVisible = true
)
)
whenever(observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any())).thenReturn(flowOf(null))
whenever(
observeLastUpdate.invoke(
selectedRange = any(),
analyticDataList = any(),
shouldAllDataBePresent = eq(true)
)
).thenReturn(flowOf(null))
configureVisibleCards(configuration)
configureSuccessfulStatsResponse()

Expand Down Expand Up @@ -889,8 +895,11 @@ class AnalyticsHubViewModelTest : BaseUnitTest() {
title = AnalyticsCards.Session.name,
isVisible = true
)

)
whenever(observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any())).thenReturn(flowOf(null))
whenever(
observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any(), shouldAllDataBePresent = eq(true))
).thenReturn(flowOf(null))
configureVisibleCards(configuration)
configureSuccessfulStatsResponse()

Expand Down Expand Up @@ -930,7 +939,9 @@ class AnalyticsHubViewModelTest : BaseUnitTest() {

val expectedVisibleCards = configuration.filter { it.isVisible }.map { it.card }

whenever(observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any())).thenReturn(flowOf(null))
whenever(
observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any(), shouldAllDataBePresent = eq(true))
).thenReturn(flowOf(null))
configureVisibleCards(configuration)
configureSuccessfulStatsResponse()

Expand Down Expand Up @@ -972,7 +983,13 @@ class AnalyticsHubViewModelTest : BaseUnitTest() {

val expectedVisibleCards = configuration.filter { it.isVisible }.map { it.card }

whenever(observeLastUpdate.invoke(selectedRange = any(), analyticDataList = any())).thenReturn(flowOf(null))
whenever(
observeLastUpdate.invoke(
selectedRange = any(),
analyticDataList = any(),
shouldAllDataBePresent = eq(true)
)
).thenReturn(flowOf(null))
configureVisibleCards(configuration)
configureSuccessfulStatsResponse()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.analytics.hub.sync
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.analytics.ranges.StatsTimeRangeSelection.SelectionType
import com.woocommerce.android.ui.analytics.ranges.StatsTimeRangeSelection.SelectionType.LAST_MONTH
Expand Down Expand Up @@ -172,6 +173,127 @@ class AnalyticsUpdateDataStoreTest : BaseUnitTest() {
assertThat(timestampUpdate).isEqualTo(2000)
}

@Test
fun `given observe should emit last update for all data sources, when a data source is missing then return null`() = testBlocking {
// Given
val selectedSiteId = 1
val lastUpdateTimestamp = 2000L
val rangeId = defaultSelectionData.selectionType.identifier
val presentKey =
"${selectedSiteId}${AnalyticsUpdateDataStore.AnalyticData.REVENUE}$rangeId"

val analyticsPreferences = mock<Preferences> {
on { get(longPreferencesKey(presentKey)) } doReturn lastUpdateTimestamp
}

createAnalyticsUpdateScenarioWith(analyticsPreferences, selectedSiteId)

// When
var timestampUpdate: Long? = null
sut.observeLastUpdate(
rangeSelection = defaultSelectionData,
analyticData = listOf(
AnalyticsUpdateDataStore.AnalyticData.REVENUE,
AnalyticsUpdateDataStore.AnalyticData.VISITORS
)
).onEach {
timestampUpdate = it
}.launchIn(this)

// Then
assertThat(timestampUpdate).isNull()
}

@Test
fun `given observe should emit last update for all data sources, when all data source are present then return the oldest timestamp`() = testBlocking {
// Given
val selectedSiteId = 1
val oldLastUpdateTimestamp = 2000L
val newLastUpdateTimestamp = 2500L
val rangeId = defaultSelectionData.selectionType.identifier
val keyRevenue =
"${selectedSiteId}${AnalyticsUpdateDataStore.AnalyticData.REVENUE}$rangeId"
val keyVisitors =
"${selectedSiteId}${AnalyticsUpdateDataStore.AnalyticData.VISITORS}$rangeId"

val analyticsPreferences = mock<Preferences> {
on { get(longPreferencesKey(keyRevenue)) } doReturn newLastUpdateTimestamp
on { get(longPreferencesKey(keyVisitors)) } doReturn oldLastUpdateTimestamp
}

createAnalyticsUpdateScenarioWith(analyticsPreferences, selectedSiteId)

// When
var timestampUpdate: Long? = null
sut.observeLastUpdate(
rangeSelection = defaultSelectionData,
analyticData = listOf(
AnalyticsUpdateDataStore.AnalyticData.REVENUE,
AnalyticsUpdateDataStore.AnalyticData.VISITORS
)
).onEach {
timestampUpdate = it
}.launchIn(this)

// Then
assertThat(timestampUpdate).isNotNull()
assertThat(timestampUpdate).isEqualTo(oldLastUpdateTimestamp)
}

@Test
fun `given observe should emit last update, when all data sources are not required, if a data source is missing then return the available last update`() = testBlocking {
// Given
val selectedSiteId = 1
val lastUpdateTimestamp = 2000L
val rangeId = defaultSelectionData.selectionType.identifier
val presentKey =
"${selectedSiteId}${AnalyticsUpdateDataStore.AnalyticData.REVENUE}$rangeId"

val analyticsPreferences = mock<Preferences> {
on { get(longPreferencesKey(presentKey)) } doReturn lastUpdateTimestamp
}

createAnalyticsUpdateScenarioWith(analyticsPreferences, selectedSiteId)

// When
var timestampUpdate: Long? = null
sut.observeLastUpdate(
rangeSelection = defaultSelectionData,
analyticData = listOf(
AnalyticsUpdateDataStore.AnalyticData.REVENUE,
AnalyticsUpdateDataStore.AnalyticData.VISITORS
),
shouldAllDataBePresent = false
).onEach {
timestampUpdate = it
}.launchIn(this)

// Then
assertThat(timestampUpdate).isNotNull()
assertThat(timestampUpdate).isEqualTo(lastUpdateTimestamp)
}

private fun createAnalyticsUpdateScenarioWith(
analyticsPreferences: Preferences,
selectedSiteId: Int
) {
dataStore = mock {
on { data } doReturn flowOf(analyticsPreferences)
}

currentTimeProvider = mock()

val selectedSite: SelectedSite = mock {
on { getSelectedSiteId() } doReturn selectedSiteId
}

sut = AnalyticsUpdateDataStore(
dataStore = dataStore,
currentTimeProvider = currentTimeProvider,
selectedSite = selectedSite
)
}

private fun createAnalyticsUpdateScenarioWith(
lastUpdateTimestamp: Long?,
currentTimestamp: Long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() {
}
private val timezoneProvider: TimezoneProvider = mock()
private val observeLastUpdate: ObserveLastUpdate = mock {
onBlocking { invoke(any(), ArgumentMatchers.anyList()) } doReturn flowOf(DEFAULT_LAST_UPDATE)
onBlocking { invoke(any(), ArgumentMatchers.anyList(), eq(false)) } doReturn flowOf(DEFAULT_LAST_UPDATE)
}
private val dateUtils: DateUtils = mock()
private val parentViewModel: DashboardViewModel = mock {
Expand Down

0 comments on commit b4366a1

Please sign in to comment.