Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit 9272127

Browse files
authored
Merge pull request #2984 from wordpress-mobile/issue/10966-bundles-stats-networking-support
Bundles stats networking support
2 parents d029eb4 + 80cd38c commit 9272127

File tree

8 files changed

+265
-1
lines changed

8 files changed

+265
-1
lines changed

example/src/test/java/org/wordpress/android/fluxc/wc/stats/WCStatsStoreTest.kt

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.wordpress.android.fluxc.wc.stats
22

33
import com.yarolegovich.wellsql.WellSql
4+
import junit.framework.TestCase.assertFalse
45
import kotlinx.coroutines.runBlocking
56
import org.hamcrest.CoreMatchers.anyOf
67
import org.hamcrest.CoreMatchers.not
@@ -23,6 +24,13 @@ import org.wordpress.android.fluxc.UnitTestUtils
2324
import org.wordpress.android.fluxc.model.SiteModel
2425
import org.wordpress.android.fluxc.model.WCNewVisitorStatsModel
2526
import org.wordpress.android.fluxc.model.WCRevenueStatsModel
27+
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType
28+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError
29+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType
30+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload
31+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats.BundleStatsApiResponse
32+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats.BundleStatsRestClient
33+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats.BundleStatsTotals
2634
import org.wordpress.android.fluxc.network.rest.wpcom.wc.orderstats.OrderStatsRestClient
2735
import org.wordpress.android.fluxc.persistence.WCVisitorStatsSqlUtils
2836
import org.wordpress.android.fluxc.persistence.WellSqlConfig
@@ -44,8 +52,14 @@ import org.hamcrest.CoreMatchers.`is` as isEqual
4452
@RunWith(RobolectricTestRunner::class)
4553
class WCStatsStoreTest {
4654
private val mockOrderStatsRestClient = mock<OrderStatsRestClient>()
55+
private val mockBundleStatsRestClient = mock<BundleStatsRestClient>()
4756
private val appContext = RuntimeEnvironment.application.applicationContext
48-
private val wcStatsStore = WCStatsStore(Dispatcher(), mockOrderStatsRestClient, initCoroutineEngine())
57+
private val wcStatsStore = WCStatsStore(
58+
Dispatcher(),
59+
mockOrderStatsRestClient,
60+
mockBundleStatsRestClient,
61+
initCoroutineEngine()
62+
)
4963

5064
@Before
5165
fun setUp() {
@@ -865,4 +879,72 @@ class WCStatsStoreTest {
865879
assertEquals(defaultWeekVisitorStats["2019-07-17"],0)
866880
assertEquals(defaultWeekVisitorStats["2019-07-18"],0)
867881
}
882+
883+
@Test
884+
fun testFetchBundlesErrorResponse() = runBlocking {
885+
val error = WooError(
886+
type = WooErrorType.INVALID_RESPONSE,
887+
original = GenericErrorType.INVALID_RESPONSE,
888+
message = "Invalid Response"
889+
)
890+
val response: WooPayload<BundleStatsApiResponse> = WooPayload(error)
891+
892+
whenever(mockBundleStatsRestClient.fetchBundleStats(any(), any(), any(), any()))
893+
.thenReturn(response)
894+
895+
val result = wcStatsStore.fetchProductBundlesStats(
896+
SiteModel(),
897+
"2024-03-01",
898+
endDate = "2024-04-01",
899+
interval = "day"
900+
)
901+
902+
assertTrue(result.isError)
903+
assertTrue(result.model == null)
904+
assertThat(result.error, isEqual(error))
905+
}
906+
907+
@Test
908+
fun testFetchBundlesNullResponse() = runBlocking {
909+
val response: WooPayload<BundleStatsApiResponse> = WooPayload(null)
910+
911+
whenever(mockBundleStatsRestClient.fetchBundleStats(any(), any(), any(), any()))
912+
.thenReturn(response)
913+
914+
val result = wcStatsStore.fetchProductBundlesStats(
915+
SiteModel(),
916+
"2024-03-01",
917+
endDate = "2024-04-01",
918+
interval = "day"
919+
)
920+
921+
assertTrue(result.isError)
922+
assertTrue(result.model == null)
923+
assertThat(result.error.type, isEqual(WooErrorType.GENERIC_ERROR))
924+
}
925+
926+
@Test
927+
fun testFetchBundlesSuccessResponse() = runBlocking {
928+
val totals = BundleStatsTotals(
929+
itemsSold = 5,
930+
netRevenue = 1000.00
931+
)
932+
val statsResponse = BundleStatsApiResponse(totals = totals)
933+
val response: WooPayload<BundleStatsApiResponse> = WooPayload(statsResponse)
934+
935+
whenever(mockBundleStatsRestClient.fetchBundleStats(any(), any(), any(), any()))
936+
.thenReturn(response)
937+
938+
val result = wcStatsStore.fetchProductBundlesStats(
939+
SiteModel(),
940+
"2024-03-01",
941+
endDate = "2024-04-01",
942+
interval = "day"
943+
)
944+
945+
assertFalse(result.isError)
946+
assertTrue(result.model != null)
947+
assertEquals(result.model!!.itemsSold, totals.itemsSold)
948+
assertEquals(result.model!!.netRevenue, totals.netRevenue)
949+
}
868950
}

fluxc-processor/src/main/resources/wc-wp-api-endpoints.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
/reports/products/totals/
4545
/reports/revenue/stats/
4646

47+
/reports/bundles/
48+
/reports/bundles/stats/
49+
4750
/settings/general/
4851
/settings/products/
4952
/settings/<group_id>#String/<id>#String
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.wordpress.android.fluxc.model
2+
3+
data class WCBundleStats(
4+
val itemsSold: Int,
5+
val netRevenue: Double
6+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.wordpress.android.fluxc.model
2+
3+
data class WCProductBundleItemReport(
4+
val name: String,
5+
val image: String?,
6+
val itemsSold: Int,
7+
val netRevenue: Double
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class BundleStatsApiResponse(val totals: BundleStatsTotals)
6+
7+
data class BundleStatsTotals(
8+
@SerializedName("items_sold")
9+
val itemsSold: Int? = null,
10+
@SerializedName("net_revenue")
11+
val netRevenue: Double? = null,
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats
2+
3+
import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE
4+
import org.wordpress.android.fluxc.model.SiteModel
5+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooNetwork
6+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload
7+
import org.wordpress.android.fluxc.utils.toWooPayload
8+
import javax.inject.Inject
9+
10+
class BundleStatsRestClient @Inject constructor(
11+
private val wooNetwork: WooNetwork
12+
) {
13+
suspend fun fetchBundleStats(
14+
site: SiteModel,
15+
startDate: String,
16+
endDate: String,
17+
interval: String = "",
18+
): WooPayload<BundleStatsApiResponse> {
19+
val url = WOOCOMMERCE.reports.bundles.stats.pathV4Analytics
20+
val parameters = mapOf(
21+
"before" to endDate,
22+
"after" to startDate,
23+
"interval" to interval
24+
).filter { it.value.isNotEmpty() }
25+
26+
val response = wooNetwork.executeGetGsonRequest(
27+
site = site,
28+
path = url,
29+
clazz = BundleStatsApiResponse::class.java,
30+
params = parameters
31+
)
32+
return response.toWooPayload()
33+
}
34+
35+
suspend fun fetchBundleReport(
36+
site: SiteModel,
37+
startDate: String,
38+
endDate: String,
39+
quantity: Int = 5
40+
): WooPayload<Array<BundlesReportApiResponse>> {
41+
val url = WOOCOMMERCE.reports.bundles.pathV4Analytics
42+
val parameters = mapOf(
43+
"before" to endDate,
44+
"after" to startDate,
45+
"orderby" to "items_sold",
46+
"order" to "desc",
47+
"page" to "1",
48+
"per_page" to quantity.toString(),
49+
"extended_info" to "true"
50+
).filter { it.value.isNotEmpty() }
51+
52+
val response = wooNetwork.executeGetGsonRequest(
53+
site = site,
54+
path = url,
55+
clazz = Array<BundlesReportApiResponse>::class.java,
56+
params = parameters
57+
)
58+
return response.toWooPayload()
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class BundlesReportApiResponse(
6+
@SerializedName("items_sold")
7+
val itemsSold: Int? = null,
8+
@SerializedName("net_revenue")
9+
val netRevenue: Double? = null,
10+
@SerializedName("extended_info")
11+
val extendedInfo: ExtendedInfo
12+
)
13+
14+
data class ExtendedInfo(
15+
val name: String? = null,
16+
val image: String? = null,
17+
)

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/store/WCStatsStore.kt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ import org.wordpress.android.fluxc.action.WCStatsAction
88
import org.wordpress.android.fluxc.annotations.action.Action
99
import org.wordpress.android.fluxc.logging.FluxCCrashLoggerProvider.crashLogger
1010
import org.wordpress.android.fluxc.model.SiteModel
11+
import org.wordpress.android.fluxc.model.WCBundleStats
1112
import org.wordpress.android.fluxc.model.WCNewVisitorStatsModel
13+
import org.wordpress.android.fluxc.model.WCProductBundleItemReport
1214
import org.wordpress.android.fluxc.model.WCRevenueStatsModel
15+
import org.wordpress.android.fluxc.network.BaseRequest
1316
import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError
17+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError
18+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType
19+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult
20+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bundlestats.BundleStatsRestClient
1421
import org.wordpress.android.fluxc.network.rest.wpcom.wc.orderstats.OrderStatsRestClient
1522
import org.wordpress.android.fluxc.network.rest.wpcom.wc.orderstats.OrderStatsRestClient.OrderStatsApiUnit
1623
import org.wordpress.android.fluxc.persistence.WCStatsSqlUtils
@@ -29,6 +36,7 @@ import javax.inject.Singleton
2936
class WCStatsStore @Inject constructor(
3037
dispatcher: Dispatcher,
3138
private val wcOrderStatsClient: OrderStatsRestClient,
39+
private val bundleStatsRestClient: BundleStatsRestClient,
3240
private val coroutineEngine: CoroutineEngine
3341
) : Store(dispatcher) {
3442
companion object {
@@ -218,6 +226,74 @@ class WCStatsStore @Inject constructor(
218226
}
219227
}
220228

229+
suspend fun fetchProductBundlesStats(
230+
site: SiteModel,
231+
startDate: String,
232+
endDate: String,
233+
interval: String,
234+
): WooResult<WCBundleStats> {
235+
return coroutineEngine.withDefaultContext(T.API, this, "fetchProductBundlesStats") {
236+
val response = bundleStatsRestClient.fetchBundleStats(
237+
site = site,
238+
startDate = startDate,
239+
endDate = endDate,
240+
interval = interval
241+
)
242+
243+
when {
244+
response.isError -> {
245+
WooResult(response.error)
246+
}
247+
248+
response.result != null -> {
249+
val bundleStats = WCBundleStats(
250+
itemsSold = response.result.totals.itemsSold ?: 0,
251+
netRevenue = response.result.totals.netRevenue ?: 0.0
252+
)
253+
WooResult(bundleStats)
254+
}
255+
256+
else -> WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN))
257+
}
258+
}
259+
}
260+
261+
suspend fun fetchProductBundlesReport(
262+
site: SiteModel,
263+
startDate: String,
264+
endDate: String,
265+
quantity: Int
266+
): WooResult<List<WCProductBundleItemReport>>{
267+
return coroutineEngine.withDefaultContext(T.API, this, "fetchProductBundlesReport") {
268+
val response = bundleStatsRestClient.fetchBundleReport(
269+
site = site,
270+
startDate = startDate,
271+
endDate = endDate,
272+
quantity = quantity
273+
)
274+
275+
when {
276+
response.isError -> {
277+
WooResult(response.error)
278+
}
279+
280+
response.result != null -> {
281+
val bundleStats = response.result.map { item ->
282+
WCProductBundleItemReport(
283+
name = item.extendedInfo.name ?: "",
284+
image = item.extendedInfo.image,
285+
itemsSold = item.itemsSold ?: 0,
286+
netRevenue = item.netRevenue ?: 0.0
287+
)
288+
}
289+
WooResult(bundleStats)
290+
}
291+
292+
else -> WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN))
293+
}
294+
}
295+
}
296+
221297
suspend fun fetchNewVisitorStats(payload: FetchNewVisitorStatsPayload): OnWCStatsChanged {
222298
if (payload.granularity == StatsGranularity.HOURS) {
223299
error("Visitor stats do not support hours granularity")

0 commit comments

Comments
 (0)