Skip to content

Commit 0681588

Browse files
authored
Merge pull request #10624 from woocommerce/fix/widget-woo-express-stores
Fix widget for woo express stores
2 parents 57f48eb + 0e73991 commit 0681588

File tree

5 files changed

+170
-15
lines changed

5 files changed

+170
-15
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/appwidgets/stats/GetWidgetStats.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ class GetWidgetStats @Inject constructor(
3535
// If siteModel is null, exit the function with WidgetStatsFailure
3636
siteModel == null -> WidgetStatsResult.WidgetStatsFailure("No site selected")
3737
else -> {
38-
// Fetch stats, always force to refresh data
3938
val areVisitorStatsSupported = siteModel.connectionType == SiteConnectionType.Jetpack
4039

40+
// Fetch stats, always force to refresh data.
4141
val fetchedStats = statsRepository.fetchStats(
4242
granularity = granularity,
4343
forced = true,
@@ -47,7 +47,7 @@ class GetWidgetStats @Inject constructor(
4747
if (fetchedStats.isError) {
4848
WidgetStatsResult.WidgetStatsFailure(fetchedStats.error.message)
4949
} else {
50-
WidgetStatsResult.WidgetStats(fetchedStats.model!!, areVisitorStatsSupported)
50+
WidgetStatsResult.WidgetStats(fetchedStats.model!!)
5151
}
5252
}
5353
}
@@ -62,15 +62,13 @@ class GetWidgetStats @Inject constructor(
6262
data class WidgetStats(
6363
private val revenueModel: WCRevenueStatsModel?,
6464
private val visitorsMap: Map<String, Int>?,
65-
val currencyCode: String,
66-
val areVisitorStatsSupported: Boolean
65+
val currencyCode: String
6766
) : WidgetStatsResult() {
6867
constructor(
6968
stats: StatsRepository.SiteStats,
70-
areVisitorStatsSupported: Boolean
71-
) : this(stats.revenue, stats.visitors, stats.currencyCode, areVisitorStatsSupported)
69+
) : this(stats.revenue, stats.visitors, stats.currencyCode)
7270

73-
val visitorsTotal: Int
71+
val visitorsTotal: Int?
7472
val ordersTotal: Int
7573
val revenueGross: Double
7674

@@ -82,7 +80,7 @@ class GetWidgetStats @Inject constructor(
8280
orderCount = total.ordersCount ?: 0
8381
}
8482

85-
visitorsTotal = visitorsMap?.values?.sum() ?: 0
83+
visitorsTotal = visitorsMap?.values?.sum()
8684
ordersTotal = orderCount
8785
revenueGross = grossRevenue
8886
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/appwidgets/stats/today/TodayStatsWidgetUIHelper.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ class TodayStatsWidgetUIHelper @Inject constructor(
6767

6868
remoteViews.setTextViewText(R.id.widget_revenue_value, revenue)
6969
remoteViews.setTextViewText(R.id.widget_orders_value, stats.ordersTotal.toString())
70-
remoteViews.setTextViewText(R.id.widget_visitors_value, stats.visitorsTotal.toString())
71-
70+
stats.visitorsTotal?.let {
71+
remoteViews.setTextViewText(R.id.widget_visitors_value, it.toString())
72+
}
7273
remoteViews.setViewVisibility(R.id.widget_revenue_value, View.VISIBLE)
7374
remoteViews.setViewVisibility(R.id.widget_revenue_skeleton, View.INVISIBLE)
7475

@@ -80,11 +81,11 @@ class TodayStatsWidgetUIHelper @Inject constructor(
8081

8182
remoteViews.setViewVisibility(
8283
R.id.widget_visitors_title,
83-
if (stats.areVisitorStatsSupported) View.VISIBLE else View.GONE
84+
if (stats.visitorsTotal != null) View.VISIBLE else View.GONE
8485
)
8586
remoteViews.setViewVisibility(
8687
R.id.widget_visitors_value,
87-
if (stats.areVisitorStatsSupported) View.VISIBLE else View.GONE
88+
if (stats.visitorsTotal != null) View.VISIBLE else View.GONE
8889
)
8990

9091
remoteViews.setTextViewText(

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/data/StatsRepository.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ class StatsRepository @Inject constructor(
285285
}
286286
}
287287

288+
/**
289+
* This function will return the site stats optional including visitor stats.
290+
* Even if the includeVisitorStats flag is set to true, errors fetching visitor
291+
* will be handled as null and only errors fetching the revenue stats will be processed.
292+
*/
288293
suspend fun fetchStats(
289294
granularity: StatsGranularity,
290295
forced: Boolean,
@@ -314,7 +319,10 @@ class StatsRepository @Inject constructor(
314319
val revenueStats = fetchRevenueStats.await()
315320
val siteCurrencyCode = wooCommerceStore.getSiteSettings(site)?.currencyCode.orEmpty()
316321

317-
return@coroutineScope if (visitorStats.isError || revenueStats.isError) {
322+
// If there was an error fetching the visitor stats chances are that is because
323+
// jetpack is not properly configure to return stats. So we take into account
324+
// only revenue stats to return process the error response.
325+
return@coroutineScope if (revenueStats.isError) {
318326
val error = WooError(
319327
type = WooErrorType.GENERIC_ERROR,
320328
original = BaseRequest.GenericErrorType.UNKNOWN,

WooCommerce/src/test/kotlin/com/woocommerce/android/ui/appwidgets/stats/GetWidgetStatsTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class GetWidgetStatsTest : BaseUnitTest() {
133133
}
134134

135135
@Test
136-
fun `when fetchStats succeed then get stats respond with WidgetStatsFailure`() =
136+
fun `when fetchStats succeed then get stats respond with WidgetStats`() =
137137
testBlocking {
138138
// Given the user is logged, v4 stats is supported and network is working fine
139139
whenever(accountRepository.isUserLoggedIn()).thenReturn(true)
@@ -146,7 +146,7 @@ class GetWidgetStatsTest : BaseUnitTest() {
146146

147147
// When GetWidgetStats is invoked
148148
val result = sut.invoke(defaultGranularity, defaultSiteModel)
149-
val expected = GetWidgetStats.WidgetStatsResult.WidgetStats(defaultResponse, true)
149+
val expected = GetWidgetStats.WidgetStatsResult.WidgetStats(defaultResponse)
150150

151151
// Then the result is WidgetStatsFailure
152152
assertThat(result).isEqualTo(expected)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.woocommerce.android.ui.mystore
2+
3+
import com.woocommerce.android.tools.SelectedSite
4+
import com.woocommerce.android.ui.mystore.data.StatsRepository
5+
import com.woocommerce.android.viewmodel.BaseUnitTest
6+
import kotlinx.coroutines.ExperimentalCoroutinesApi
7+
import org.assertj.core.api.Assertions.assertThat
8+
import org.junit.Before
9+
import org.junit.Test
10+
import org.mockito.kotlin.any
11+
import org.mockito.kotlin.eq
12+
import org.mockito.kotlin.mock
13+
import org.mockito.kotlin.whenever
14+
import org.wordpress.android.fluxc.action.WCStatsAction
15+
import org.wordpress.android.fluxc.model.SiteModel
16+
import org.wordpress.android.fluxc.model.WCRevenueStatsModel
17+
import org.wordpress.android.fluxc.store.WCLeaderboardsStore
18+
import org.wordpress.android.fluxc.store.WCOrderStore
19+
import org.wordpress.android.fluxc.store.WCStatsStore
20+
import org.wordpress.android.fluxc.store.WooCommerceStore
21+
22+
@OptIn(ExperimentalCoroutinesApi::class)
23+
class StatsRepositoryTests : BaseUnitTest() {
24+
25+
private val selectedSite: SelectedSite = mock()
26+
private val wcStatsStore: WCStatsStore = mock()
27+
private val wcOrderStore: WCOrderStore = mock()
28+
private val wcLeaderboardsStore: WCLeaderboardsStore = mock()
29+
private val wooCommerceStore: WooCommerceStore = mock()
30+
31+
private lateinit var sut: StatsRepository
32+
33+
private val defaultSiteModel = SiteModel()
34+
35+
@Before
36+
fun setup() {
37+
sut = StatsRepository(
38+
selectedSite = selectedSite,
39+
wcStatsStore = wcStatsStore,
40+
wcOrderStore = wcOrderStore,
41+
wcLeaderboardsStore = wcLeaderboardsStore,
42+
wooCommerceStore = wooCommerceStore
43+
)
44+
}
45+
46+
@Test
47+
fun `when visitors and revenue requests succeed then a success response is returned containing both value`() = testBlocking {
48+
val granularity = WCStatsStore.StatsGranularity.DAYS
49+
val startDate = "2024-01-25 00:00:00"
50+
val endDate = "2024-01-25 23:59:59"
51+
val visitorStatsResponse = WCStatsStore.OnWCStatsChanged(
52+
rowsAffected = 2,
53+
granularity = granularity,
54+
quantity = "5",
55+
date = startDate
56+
)
57+
58+
val revenueStatsResponse = WCStatsStore.OnWCRevenueStatsChanged(
59+
rowsAffected = 2,
60+
granularity = granularity,
61+
startDate = startDate,
62+
endDate = endDate
63+
)
64+
65+
whenever(selectedSite.get()).thenReturn(defaultSiteModel)
66+
whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(null)
67+
whenever(wcStatsStore.fetchNewVisitorStats(any())).thenReturn(visitorStatsResponse)
68+
whenever(wcStatsStore.fetchRevenueStats(any())).thenReturn(revenueStatsResponse)
69+
whenever(wcStatsStore.getRawRevenueStats(eq(defaultSiteModel), eq(granularity), eq(startDate), eq(endDate)))
70+
.thenReturn(WCRevenueStatsModel())
71+
72+
val result = sut.fetchStats(
73+
granularity = WCStatsStore.StatsGranularity.DAYS,
74+
forced = true,
75+
includeVisitorStats = true
76+
)
77+
78+
assertThat(result.isError).isEqualTo(false)
79+
assertThat(result.model).isNotNull
80+
assertThat(result.model!!.revenue).isNotNull
81+
assertThat(result.model!!.visitors).isNotNull
82+
}
83+
84+
@Test
85+
fun `when visitors requests fails then a success response is returned with visitors null`() = testBlocking {
86+
val granularity = WCStatsStore.StatsGranularity.DAYS
87+
val startDate = "2024-01-25 00:00:00"
88+
val endDate = "2024-01-25 23:59:59"
89+
val visitorStatsResponse = WCStatsStore.OnWCStatsChanged(0, granularity).also {
90+
it.error = WCStatsStore.OrderStatsError()
91+
it.causeOfChange = WCStatsAction.FETCH_NEW_VISITOR_STATS
92+
}
93+
94+
val revenueStatsResponse = WCStatsStore.OnWCRevenueStatsChanged(
95+
rowsAffected = 2,
96+
granularity = granularity,
97+
startDate = startDate,
98+
endDate = endDate
99+
)
100+
101+
whenever(selectedSite.get()).thenReturn(defaultSiteModel)
102+
whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(null)
103+
whenever(wcStatsStore.fetchNewVisitorStats(any())).thenReturn(visitorStatsResponse)
104+
whenever(wcStatsStore.fetchRevenueStats(any())).thenReturn(revenueStatsResponse)
105+
whenever(wcStatsStore.getRawRevenueStats(eq(defaultSiteModel), eq(granularity), eq(startDate), eq(endDate)))
106+
.thenReturn(WCRevenueStatsModel())
107+
108+
val result = sut.fetchStats(
109+
granularity = WCStatsStore.StatsGranularity.DAYS,
110+
forced = true,
111+
includeVisitorStats = true
112+
)
113+
114+
assertThat(result.isError).isEqualTo(false)
115+
assertThat(result.model).isNotNull
116+
assertThat(result.model!!.revenue).isNotNull
117+
assertThat(result.model!!.visitors).isNull()
118+
}
119+
120+
@Test
121+
fun `when revenue requests fails then an error is returned`() = testBlocking {
122+
val granularity = WCStatsStore.StatsGranularity.DAYS
123+
val startDate = "2024-01-25 00:00:00"
124+
val visitorStatsResponse = WCStatsStore.OnWCStatsChanged(
125+
rowsAffected = 2,
126+
granularity = granularity,
127+
quantity = "5",
128+
date = startDate
129+
)
130+
131+
val revenueStatsResponse = WCStatsStore.OnWCRevenueStatsChanged(0, granularity)
132+
.also { it.error = WCStatsStore.OrderStatsError() }
133+
134+
whenever(selectedSite.get()).thenReturn(defaultSiteModel)
135+
whenever(wooCommerceStore.getSiteSettings(any())).thenReturn(null)
136+
whenever(wcStatsStore.fetchNewVisitorStats(any())).thenReturn(visitorStatsResponse)
137+
whenever(wcStatsStore.fetchRevenueStats(any())).thenReturn(revenueStatsResponse)
138+
139+
val result = sut.fetchStats(
140+
granularity = WCStatsStore.StatsGranularity.DAYS,
141+
forced = true,
142+
includeVisitorStats = true
143+
)
144+
145+
assertThat(result.isError).isEqualTo(true)
146+
assertThat(result.model).isNull()
147+
}
148+
}

0 commit comments

Comments
 (0)